@servlyadmin/runtime-core
v0.1.46
Published
Framework-agnostic core renderer for Servly components
Maintainers
Readme
@servlyadmin/runtime-core
Framework-agnostic core renderer for Servly components. This package provides the foundation for rendering Servly components in any JavaScript environment.
Installation
npm install @servlyadmin/runtime-core
# or
yarn add @servlyadmin/runtime-core
# or
pnpm add @servlyadmin/runtime-coreQuick Start
The simplest way to render a component:
import { mount } from '@servlyadmin/runtime-core';
// Mount a component - that's it!
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello World' }
});
// Update props
app.update({ props: { title: 'Updated!' } });
// Cleanup
app.destroy();With Callbacks
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello' },
onReady: (result) => console.log('Mounted!', result.version),
onError: (err) => console.error('Failed:', err)
});With Version & Cache Control
const app = await mount({
componentId: 'my-component-id',
target: document.getElementById('app'),
props: { title: 'Hello' },
version: '^1.0.0',
fetchOptions: {
cacheStrategy: 'memory',
forceRefresh: true
}
});Low-Level API
For more control, use fetchComponent and render directly:
import { render, fetchComponent } from '@servlyadmin/runtime-core';
// Fetch a component from the registry
const { data } = await fetchComponent('my-component-id');
// Render to a container
const result = render({
container: document.getElementById('app'),
elements: data.layout,
context: {
props: { title: 'Hello World' },
state: {},
context: {},
},
});
// Update props
result.update({
props: { title: 'Updated Title' },
state: {},
context: {},
});
// Cleanup
result.destroy();Registry Configuration
By default, components are fetched from Servly's production registry:
- Default URL:
https://core-api.servly.app/v1/views/registry
You can override this if you have a custom registry:
import { setRegistryUrl } from '@servlyadmin/runtime-core';
// Use a custom registry
setRegistryUrl('https://your-api.com/v1/views/registry');Cache Strategies
The runtime supports three caching strategies to optimize component loading:
| Strategy | Description | Persistence | Best For |
|----------|-------------|-------------|----------|
| localStorage | Persists across browser sessions | Yes | Production apps (default) |
| memory | In-memory cache, cleared on page refresh | No | Development, SSR |
| none | No caching, always fetches fresh | No | Testing, debugging |
Default: localStorage - Components are cached in the browser's localStorage for fast subsequent loads.
// Use default localStorage caching
const { data } = await fetchComponent('my-component');
// Explicitly set cache strategy
const { data } = await fetchComponent('my-component', {
cacheStrategy: 'memory', // or 'localStorage' or 'none'
});
// Force refresh (bypass cache)
const { data } = await fetchComponent('my-component', {
forceRefresh: true,
});Core Concepts
Layout Elements
Components are defined as a tree of layout elements:
interface LayoutElement {
i: string; // Unique identifier
componentId: string; // Element type (container, text, button, etc.)
configuration?: { // Element configuration
classNames?: string;
style?: Record<string, any>;
text?: string;
// ... other attributes
};
children?: string[]; // Child element IDs
parent?: string; // Parent element ID
}Binding Context
Data is passed to components through a binding context:
interface BindingContext {
props: Record<string, any>; // Component props
state: Record<string, any>; // Component state
context: Record<string, any>; // Additional context
}Template Bindings
Use {{path}} syntax to bind data:
const elements = [
{
i: 'greeting',
componentId: 'text',
configuration: {
text: 'Hello, {{props.name}}!',
classNames: '{{props.className}}',
},
},
];API Reference
mount(options) - Recommended
The simplest way to render a component. Handles fetching, container resolution, and rendering.
const app = await mount({
componentId: string, // Required: Component ID to fetch
target: string | HTMLElement, // Required: CSS selector or element
props?: Record<string, any>, // Props to pass (default: {})
state?: Record<string, any>, // Initial state (default: {})
context?: Record<string, any>, // Additional context (default: {})
version?: string, // Version specifier (default: 'latest')
eventHandlers?: Record<string, Record<string, (e: Event) => void>>,
fetchOptions?: {
cacheStrategy?: 'localStorage' | 'memory' | 'none',
forceRefresh?: boolean,
apiKey?: string,
retryConfig?: RetryConfig,
},
onReady?: (result: MountResult) => void,
onError?: (error: Error) => void,
});
// Returns MountResult
interface MountResult {
update(context: Partial<BindingContext>): void;
destroy(): void;
rootElement: HTMLElement | null;
container: HTMLElement;
data: ComponentData;
fromCache: boolean;
version: string;
}mountData(options)
Mount with pre-fetched data (useful for SSR or custom data sources):
import { mountData } from '@servlyadmin/runtime-core';
const app = mountData({
data: myComponentData, // Pre-fetched ComponentData
target: '#app',
props: { title: 'Hello' }
});render(options)
Renders elements to a container.
const result = render({
container: HTMLElement,
elements: LayoutElement[],
context: BindingContext,
eventHandlers?: Record<string, Record<string, (e: Event) => void>>,
});
// Returns
interface RenderResult {
update(context: BindingContext): void;
destroy(): void;
getElement(id: string): HTMLElement | null;
}fetchComponent(id, options?)
Fetches a component from the registry.
const { data, fromCache, version } = await fetchComponent('component-id', {
version: 'latest', // Version specifier (default: 'latest')
cacheStrategy: 'localStorage', // 'localStorage' | 'memory' | 'none' (default: 'localStorage')
forceRefresh: false, // Bypass cache (default: false)
retryConfig: {
maxRetries: 3, // Number of retry attempts (default: 3)
initialDelay: 1000, // Initial retry delay in ms (default: 1000)
maxDelay: 10000, // Maximum retry delay in ms (default: 10000)
backoffMultiplier: 2, // Exponential backoff multiplier (default: 2)
},
});setRegistryUrl(url)
Configure a custom registry URL.
import { setRegistryUrl, DEFAULT_REGISTRY_URL } from '@servlyadmin/runtime-core';
// Use custom registry
setRegistryUrl('https://your-api.com/v1/views/registry');
// Reset to default
setRegistryUrl(DEFAULT_REGISTRY_URL);StateManager
Manages component state with subscriptions.
import { StateManager } from '@servlyadmin/runtime-core';
const stateManager = new StateManager({ count: 0 });
// Get/set state
stateManager.set('count', 1);
stateManager.get('count'); // 1
stateManager.set('user.name', 'John');
// Subscribe to changes
const unsubscribe = stateManager.subscribe((event) => {
console.log('State changed:', event.path, event.value);
});
// Cleanup
stateManager.clear();EventSystem
Handles events with plugin-based actions.
import { EventSystem, getEventSystem } from '@servlyadmin/runtime-core';
const eventSystem = getEventSystem();
// Register custom plugin
eventSystem.registerPlugin('my-action', async (action, context) => {
console.log('Action executed with:', action.config);
});Built-in Plugins
executeCode- Execute arbitrary JavaScript codestate-setState- Update state valuesnavigateTo- Navigate to URLlocalStorage-set/get/remove- LocalStorage operationssessionStorage-set/get- SessionStorage operationsalert- Show alert dialogconsole-log- Log to consoleclipboard-copy- Copy text to clipboardscrollTo- Scroll to elementfocus/blur- Focus/blur elementsaddClass/removeClass/toggleClass- CSS class manipulationsetAttribute/removeAttribute- Attribute manipulationdispatchEvent- Dispatch custom eventsdelay- Add delay between actions
Cache Management
import {
clearAllCaches,
clearMemoryCache,
clearLocalStorageCache,
getMemoryCacheSize
} from '@servlyadmin/runtime-core';
// Clear all caches
clearAllCaches();
// Clear specific cache
clearMemoryCache();
clearLocalStorageCache();
// Get cache size
const size = getMemoryCacheSize();Bindings
Template resolution utilities.
import { resolveTemplate, hasTemplateSyntax } from '@servlyadmin/runtime-core';
const context = {
props: { name: 'World', count: 42 },
state: {},
context: {},
};
// Resolve single template
resolveTemplate('Hello, {{props.name}}!', context); // "Hello, World!"
// Check if string has template syntax
hasTemplateSyntax('{{props.name}}'); // true
hasTemplateSyntax('static text'); // falseSlots
Components can define slots for content injection:
const elements = [
{
i: 'card',
componentId: 'container',
configuration: { classNames: 'card' },
children: ['header-slot', 'content-slot'],
},
{
i: 'header-slot',
componentId: 'slot',
configuration: {
slotName: 'header',
},
parent: 'card',
},
{
i: 'content-slot',
componentId: 'slot',
configuration: {
slotName: 'default',
},
parent: 'card',
},
];Framework wrappers handle slot content injection automatically.
TypeScript Support
Full TypeScript support with exported types:
import type {
LayoutElement,
BindingContext,
RenderResult,
RenderOptions,
ComponentData,
CacheStrategy,
RetryConfig,
FetchOptions,
FetchResult,
} from '@servlyadmin/runtime-core';Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
License
MIT
