feat(globalStateStore): implement global state management with persistence

- refactor state structure to include app, layout, navigation, owner, program, session, and user
- add slices for managing program, session, owner, user, layout, navigation, and app states
- create context provider for global state with automatic fetching and throttling
- implement persistence using IndexedDB with localStorage fallback
- add comprehensive README documentation for usage and API
This commit is contained in:
2026-02-07 20:03:27 +02:00
parent 202a826642
commit f737b1d11d
22 changed files with 3098 additions and 488 deletions

953
mcp/server.js Executable file
View File

@@ -0,0 +1,953 @@
#!/usr/bin/env node
/**
* Oranguru MCP Server
* Provides documentation and code generation for Oranguru components
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Component documentation data
const COMPONENTS = {
Boxer: {
component: 'Boxer',
description: 'Advanced combobox/select with virtualization and server-side data support',
examples: {
basic: `import { Boxer } from '@warkypublic/oranguru';
import { useState } from 'react';
const sampleData = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' }
];
function MyComponent() {
const [value, setValue] = useState(null);
return (
<Boxer
data={sampleData}
dataSource="local"
label="Favorite Fruit"
value={value}
onChange={setValue}
searchable
clearable
placeholder="Select a fruit"
/>
);
}`,
multiSelect: `import { Boxer } from '@warkypublic/oranguru';
import { useState } from 'react';
function MultiSelectExample() {
const [values, setValues] = useState([]);
return (
<Boxer
data={sampleData}
dataSource="local"
label="Favorite Fruits"
multiSelect
value={values}
onChange={setValues}
searchable
clearable
/>
);
}`,
server: `import { Boxer } from '@warkypublic/oranguru';
function ServerSideExample() {
const [value, setValue] = useState(null);
const handleAPICall = async ({ page, pageSize, search }) => {
const response = await fetch(\`/api/items?page=\${page}&size=\${pageSize}&search=\${search}\`);
const result = await response.json();
return {
data: result.items,
total: result.total
};
};
return (
<Boxer
dataSource="server"
label="Server-side Data"
onAPICall={handleAPICall}
value={value}
onChange={setValue}
pageSize={10}
searchable
/>
);
}`
},
exports: ['Boxer', 'BoxerProvider', 'useBoxerStore'],
hook: 'useBoxerStore()',
name: 'Boxer',
provider: 'BoxerProvider'
},
ErrorBoundary: {
components: ['ReactErrorBoundary', 'ReactBasicErrorBoundary'],
description: 'React error boundary components for graceful error handling',
examples: {
basic: `import { ReactBasicErrorBoundary } from '@warkypublic/oranguru';
function App() {
return (
<ReactBasicErrorBoundary>
<MyComponent />
</ReactBasicErrorBoundary>
);
}`,
full: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
function App() {
const handleReportError = () => {
console.log('Report error to support');
};
const handleReset = () => {
console.log('Reset application state');
window.location.reload();
};
const handleRetry = () => {
console.log('Retry failed operation');
};
return (
<ReactErrorBoundary
namespace="main-app"
reportAPI="/api/errors/report"
onReportClick={handleReportError}
onResetClick={handleReset}
onRetryClick={handleRetry}
>
<MyApp />
</ReactErrorBoundary>
);
}`,
nested: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
// Multiple error boundaries for granular error handling
function App() {
return (
<ReactErrorBoundary namespace="app">
<Header />
<ReactErrorBoundary namespace="sidebar">
<Sidebar />
</ReactErrorBoundary>
<ReactErrorBoundary namespace="main-content">
<MainContent />
</ReactErrorBoundary>
</ReactErrorBoundary>
);
}`,
globalConfig: `import { SetErrorBoundaryOptions } from '@warkypublic/oranguru';
// Configure error boundary globally
SetErrorBoundaryOptions({
disabled: false, // Set to true to pass through errors (dev mode)
onError: (error, errorInfo) => {
console.error('Global error handler:', error);
// Send to analytics service
analytics.trackError(error, errorInfo);
}
});`
},
exports: ['ReactErrorBoundary', 'ReactBasicErrorBoundary', 'SetErrorBoundaryOptions', 'GetErrorBoundaryOptions'],
name: 'ErrorBoundary'
},
Former: {
component: 'Former',
description: 'Form component with React Hook Form integration and validation',
examples: {
basic: `import { Former } from '@warkypublic/oranguru';
import { useRef } from 'react';
import { Controller } from 'react-hook-form';
function BasicForm() {
const formRef = useRef(null);
return (
<Former
ref={formRef}
primeData={{ name: '', email: '' }}
onSave={async (data) => {
console.log('Saving:', data);
await fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data)
});
}}
>
<Controller
name="name"
render={({ field }) => (
<input {...field} placeholder="Name" />
)}
rules={{ required: 'Name is required' }}
/>
<Controller
name="email"
render={({ field }) => (
<input {...field} type="email" placeholder="Email" />
)}
rules={{ required: 'Email is required' }}
/>
<button type="submit">Save</button>
</Former>
);
}`,
withWrapper: `import { Former, FormerModel } from '@warkypublic/oranguru';
import { useState } from 'react';
function ModalForm() {
const [opened, setOpened] = useState(false);
return (
<>
<button onClick={() => setOpened(true)}>Open Form</button>
<FormerModel
former={{ request: 'insert' }}
opened={opened}
onClose={() => setOpened(false)}
>
<Controller
name="title"
render={({ field }) => <input {...field} />}
/>
</FormerModel>
</>
);
}`,
withAPI: `import { Former, FormerRestHeadSpecAPI } from '@warkypublic/oranguru';
function APIForm() {
return (
<Former
request="update"
uniqueKeyField="id"
primeData={{ id: 123 }}
onAPICall={FormerRestHeadSpecAPI({
url: 'https://api.example.com/items',
authToken: 'your-token'
})}
>
{/* Form fields */}
</Former>
);
}`,
customLayout: `import { Former } from '@warkypublic/oranguru';
function CustomLayoutForm() {
return (
<Former
layout={{
title: 'Edit User Profile',
buttonArea: 'bottom',
buttonAreaGroupProps: { justify: 'space-between' }
}}
primeData={{ username: '', bio: '' }}
>
{/* Form fields */}
</Former>
);
}`,
refMethods: `import { Former } from '@warkypublic/oranguru';
import { useRef } from 'react';
function FormWithRef() {
const formRef = useRef(null);
const handleValidate = async () => {
const isValid = await formRef.current?.validate();
console.log('Form valid:', isValid);
};
const handleSave = async () => {
const result = await formRef.current?.save();
console.log('Save result:', result);
};
const handleReset = () => {
formRef.current?.reset();
};
return (
<div>
<Former ref={formRef} primeData={{}}>
{/* Form fields */}
</Former>
<button onClick={handleValidate}>Validate</button>
<button onClick={handleSave}>Save</button>
<button onClick={handleReset}>Reset</button>
</div>
);
}`
},
exports: ['Former', 'FormerDialog', 'FormerModel', 'FormerPopover', 'FormerRestHeadSpecAPI'],
name: 'Former',
wrappers: ['FormerDialog', 'FormerModel', 'FormerPopover']
},
FormerControllers: {
controls: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
description: 'Pre-built form input controls for use with Former',
examples: {
basic: `import { TextInputCtrl, PasswordInputCtrl, NativeSelectCtrl, ButtonCtrl } from '@warkypublic/oranguru';
<Former>
<TextInputCtrl name="username" label="Username" />
<PasswordInputCtrl name="password" label="Password" />
<NativeSelectCtrl name="role" data={['Admin', 'User']} />
<ButtonCtrl type="submit">Save</ButtonCtrl>
</Former>`
},
exports: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
name: 'FormerControllers'
},
GlobalStateStore: {
description: 'Zustand-based global state management with automatic persistence',
examples: {
basic: `import { useGlobalStateStore } from '@warkypublic/oranguru';
function MyComponent() {
const state = useGlobalStateStore();
return (
<div>
<h1>{state.program.name}</h1>
<p>User: {state.user.username}</p>
<p>Email: {state.user.email}</p>
<p>Connected: {state.session.connected ? 'Yes' : 'No'}</p>
</div>
);
}`,
provider: `import { GlobalStateStoreProvider } from '@warkypublic/oranguru';
function App() {
return (
<GlobalStateStoreProvider
apiURL="https://api.example.com"
fetchOnMount={true}
throttleMs={5000}
>
<MyApp />
</GlobalStateStoreProvider>
);
}`,
stateUpdates: `import { useGlobalStateStore } from '@warkypublic/oranguru';
function StateControls() {
const state = useGlobalStateStore();
const handleUpdateProgram = () => {
state.setProgram({
name: 'My App',
slug: 'my-app',
description: 'A great application'
});
};
const handleUpdateUser = () => {
state.setUser({
username: 'john_doe',
email: 'john@example.com',
fullNames: 'John Doe'
});
};
const handleToggleTheme = () => {
state.setUser({
theme: {
...state.user.theme,
darkMode: !state.user.theme?.darkMode
}
});
};
return (
<div>
<button onClick={handleUpdateProgram}>Update Program</button>
<button onClick={handleUpdateUser}>Update User</button>
<button onClick={handleToggleTheme}>Toggle Dark Mode</button>
</div>
);
}`,
layout: `import { useGlobalStateStore } from '@warkypublic/oranguru';
function LayoutControls() {
const state = useGlobalStateStore();
return (
<div>
<button onClick={() => state.setLeftBar({ open: !state.layout.leftBar.open })}>
Toggle Left Bar
</button>
<button onClick={() => state.setRightBar({ open: !state.layout.rightBar.open })}>
Toggle Right Bar
</button>
<button onClick={() => state.setLeftBar({ pinned: true, size: 250 })}>
Pin & Resize Left Bar
</button>
</div>
);
}`,
outsideReact: `import { GlobalStateStore } from '@warkypublic/oranguru';
// Access state outside React components
const currentState = GlobalStateStore.getState();
console.log('API URL:', currentState.session.apiURL);
// Update state outside React
GlobalStateStore.getState().setAuthToken('new-token');
GlobalStateStore.getState().setApiURL('https://new-api.com');
// Subscribe to changes
const unsubscribe = GlobalStateStore.subscribe(
(state) => state.session.connected,
(connected) => console.log('Connected:', connected)
);`
},
exports: ['GlobalStateStore', 'GlobalStateStoreProvider', 'useGlobalStateStore', 'useGlobalStateStoreContext'],
hook: 'useGlobalStateStore()',
name: 'GlobalStateStore',
provider: 'GlobalStateStoreProvider',
store: 'GlobalStateStore'
},
Gridler: {
adaptors: ['LocalDataAdaptor', 'APIAdaptorForGoLangv2', 'FormAdaptor'],
component: 'Gridler',
description: 'Powerful data grid component with sorting, filtering, and pagination',
examples: {
basic: `import { Gridler } from '@warkypublic/oranguru';
const columns = [
{ id: 'name', title: 'Name', width: 200 },
{ id: 'email', title: 'Email', width: 250 },
{ id: 'role', title: 'Role', width: 150 }
];
const data = [
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
];
function GridExample() {
return (
<Gridler columns={columns} uniqueid="my-grid">
<Gridler.LocalDataAdaptor data={data} />
</Gridler>
);
}`,
customCell: `// Custom cell rendering with icons
const columns = [
{
id: 'status',
title: 'Status',
width: 100,
Cell: (row) => {
const icon = row?.active ? '✅' : '❌';
return {
data: \`\${icon} \${row?.status}\`,
displayData: \`\${icon} \${row?.status}\`
};
}
},
{ id: 'name', title: 'Name', width: 200 }
];`,
api: `import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
import { useRef } from 'react';
function APIGridExample() {
const gridRef = useRef(null);
const columns = [
{ id: 'id', title: 'ID', width: 100 },
{ id: 'name', title: 'Name', width: 200 },
{ id: 'status', title: 'Status', width: 150 }
];
return (
<Gridler
columns={columns}
uniqueid="api-grid"
ref={gridRef}
selectMode="row"
searchStr={searchTerm}
onChange={(values) => console.log('Selected:', values)}
>
<GlidlerAPIAdaptorForGoLangv2
url="https://api.example.com/data"
authtoken="your-api-key"
/>
</Gridler>
);
}`,
withForm: `// Gridler with Former integration for inline editing
import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
import { FormerDialog } from '@warkypublic/oranguru';
import { TextInputCtrl, NativeSelectCtrl } from '@warkypublic/oranguru';
import { useState, useRef } from 'react';
function EditableGrid() {
const gridRef = useRef(null);
const [formProps, setFormProps] = useState({
opened: false,
request: null,
values: null,
onClose: () => setFormProps(prev => ({
...prev,
opened: false,
request: null,
values: null
})),
onChange: (request, data) => {
gridRef.current?.refresh({ value: data });
}
});
const columns = [
{ id: 'id', title: 'ID', width: 100 },
{ id: 'name', title: 'Name', width: 200 },
{ id: 'type', title: 'Type', width: 150 }
];
return (
<>
<Gridler
ref={gridRef}
columns={columns}
uniqueid="editable-grid"
selectMode="row"
>
<GlidlerAPIAdaptorForGoLangv2
url="https://api.example.com/items"
authtoken="your-token"
/>
<Gridler.FormAdaptor
changeOnActiveClick={true}
descriptionField="name"
onRequestForm={(request, data) => {
setFormProps(prev => ({
...prev,
opened: true,
request: request,
values: data
}));
}}
/>
</Gridler>
<FormerDialog
former={{
request: formProps.request ?? "insert",
values: formProps.values
}}
opened={formProps.opened}
onClose={formProps.onClose}
title="Edit Item"
>
<TextInputCtrl label="Name" name="name" />
<TextInputCtrl label="Description" name="description" />
<NativeSelectCtrl
label="Type"
name="type"
data={["Type A", "Type B", "Type C"]}
/>
</FormerDialog>
</>
);
}`,
refMethods: `// Using Gridler ref methods for programmatic control
import { Gridler } from '@warkypublic/oranguru';
import { useRef } from 'react';
function GridWithControls() {
const gridRef = useRef(null);
return (
<>
<Gridler ref={gridRef} columns={columns} uniqueid="controlled-grid">
<Gridler.LocalDataAdaptor data={data} />
</Gridler>
<div>
<button onClick={() => gridRef.current?.refresh()}>
Refresh Grid
</button>
<button onClick={() => gridRef.current?.selectRow(123)}>
Select Row 123
</button>
<button onClick={() => gridRef.current?.scrollToRow(456)}>
Scroll to Row 456
</button>
<button onClick={() => gridRef.current?.reloadRow(789)}>
Reload Row 789
</button>
</div>
</>
);
}`,
sections: `// Gridler with custom side sections
import { Gridler } from '@warkypublic/oranguru';
import { useState } from 'react';
function GridWithSections() {
const [sections, setSections] = useState({
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>Top Bar</div>,
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>Bottom Bar</div>,
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>
});
return (
<Gridler
columns={columns}
uniqueid="sections-grid"
sections={{ ...sections, rightElementDisabled: false }}
>
<Gridler.LocalDataAdaptor data={data} />
</Gridler>
);
}`
},
exports: ['Gridler', 'GlidlerLocalDataAdaptor', 'GlidlerAPIAdaptorForGoLangv2', 'GlidlerFormAdaptor'],
hook: 'useGridlerStore()',
name: 'Gridler'
},
MantineBetterMenu: {
description: 'Enhanced context menus with better positioning and visibility control',
examples: {
provider: `import { MantineBetterMenusProvider } from '@warkypublic/oranguru';
import { MantineProvider } from '@mantine/core';
function App() {
return (
<MantineProvider>
<MantineBetterMenusProvider providerID="main">
<YourApp />
</MantineBetterMenusProvider>
</MantineProvider>
);
}`,
contextMenu: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
function MyComponent() {
const { show, hide } = useMantineBetterMenus();
const handleContextMenu = (e) => {
e.preventDefault();
show('context-menu', {
x: e.clientX,
y: e.clientY,
items: [
{
label: 'Edit',
onClick: () => console.log('Edit clicked')
},
{
label: 'Copy',
onClick: () => console.log('Copy clicked')
},
{
isDivider: true
},
{
label: 'Delete',
onClick: () => console.log('Delete clicked'),
color: 'red'
}
]
});
};
return (
<div onContextMenu={handleContextMenu}>
Right-click me for a context menu
</div>
);
}`,
asyncActions: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
function AsyncMenuExample() {
const { show } = useMantineBetterMenus();
const handleClick = (e) => {
show('async-menu', {
x: e.clientX,
y: e.clientY,
items: [
{
label: 'Save',
onClickAsync: async () => {
await fetch('/api/save', { method: 'POST' });
console.log('Saved successfully');
}
},
{
label: 'Load Data',
onClickAsync: async () => {
const data = await fetch('/api/data').then(r => r.json());
console.log('Data loaded:', data);
}
}
]
});
};
return <button onClick={handleClick}>Show Menu</button>;
}`,
customRenderer: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
function CustomMenuExample() {
const { show } = useMantineBetterMenus();
const handleClick = (e) => {
show('custom-menu', {
x: e.clientX,
y: e.clientY,
items: [
{
renderer: ({ loading }) => (
<div style={{ padding: '8px 12px', color: 'blue' }}>
{loading ? 'Processing...' : 'Custom Item'}
</div>
)
}
]
});
};
return <button onClick={handleClick}>Custom Menu</button>;
}`
},
exports: ['MantineBetterMenusProvider', 'useMantineBetterMenus'],
hook: 'useMantineBetterMenus()',
name: 'MantineBetterMenu',
provider: 'MantineBetterMenusProvider'
}
};
class OranguruMCPServer {
constructor() {
this.server = new Server(
{
name: 'oranguru-docs',
version: '0.0.31',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Oranguru MCP server running on stdio');
}
setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
description: 'Get documentation for a specific Oranguru component',
inputSchema: {
properties: {
component: {
description: 'The component name',
enum: Object.keys(COMPONENTS),
type: 'string',
},
},
required: ['component'],
type: 'object',
},
name: 'get_component_docs',
},
{
description: 'Generate code example for a specific Oranguru component',
inputSchema: {
properties: {
component: {
description: 'The component name',
enum: Object.keys(COMPONENTS),
type: 'string',
},
variant: {
description: "Example variant (e.g., 'basic', 'local', 'server')",
type: 'string',
},
},
required: ['component'],
type: 'object',
},
name: 'get_component_example',
},
{
description: 'List all available Oranguru components',
inputSchema: {
properties: {},
type: 'object',
},
name: 'list_components',
},
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { arguments: args, name } = request.params;
switch (name) {
case 'get_component_docs':
if (!args.component || !COMPONENTS[args.component]) {
throw new Error(`Component ${args.component} not found`);
}
return {
content: [
{
text: JSON.stringify(COMPONENTS[args.component], null, 2),
type: 'text',
},
],
};
case 'get_component_example':
if (!args.component || !COMPONENTS[args.component]) {
throw new Error(`Component ${args.component} not found`);
}
const component = COMPONENTS[args.component];
const variant = args.variant || Object.keys(component.examples)[0];
const example = component.examples[variant];
if (!example) {
throw new Error(`Variant ${variant} not found for ${args.component}`);
}
return {
content: [
{
text: example,
type: 'text',
},
],
};
case 'list_components':
return {
content: [
{
text: JSON.stringify(
Object.entries(COMPONENTS).map(([key, comp]) => ({
description: comp.description,
exports: comp.exports,
name: key,
})),
null,
2
),
type: 'text',
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// List resources
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
description: 'Main documentation for the Oranguru library',
mimeType: 'text/markdown',
name: 'Oranguru Documentation',
uri: 'oranguru://docs/readme',
},
{
description: 'List of all available components',
mimeType: 'application/json',
name: 'Component List',
uri: 'oranguru://docs/components',
},
],
}));
// Read resources
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'oranguru://docs/readme') {
const readmePath = join(__dirname, '..', 'README.md');
const readme = readFileSync(readmePath, 'utf-8');
return {
contents: [
{
mimeType: 'text/markdown',
text: readme,
uri,
},
],
};
}
if (uri === 'oranguru://docs/components') {
return {
contents: [
{
mimeType: 'application/json',
text: JSON.stringify(
Object.entries(COMPONENTS).map(([key, comp]) => ({
description: comp.description,
exports: comp.exports,
name: key,
})),
null,
2
),
uri,
},
],
};
}
throw new Error(`Resource not found: ${uri}`);
});
}
}
const server = new OranguruMCPServer();
server.run().catch(console.error);