Skip to content

Commit 880995e

Browse files
committed
feat: add Studio Plugin system with comprehensive type definitions and JSON schemas
- Introduced `types.ts` for defining React-specific types for the plugin system, including MetadataViewerProps, ActionContext, and various resolved types. - Created JSON schemas for plugin contributions, including ActionContribution, CommandContribution, MetadataViewerContribution, and others to standardize plugin definitions. - Implemented schemas for activation events and view modes to enhance plugin activation control. - Developed a helper function `defineStudioPlugin` for type-safe plugin manifest creation. - Established a structured approach for plugin contributions, allowing for metadata viewers, sidebar groups, actions, commands, and panels.
1 parent c82f606 commit 880995e

34 files changed

Lines changed: 2723 additions & 7 deletions

apps/studio/src/App.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { AppSidebar } from "./components/app-sidebar"
66
import { SiteHeader } from "@/components/site-header"
77
import { SidebarProvider } from "@/components/ui/sidebar"
88
import { DeveloperOverview } from './components/DeveloperOverview';
9-
import { ObjectExplorer } from './components/ObjectExplorer';
10-
import { MetadataInspector } from './components/MetadataInspector';
119
import { PackageManager } from './components/PackageManager';
1210
import { Toaster } from "@/components/ui/toaster"
1311
import { getApiBaseUrl, config } from './lib/config';
12+
import { PluginRegistryProvider, PluginHost } from './plugins';
13+
import { builtInPlugins } from './plugins/built-in';
1414
import type { InstalledPackage } from '@objectstack/spec/kernel';
1515

1616
type ViewType = 'overview' | 'packages' | 'object' | 'metadata';
@@ -99,6 +99,7 @@ export default function App() {
9999

100100
return (
101101
<ObjectStackProvider client={client}>
102+
<PluginRegistryProvider plugins={builtInPlugins}>
102103
<ErrorBoundary>
103104
<SidebarProvider>
104105
<AppSidebar
@@ -121,11 +122,9 @@ export default function App() {
121122
/>
122123
<div className="flex flex-1 flex-col overflow-hidden">
123124
{selectedView === 'object' && selectedObject ? (
124-
<ObjectExplorer objectApiName={selectedObject} />
125+
<PluginHost metadataType="object" metadataName={selectedObject} />
125126
) : selectedView === 'metadata' && selectedMeta ? (
126-
<div className="flex-1 overflow-auto p-4">
127-
<MetadataInspector metaType={selectedMeta.type} metaName={selectedMeta.name} />
128-
</div>
127+
<PluginHost metadataType={selectedMeta.type} metadataName={selectedMeta.name} />
129128
) : selectedView === 'packages' ? (
130129
<PackageManager />
131130
) : (
@@ -140,6 +139,7 @@ export default function App() {
140139
<Toaster />
141140
</SidebarProvider>
142141
</ErrorBoundary>
142+
</PluginRegistryProvider>
143143
</ObjectStackProvider>
144144
);
145145
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Built-in Plugin: AI Protocol
3+
*
4+
* Provides sidebar groups and icons for AI metadata types
5+
* (agents, ragPipelines).
6+
*/
7+
8+
import { defineStudioPlugin } from '@objectstack/spec/studio';
9+
import type { StudioPlugin } from '../types';
10+
import { Bot, BookOpen } from 'lucide-react';
11+
12+
export const aiProtocolPlugin: StudioPlugin = {
13+
manifest: defineStudioPlugin({
14+
id: 'objectstack.ai-protocol',
15+
name: 'AI Protocol',
16+
version: '1.0.0',
17+
description: 'Sidebar groups and icons for AI metadata types.',
18+
contributes: {
19+
sidebarGroups: [
20+
{
21+
key: 'ai',
22+
label: 'AI',
23+
icon: 'bot',
24+
metadataTypes: ['agents', 'ragPipelines'],
25+
order: 50,
26+
},
27+
],
28+
metadataIcons: [
29+
{ metadataType: 'agents', label: 'Agents', icon: 'bot' },
30+
{ metadataType: 'ragPipelines', label: 'RAG Pipelines', icon: 'book-open' },
31+
],
32+
},
33+
}),
34+
35+
activate(api) {
36+
api.registerMetadataIcon('agents', Bot, 'Agents');
37+
api.registerMetadataIcon('ragPipelines', BookOpen, 'RAG Pipelines');
38+
},
39+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Built-in Plugin: API Protocol
3+
*
4+
* Provides sidebar groups and icons for API metadata types
5+
* (apis, connectors).
6+
*/
7+
8+
import { defineStudioPlugin } from '@objectstack/spec/studio';
9+
import type { StudioPlugin } from '../types';
10+
import { Globe, Link2 } from 'lucide-react';
11+
12+
export const apiProtocolPlugin: StudioPlugin = {
13+
manifest: defineStudioPlugin({
14+
id: 'objectstack.api-protocol',
15+
name: 'API Protocol',
16+
version: '1.0.0',
17+
description: 'Sidebar groups and icons for API metadata types.',
18+
contributes: {
19+
sidebarGroups: [
20+
{
21+
key: 'api',
22+
label: 'API',
23+
icon: 'globe',
24+
metadataTypes: ['apis', 'connectors'],
25+
order: 60,
26+
},
27+
],
28+
metadataIcons: [
29+
{ metadataType: 'apis', label: 'APIs', icon: 'globe' },
30+
{ metadataType: 'connectors', label: 'Connectors', icon: 'link-2' },
31+
],
32+
},
33+
}),
34+
35+
activate(api) {
36+
api.registerMetadataIcon('apis', Globe, 'APIs');
37+
api.registerMetadataIcon('connectors', Link2, 'Connectors');
38+
},
39+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Built-in Plugin: Automation Protocol
3+
*
4+
* Provides sidebar groups and icons for automation metadata types
5+
* (flows, workflows, approvals, webhooks).
6+
*/
7+
8+
import { defineStudioPlugin } from '@objectstack/spec/studio';
9+
import type { StudioPlugin } from '../types';
10+
import { Workflow, CheckSquare, Webhook } from 'lucide-react';
11+
12+
export const automationProtocolPlugin: StudioPlugin = {
13+
manifest: defineStudioPlugin({
14+
id: 'objectstack.automation-protocol',
15+
name: 'Automation Protocol',
16+
version: '1.0.0',
17+
description: 'Sidebar groups and icons for automation metadata types.',
18+
contributes: {
19+
sidebarGroups: [
20+
{
21+
key: 'automation',
22+
label: 'Automation',
23+
icon: 'workflow',
24+
metadataTypes: ['flows', 'workflows', 'approvals', 'webhooks'],
25+
order: 30,
26+
},
27+
],
28+
metadataIcons: [
29+
{ metadataType: 'flows', label: 'Flows', icon: 'workflow' },
30+
{ metadataType: 'workflows', label: 'Workflows', icon: 'workflow' },
31+
{ metadataType: 'approvals', label: 'Approvals', icon: 'check-square' },
32+
{ metadataType: 'webhooks', label: 'Webhooks', icon: 'webhook' },
33+
],
34+
},
35+
}),
36+
37+
activate(api) {
38+
api.registerMetadataIcon('flows', Workflow, 'Flows');
39+
api.registerMetadataIcon('workflows', Workflow, 'Workflows');
40+
api.registerMetadataIcon('approvals', CheckSquare, 'Approvals');
41+
api.registerMetadataIcon('webhooks', Webhook, 'Webhooks');
42+
},
43+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Built-in Plugin: Default Metadata Inspector
3+
*
4+
* Provides a JSON tree viewer as the fallback for any metadata type
5+
* that doesn't have a specialized plugin. This is the "catch-all" viewer.
6+
*
7+
* Priority is set to -1 so any type-specific plugin will take precedence.
8+
*/
9+
10+
import { defineStudioPlugin } from '@objectstack/spec/studio';
11+
import { MetadataInspector } from '@/components/MetadataInspector';
12+
import type { StudioPlugin, MetadataViewerProps } from '../types';
13+
14+
// ─── Viewer Component (adapts MetadataInspector to plugin interface) ─
15+
16+
function DefaultViewerComponent({ metadataType, metadataName }: MetadataViewerProps) {
17+
return <MetadataInspector metaType={metadataType} metaName={metadataName} />;
18+
}
19+
20+
// ─── Plugin Definition ───────────────────────────────────────────────
21+
22+
export const defaultInspectorPlugin: StudioPlugin = {
23+
manifest: defineStudioPlugin({
24+
id: 'objectstack.default-inspector',
25+
name: 'Default Metadata Inspector',
26+
version: '1.0.0',
27+
description: 'JSON tree viewer for any metadata type. Fallback when no specialized viewer is available.',
28+
contributes: {
29+
metadataViewers: [
30+
{
31+
id: 'json-inspector',
32+
metadataTypes: ['*'], // Wildcard: matches all types
33+
label: 'JSON Inspector',
34+
priority: -1, // Lowest priority — any plugin overrides this
35+
modes: ['preview', 'code'],
36+
},
37+
],
38+
},
39+
}),
40+
41+
activate(api) {
42+
api.registerViewer('json-inspector', DefaultViewerComponent);
43+
},
44+
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Example: Writing a Custom Studio Plugin
3+
*
4+
* This file demonstrates how to create an external plugin for ObjectStack Studio.
5+
* A Studio plugin follows the VS Code extension model:
6+
*
7+
* 1. **Manifest (Declarative)** — Declare what you contribute (viewers, icons, groups)
8+
* 2. **Activate (Imperative)** — Register React components and handlers at runtime
9+
*
10+
* ## Quick Start
11+
*
12+
* ```tsx
13+
* import { myCustomPlugin } from './my-plugin';
14+
* import { builtInPlugins } from './plugins/built-in';
15+
*
16+
* // Add your plugin alongside built-ins
17+
* const allPlugins = [...builtInPlugins, myCustomPlugin];
18+
*
19+
* <PluginRegistryProvider plugins={allPlugins}>
20+
* <App />
21+
* </PluginRegistryProvider>
22+
* ```
23+
*/
24+
25+
import { defineStudioPlugin } from '@objectstack/spec/studio';
26+
import type { StudioPlugin, MetadataViewerProps } from '../types';
27+
import { Workflow } from 'lucide-react';
28+
29+
// ─── Step 1: Create your viewer component ────────────────────────────
30+
31+
/**
32+
* A custom viewer component for Flow metadata.
33+
*
34+
* This receives standard props from the plugin host:
35+
* - `metadataType` — The type of metadata (e.g., "flows")
36+
* - `metadataName` — The item name (e.g., "approval_flow")
37+
* - `data` — The raw metadata payload (loaded from API)
38+
* - `mode` — Current view mode ("preview" | "design" | "code" | "data")
39+
*/
40+
function FlowDesignerComponent({ metadataType, metadataName, data, mode }: MetadataViewerProps) {
41+
return (
42+
<div className="p-6 space-y-4">
43+
<div className="flex items-center gap-2">
44+
<Workflow className="h-5 w-5 text-primary" />
45+
<h2 className="text-lg font-semibold">Flow Designer</h2>
46+
<span className="text-xs text-muted-foreground">({mode} mode)</span>
47+
</div>
48+
49+
<div className="rounded-lg border bg-card p-4">
50+
<h3 className="font-medium">{metadataName}</h3>
51+
<p className="text-sm text-muted-foreground mt-1">
52+
Type: {metadataType}
53+
</p>
54+
</div>
55+
56+
{mode === 'design' && (
57+
<div className="rounded-lg border-2 border-dashed border-primary/20 bg-primary/5 p-8 text-center">
58+
<p className="text-sm text-muted-foreground">
59+
🎨 Visual flow designer canvas would go here
60+
</p>
61+
<p className="text-xs text-muted-foreground mt-1">
62+
Drag and drop flow nodes, connect with edges, etc.
63+
</p>
64+
</div>
65+
)}
66+
67+
{mode === 'preview' && data && (
68+
<pre className="rounded-lg bg-muted p-4 text-xs overflow-auto">
69+
{JSON.stringify(data, null, 2)}
70+
</pre>
71+
)}
72+
</div>
73+
);
74+
}
75+
76+
// ─── Step 2: Define the plugin ───────────────────────────────────────
77+
78+
export const flowDesignerPlugin: StudioPlugin = {
79+
/**
80+
* The manifest declares what this plugin contributes.
81+
* This is purely declarative — no React components here.
82+
*/
83+
manifest: defineStudioPlugin({
84+
id: 'example.flow-designer',
85+
name: 'Flow Designer',
86+
version: '0.1.0',
87+
description: 'Visual flow designer for automation flows.',
88+
author: 'Example',
89+
90+
contributes: {
91+
// Register a viewer for the "flows" metadata type
92+
metadataViewers: [
93+
{
94+
id: 'flow-canvas',
95+
metadataTypes: ['flows'],
96+
label: 'Flow Designer',
97+
priority: 50, // Higher than default inspector (-1)
98+
modes: ['preview', 'design'], // Supports both preview and design modes
99+
},
100+
],
101+
102+
// Optionally add custom actions
103+
actions: [
104+
{
105+
id: 'validate-flow',
106+
label: 'Validate Flow',
107+
icon: 'check-circle',
108+
location: 'toolbar',
109+
metadataTypes: ['flows'],
110+
},
111+
],
112+
113+
// Optionally add commands
114+
commands: [
115+
{
116+
id: 'example.flow-designer.create',
117+
label: 'Create New Flow',
118+
shortcut: 'Ctrl+Shift+F',
119+
icon: 'plus',
120+
},
121+
],
122+
},
123+
}),
124+
125+
/**
126+
* The activate function registers runtime components and handlers.
127+
* It receives the `StudioPluginAPI` — similar to VS Code's `vscode` module.
128+
*/
129+
activate(api) {
130+
// Register the React component for our declared viewer
131+
api.registerViewer('flow-canvas', FlowDesignerComponent);
132+
133+
// Register action handler
134+
api.registerAction('validate-flow', async (ctx) => {
135+
console.log(`Validating flow: ${ctx.metadataName}`, ctx.data);
136+
// In a real plugin, this would validate the flow structure
137+
alert(`Flow "${ctx.metadataName}" is valid! ✅`);
138+
});
139+
140+
// Register command handler
141+
api.registerCommand('example.flow-designer.create', () => {
142+
console.log('Creating new flow...');
143+
// In a real plugin, this would open a creation dialog
144+
});
145+
146+
// Register a custom icon
147+
api.registerMetadataIcon('flows', Workflow, 'Flows');
148+
},
149+
150+
/**
151+
* Optional: cleanup when the plugin is deactivated.
152+
*/
153+
deactivate() {
154+
console.log('[FlowDesigner] Plugin deactivated');
155+
},
156+
};

0 commit comments

Comments
 (0)