
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
@servlyadmin/runtime-core
Advanced tools
Framework-agnostic core renderer for Servly components with prefetching and loading states
Framework-agnostic core renderer for Servly components. This package provides the foundation for rendering Servly components in any JavaScript environment.
npm install @servlyadmin/runtime-core
# or
yarn add @servlyadmin/runtime-core
# or
pnpm add @servlyadmin/runtime-core
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();
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)
});
const app = await mount({
componentId: 'my-component-id',
target: '#app',
props: { title: 'Hello' },
// Show loading indicator
loadingComponent: '<div class="skeleton animate-pulse h-32 bg-gray-200 rounded"></div>',
// Show error message on failure
errorComponent: (err) => `<div class="text-red-500">Failed to load: ${err.message}</div>`,
// Optional: delay before showing loading (avoids flash for fast loads)
loadingDelay: 200,
// Optional: minimum time to show loading (avoids flash)
minLoadingTime: 500,
// Lifecycle callbacks
onLoadStart: () => console.log('Loading...'),
onLoadEnd: () => console.log('Done!'),
onReady: (result) => console.log('Mounted!'),
onError: (err) => console.error('Failed:', err)
});
const app = await mount({
componentId: 'my-component-id',
target: document.getElementById('app'),
props: { title: 'Hello' },
version: '^1.0.0',
fetchOptions: {
cacheStrategy: 'memory',
forceRefresh: true
}
});
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();
By default, components are fetched from Servly's production registry:
https://core-api.servly.app/v1/views/registryYou 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');
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,
});
Preload components for faster subsequent rendering:
import { prefetch, prefetchAll, prefetchOnHover, prefetchOnVisible, prefetchOnIdle } from '@servlyadmin/runtime-core';
// Prefetch a single component
await prefetch('pricing-card');
// Prefetch multiple components
await prefetchAll(['pricing-card', 'contact-form', 'navbar']);
// Prefetch with versions
await prefetchAll([
{ id: 'pricing-card', version: '^1.0.0' },
{ id: 'contact-form', version: 'latest' }
]);
// Prefetch on hover (great for modals/dialogs)
const cleanup = prefetchOnHover('#open-modal-btn', 'modal-component');
// Later: cleanup() to remove listener
// Prefetch when element becomes visible (Intersection Observer)
const cleanup = prefetchOnVisible('#pricing-section', ['pricing-card', 'feature-list']);
// Prefetch when browser is idle (non-critical components)
prefetchOnIdle(['footer', 'sidebar', 'help-modal']);
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
}
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
}
Use {{path}} syntax to bind data:
const elements = [
{
i: 'greeting',
componentId: 'text',
configuration: {
text: 'Hello, {{props.name}}!',
classNames: '{{props.className}}',
},
},
];
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,
},
// Loading & Error States
loadingComponent?: string | HTMLElement, // HTML to show while loading
errorComponent?: (err: Error) => string | HTMLElement, // Error display
loadingDelay?: number, // Delay before showing loading (ms)
minLoadingTime?: number, // Minimum loading display time (ms)
// Lifecycle Callbacks
onLoadStart?: () => void, // Called when loading starts
onLoadEnd?: () => void, // Called when loading ends
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;
}
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' }
});
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;
}
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)
},
});
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);
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();
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);
});
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 actionsimport {
clearAllCaches,
clearMemoryCache,
clearLocalStorageCache,
getMemoryCacheSize
} from '@servlyadmin/runtime-core';
// Clear all caches
clearAllCaches();
// Clear specific cache
clearMemoryCache();
clearLocalStorageCache();
// Get cache size
const size = getMemoryCacheSize();
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'); // false
Preload components for faster subsequent rendering.
import {
prefetch,
prefetchAll,
prefetchOnHover,
prefetchOnVisible,
prefetchOnIdle
} from '@servlyadmin/runtime-core';
// Prefetch single component
await prefetch('pricing-card');
await prefetch('pricing-card', '^1.0.0'); // With version
// Prefetch multiple components
const { success, failed } = await prefetchAll([
'pricing-card',
'contact-form',
{ id: 'navbar', version: '^2.0.0' }
]);
// Prefetch on hover (returns cleanup function)
const cleanup = prefetchOnHover(
'#open-modal-btn', // Target element
'modal-component', // Component ID
'latest', // Version
{ delay: 100 } // Options: delay before prefetch
);
// Later: cleanup()
// Prefetch when element becomes visible
const cleanup = prefetchOnVisible(
'#pricing-section', // Target element
['pricing-card', 'feature-list'], // Components to prefetch
{ rootMargin: '100px', threshold: 0 } // IntersectionObserver options
);
// Prefetch during browser idle time
prefetchOnIdle(
['footer', 'sidebar', 'help-modal'],
{ timeout: 5000 } // Max wait time before forcing prefetch
);
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.
Full TypeScript support with exported types:
import type {
LayoutElement,
BindingContext,
RenderResult,
RenderOptions,
ComponentData,
CacheStrategy,
RetryConfig,
FetchOptions,
FetchResult,
} from '@servlyadmin/runtime-core';
MIT
FAQs
Framework-agnostic core renderer for Servly components with prefetching and loading states
We found that @servlyadmin/runtime-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.