@servlyadmin/runtime-core 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +308 -0
- package/dist/{chunk-CIUQK4GA.js → chunk-EQFZFPI7.mjs} +1 -1
- package/dist/chunk-RKUT63EF.mjs +154 -0
- package/dist/index.js +2930 -117
- package/dist/index.mjs +4849 -0
- package/dist/{registry-GCCVK65D.js → registry-HKUXXQ5V.mjs} +1 -1
- package/dist/tailwind-UHWJOUFF.mjs +24 -0
- package/package.json +5 -7
- package/dist/index.cjs +0 -2818
- package/dist/index.d.cts +0 -1213
- package/dist/index.d.ts +0 -1213
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# @servlyadmin/runtime-core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic core renderer for Servly components. This package provides the foundation for rendering Servly components in any JavaScript environment.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @servlyadmin/runtime-core
|
|
9
|
+
# or
|
|
10
|
+
yarn add @servlyadmin/runtime-core
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @servlyadmin/runtime-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { render, fetchComponent } from '@servlyadmin/runtime-core';
|
|
19
|
+
|
|
20
|
+
// Fetch a component from the registry
|
|
21
|
+
const { data } = await fetchComponent('my-component', { version: 'latest' });
|
|
22
|
+
|
|
23
|
+
// Render to a container
|
|
24
|
+
const result = render({
|
|
25
|
+
container: document.getElementById('app'),
|
|
26
|
+
elements: data.layout,
|
|
27
|
+
context: {
|
|
28
|
+
props: { title: 'Hello World' },
|
|
29
|
+
state: {},
|
|
30
|
+
context: {},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Update props
|
|
35
|
+
result.update({
|
|
36
|
+
props: { title: 'Updated Title' },
|
|
37
|
+
state: {},
|
|
38
|
+
context: {},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Cleanup
|
|
42
|
+
result.destroy();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Core Concepts
|
|
46
|
+
|
|
47
|
+
### Layout Elements
|
|
48
|
+
|
|
49
|
+
Components are defined as a tree of layout elements:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface LayoutElement {
|
|
53
|
+
i: string; // Unique identifier
|
|
54
|
+
type: string; // HTML tag or component type
|
|
55
|
+
configuration?: { // Element configuration
|
|
56
|
+
className?: string;
|
|
57
|
+
style?: Record<string, any>;
|
|
58
|
+
textContent?: string;
|
|
59
|
+
// ... other attributes
|
|
60
|
+
};
|
|
61
|
+
children?: LayoutElement[]; // Nested elements
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Binding Context
|
|
66
|
+
|
|
67
|
+
Data is passed to components through a binding context:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface BindingContext {
|
|
71
|
+
props: Record<string, any>; // Component props
|
|
72
|
+
state: Record<string, any>; // Component state
|
|
73
|
+
context: Record<string, any>; // Additional context
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Template Bindings
|
|
78
|
+
|
|
79
|
+
Use `{{path}}` syntax to bind data:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const elements = [
|
|
83
|
+
{
|
|
84
|
+
i: 'greeting',
|
|
85
|
+
type: 'h1',
|
|
86
|
+
configuration: {
|
|
87
|
+
textContent: 'Hello, {{props.name}}!',
|
|
88
|
+
className: '{{props.className}}',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### render(options)
|
|
97
|
+
|
|
98
|
+
Renders elements to a container.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const result = render({
|
|
102
|
+
container: HTMLElement,
|
|
103
|
+
elements: LayoutElement[],
|
|
104
|
+
context: BindingContext,
|
|
105
|
+
eventHandlers?: Record<string, Record<string, (e: Event) => void>>,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Returns
|
|
109
|
+
interface RenderResult {
|
|
110
|
+
update(context: BindingContext): void;
|
|
111
|
+
destroy(): void;
|
|
112
|
+
getElement(id: string): HTMLElement | null;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### fetchComponent(id, options?)
|
|
117
|
+
|
|
118
|
+
Fetches a component from the registry.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const { data, fromCache } = await fetchComponent('component-id', {
|
|
122
|
+
version: 'latest', // Version specifier
|
|
123
|
+
cacheStrategy: 'memory', // 'memory' | 'localStorage' | 'none'
|
|
124
|
+
forceRefresh: false, // Bypass cache
|
|
125
|
+
timeout: 30000, // Request timeout in ms
|
|
126
|
+
retryConfig: {
|
|
127
|
+
maxRetries: 3,
|
|
128
|
+
retryDelay: 1000,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### StateManager
|
|
134
|
+
|
|
135
|
+
Manages component state with subscriptions.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { StateManager } from '@servlyadmin/runtime-core';
|
|
139
|
+
|
|
140
|
+
const stateManager = new StateManager({ count: 0 });
|
|
141
|
+
|
|
142
|
+
// Get/set state
|
|
143
|
+
stateManager.setState({ count: 1 });
|
|
144
|
+
stateManager.getValue('count'); // 1
|
|
145
|
+
stateManager.setValue('user.name', 'John');
|
|
146
|
+
|
|
147
|
+
// Subscribe to changes
|
|
148
|
+
const unsubscribe = stateManager.subscribe((state) => {
|
|
149
|
+
console.log('State changed:', state);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Subscribe to specific path
|
|
153
|
+
stateManager.subscribeToPath('count', (value) => {
|
|
154
|
+
console.log('Count changed:', value);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Batch updates
|
|
158
|
+
stateManager.batch(() => {
|
|
159
|
+
stateManager.setValue('a', 1);
|
|
160
|
+
stateManager.setValue('b', 2);
|
|
161
|
+
}); // Only one notification
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### EventSystem
|
|
165
|
+
|
|
166
|
+
Handles events with plugin-based actions.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { EventSystem } from '@servlyadmin/runtime-core';
|
|
170
|
+
|
|
171
|
+
const eventSystem = new EventSystem();
|
|
172
|
+
|
|
173
|
+
// Register custom plugin
|
|
174
|
+
eventSystem.registerPlugin('my-action', async (config, context) => {
|
|
175
|
+
console.log('Action executed with:', config);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Create event handler
|
|
179
|
+
const handler = eventSystem.createHandler([
|
|
180
|
+
{ key: 'prevent-default', config: {} },
|
|
181
|
+
{ key: 'set-state', config: { path: 'clicked', value: true } },
|
|
182
|
+
{ key: 'my-action', config: { message: 'Button clicked!' } },
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
// Use with element
|
|
186
|
+
button.addEventListener('click', (e) => {
|
|
187
|
+
handler(e, 'button-id', bindingContext);
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Built-in Plugins
|
|
192
|
+
|
|
193
|
+
- `console-log` - Log messages to console
|
|
194
|
+
- `set-state` - Update state values
|
|
195
|
+
- `delay` - Add delay between actions
|
|
196
|
+
- `prevent-default` - Call event.preventDefault()
|
|
197
|
+
- `stop-propagation` - Call event.stopPropagation()
|
|
198
|
+
|
|
199
|
+
### Cache
|
|
200
|
+
|
|
201
|
+
Component caching with multiple strategies.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { ComponentCache } from '@servlyadmin/runtime-core';
|
|
205
|
+
|
|
206
|
+
const cache = new ComponentCache({
|
|
207
|
+
maxSize: 100,
|
|
208
|
+
strategy: 'memory', // 'memory' | 'localStorage' | 'none'
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Set with TTL
|
|
212
|
+
cache.set('key', data, { ttl: 60000 }); // 1 minute
|
|
213
|
+
|
|
214
|
+
// Component-specific methods
|
|
215
|
+
cache.setComponent('comp-id', '1.0.0', componentData);
|
|
216
|
+
cache.getComponent('comp-id', 'latest');
|
|
217
|
+
cache.invalidateComponent('comp-id');
|
|
218
|
+
|
|
219
|
+
// Statistics
|
|
220
|
+
const stats = cache.getStats();
|
|
221
|
+
console.log(`Hit rate: ${stats.hitRate * 100}%`);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Bindings
|
|
225
|
+
|
|
226
|
+
Template resolution utilities.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { resolveTemplate, resolveBindings, isTemplate } from '@servlyadmin/runtime-core';
|
|
230
|
+
|
|
231
|
+
const context = {
|
|
232
|
+
props: { name: 'World', count: 42 },
|
|
233
|
+
state: {},
|
|
234
|
+
context: {},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Resolve single template
|
|
238
|
+
resolveTemplate('Hello, {{props.name}}!', context); // "Hello, World!"
|
|
239
|
+
|
|
240
|
+
// Check if string is a template
|
|
241
|
+
isTemplate('{{props.name}}'); // true
|
|
242
|
+
isTemplate('static text'); // false
|
|
243
|
+
|
|
244
|
+
// Resolve all bindings in an object
|
|
245
|
+
resolveBindings({
|
|
246
|
+
title: '{{props.name}}',
|
|
247
|
+
subtitle: 'Count: {{props.count}}',
|
|
248
|
+
}, context);
|
|
249
|
+
// { title: 'World', subtitle: 'Count: 42' }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Slots
|
|
253
|
+
|
|
254
|
+
Components can define slots for content injection:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const elements = [
|
|
258
|
+
{
|
|
259
|
+
i: 'card',
|
|
260
|
+
type: 'div',
|
|
261
|
+
configuration: { className: 'card' },
|
|
262
|
+
children: [
|
|
263
|
+
{
|
|
264
|
+
i: 'header-slot',
|
|
265
|
+
type: 'div',
|
|
266
|
+
configuration: {
|
|
267
|
+
'data-slot': 'header', // Slot placeholder
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
i: 'content-slot',
|
|
272
|
+
type: 'div',
|
|
273
|
+
configuration: {
|
|
274
|
+
'data-slot': 'default',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Framework wrappers handle slot content injection automatically.
|
|
283
|
+
|
|
284
|
+
## TypeScript Support
|
|
285
|
+
|
|
286
|
+
Full TypeScript support with exported types:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import type {
|
|
290
|
+
LayoutElement,
|
|
291
|
+
BindingContext,
|
|
292
|
+
RenderResult,
|
|
293
|
+
ComponentData,
|
|
294
|
+
CacheStrategy,
|
|
295
|
+
RetryConfig,
|
|
296
|
+
} from '@servlyadmin/runtime-core';
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Browser Support
|
|
300
|
+
|
|
301
|
+
- Chrome 80+
|
|
302
|
+
- Firefox 75+
|
|
303
|
+
- Safari 13+
|
|
304
|
+
- Edge 80+
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// packages/runtime-core/src/tailwind.ts
|
|
2
|
+
var DEFAULT_TAILWIND_CDN = "https://cdn.tailwindcss.com";
|
|
3
|
+
var tailwindInjected = false;
|
|
4
|
+
var tailwindScript = null;
|
|
5
|
+
function injectTailwind(config = {}) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
if (tailwindInjected && tailwindScript) {
|
|
8
|
+
resolve();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (typeof document === "undefined") {
|
|
12
|
+
resolve();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (window.tailwind) {
|
|
16
|
+
tailwindInjected = true;
|
|
17
|
+
config.onReady?.();
|
|
18
|
+
resolve();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const {
|
|
22
|
+
cdnUrl = DEFAULT_TAILWIND_CDN,
|
|
23
|
+
config: tailwindConfig,
|
|
24
|
+
plugins = [],
|
|
25
|
+
usePlayCdn = false,
|
|
26
|
+
onReady,
|
|
27
|
+
onError
|
|
28
|
+
} = config;
|
|
29
|
+
const script = document.createElement("script");
|
|
30
|
+
script.src = usePlayCdn ? `${cdnUrl}?plugins=forms,typography,aspect-ratio` : cdnUrl;
|
|
31
|
+
script.async = true;
|
|
32
|
+
script.onload = () => {
|
|
33
|
+
tailwindInjected = true;
|
|
34
|
+
tailwindScript = script;
|
|
35
|
+
if (tailwindConfig && window.tailwind) {
|
|
36
|
+
window.tailwind.config = tailwindConfig;
|
|
37
|
+
}
|
|
38
|
+
onReady?.();
|
|
39
|
+
resolve();
|
|
40
|
+
};
|
|
41
|
+
script.onerror = (event) => {
|
|
42
|
+
const error = new Error(`Failed to load Tailwind CSS from ${cdnUrl}`);
|
|
43
|
+
onError?.(error);
|
|
44
|
+
reject(error);
|
|
45
|
+
};
|
|
46
|
+
document.head.appendChild(script);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function removeTailwind() {
|
|
50
|
+
if (tailwindScript && tailwindScript.parentNode) {
|
|
51
|
+
tailwindScript.parentNode.removeChild(tailwindScript);
|
|
52
|
+
tailwindScript = null;
|
|
53
|
+
tailwindInjected = false;
|
|
54
|
+
delete window.tailwind;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isTailwindLoaded() {
|
|
58
|
+
return tailwindInjected || !!window.tailwind;
|
|
59
|
+
}
|
|
60
|
+
function getTailwind() {
|
|
61
|
+
return window.tailwind;
|
|
62
|
+
}
|
|
63
|
+
function updateTailwindConfig(config) {
|
|
64
|
+
if (window.tailwind) {
|
|
65
|
+
window.tailwind.config = {
|
|
66
|
+
...window.tailwind.config,
|
|
67
|
+
...config
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function addCustomStyles(css, id) {
|
|
72
|
+
if (typeof document === "undefined") {
|
|
73
|
+
throw new Error("addCustomStyles can only be used in browser environment");
|
|
74
|
+
}
|
|
75
|
+
const styleId = id || `servly-custom-styles-${Date.now()}`;
|
|
76
|
+
let existingStyle = document.getElementById(styleId);
|
|
77
|
+
if (existingStyle) {
|
|
78
|
+
existingStyle.textContent = css;
|
|
79
|
+
return existingStyle;
|
|
80
|
+
}
|
|
81
|
+
const style = document.createElement("style");
|
|
82
|
+
style.id = styleId;
|
|
83
|
+
style.textContent = css;
|
|
84
|
+
document.head.appendChild(style);
|
|
85
|
+
return style;
|
|
86
|
+
}
|
|
87
|
+
function removeCustomStyles(id) {
|
|
88
|
+
if (typeof document === "undefined") return;
|
|
89
|
+
const style = document.getElementById(id);
|
|
90
|
+
if (style && style.parentNode) {
|
|
91
|
+
style.parentNode.removeChild(style);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
var DEFAULT_SERVLY_TAILWIND_CONFIG = {
|
|
95
|
+
theme: {
|
|
96
|
+
extend: {
|
|
97
|
+
// Add any Servly-specific theme extensions here
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
// Safelist common dynamic classes
|
|
101
|
+
safelist: [
|
|
102
|
+
// Spacing
|
|
103
|
+
{ pattern: /^(p|m|gap)-/ },
|
|
104
|
+
// Sizing
|
|
105
|
+
{ pattern: /^(w|h|min-w|min-h|max-w|max-h)-/ },
|
|
106
|
+
// Flexbox
|
|
107
|
+
{ pattern: /^(flex|justify|items|self)-/ },
|
|
108
|
+
// Grid
|
|
109
|
+
{ pattern: /^(grid|col|row)-/ },
|
|
110
|
+
// Colors
|
|
111
|
+
{ pattern: /^(bg|text|border|ring)-/ },
|
|
112
|
+
// Typography
|
|
113
|
+
{ pattern: /^(font|text|leading|tracking)-/ },
|
|
114
|
+
// Borders
|
|
115
|
+
{ pattern: /^(rounded|border)-/ },
|
|
116
|
+
// Effects
|
|
117
|
+
{ pattern: /^(shadow|opacity|blur)-/ },
|
|
118
|
+
// Transforms
|
|
119
|
+
{ pattern: /^(scale|rotate|translate|skew)-/ },
|
|
120
|
+
// Transitions
|
|
121
|
+
{ pattern: /^(transition|duration|ease|delay)-/ }
|
|
122
|
+
]
|
|
123
|
+
};
|
|
124
|
+
async function initServlyTailwind(customConfig) {
|
|
125
|
+
const config = customConfig ? { ...DEFAULT_SERVLY_TAILWIND_CONFIG, ...customConfig } : DEFAULT_SERVLY_TAILWIND_CONFIG;
|
|
126
|
+
await injectTailwind({
|
|
127
|
+
config,
|
|
128
|
+
usePlayCdn: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
var tailwind_default = {
|
|
132
|
+
injectTailwind,
|
|
133
|
+
removeTailwind,
|
|
134
|
+
isTailwindLoaded,
|
|
135
|
+
getTailwind,
|
|
136
|
+
updateTailwindConfig,
|
|
137
|
+
addCustomStyles,
|
|
138
|
+
removeCustomStyles,
|
|
139
|
+
initServlyTailwind,
|
|
140
|
+
DEFAULT_SERVLY_TAILWIND_CONFIG
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
injectTailwind,
|
|
145
|
+
removeTailwind,
|
|
146
|
+
isTailwindLoaded,
|
|
147
|
+
getTailwind,
|
|
148
|
+
updateTailwindConfig,
|
|
149
|
+
addCustomStyles,
|
|
150
|
+
removeCustomStyles,
|
|
151
|
+
DEFAULT_SERVLY_TAILWIND_CONFIG,
|
|
152
|
+
initServlyTailwind,
|
|
153
|
+
tailwind_default
|
|
154
|
+
};
|