diff --git a/apps/studio/src/App.tsx b/apps/studio/src/App.tsx index f134038ba3..89ebe94321 100644 --- a/apps/studio/src/App.tsx +++ b/apps/studio/src/App.tsx @@ -62,6 +62,7 @@ export default function App() { const handleSelectPackage = useCallback((pkg: InstalledPackage) => { setSelectedPackage(pkg); setSelectedObject(null); + setSelectedMeta(null); setSelectedView('overview'); }, []); @@ -126,9 +127,17 @@ export default function App() { />
{selectedView === 'object' && selectedObject ? ( - + ) : selectedView === 'metadata' && selectedMeta ? ( - + ) : selectedView === 'packages' ? ( ) : selectedView === 'api-console' ? ( diff --git a/apps/studio/src/components/MetadataInspector.tsx b/apps/studio/src/components/MetadataInspector.tsx index 3944554ecb..dbfea2065e 100644 --- a/apps/studio/src/components/MetadataInspector.tsx +++ b/apps/studio/src/components/MetadataInspector.tsx @@ -18,6 +18,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp interface MetadataInspectorProps { metaType: string; metaName: string; + packageId?: string; } const TYPE_ICONS: Record = { @@ -173,7 +174,7 @@ function JsonTree({ data, depth = 0 }: { data: any; depth?: number }) { } -export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps) { +export function MetadataInspector({ metaType, metaName, packageId }: MetadataInspectorProps) { const client = useClient(); const [item, setItem] = useState(null); const [loading, setLoading] = useState(true); @@ -189,7 +190,7 @@ export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps async function load() { try { - const result: any = await client.meta.getItem(metaType, metaName); + const result: any = await client.meta.getItem(metaType, metaName, packageId ? { packageId } : undefined); if (mounted) { setItem(result?.item || result); } @@ -201,7 +202,7 @@ export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps } load(); return () => { mounted = false; }; - }, [client, metaType, metaName]); + }, [client, metaType, metaName, packageId]); if (loading) { return ( diff --git a/apps/studio/src/plugins/built-in/default-plugin.tsx b/apps/studio/src/plugins/built-in/default-plugin.tsx index bc6a5e7972..5eac0d76bd 100644 --- a/apps/studio/src/plugins/built-in/default-plugin.tsx +++ b/apps/studio/src/plugins/built-in/default-plugin.tsx @@ -15,8 +15,8 @@ import type { StudioPlugin, MetadataViewerProps } from '../types'; // ─── Viewer Component (adapts MetadataInspector to plugin interface) ─ -function DefaultViewerComponent({ metadataType, metadataName }: MetadataViewerProps) { - return ; +function DefaultViewerComponent({ metadataType, metadataName, packageId }: MetadataViewerProps) { + return ; } // ─── Plugin Definition ─────────────────────────────────────────────── diff --git a/apps/studio/src/plugins/plugin-host.tsx b/apps/studio/src/plugins/plugin-host.tsx index 2378249693..958c1198e9 100644 --- a/apps/studio/src/plugins/plugin-host.tsx +++ b/apps/studio/src/plugins/plugin-host.tsx @@ -48,11 +48,13 @@ interface PluginHostProps { metadataName: string; /** Pre-loaded metadata data (optional) */ data?: any; + /** Package ID to filter metadata by (optional) */ + packageId?: string; } // ─── Component ─────────────────────────────────────────────────────── -export function PluginHost({ metadataType, metadataName, data }: PluginHostProps) { +export function PluginHost({ metadataType, metadataName, data, packageId }: PluginHostProps) { const [activeMode, setActiveMode] = useState('preview'); // Get available modes and viewers for this metadata type @@ -176,6 +178,7 @@ export function PluginHost({ metadataType, metadataName, data }: PluginHostProps metadataName={metadataName} data={data} mode={effectiveMode} + packageId={packageId} />
diff --git a/apps/studio/src/plugins/types.ts b/apps/studio/src/plugins/types.ts index 11f9afcbd1..9c68d36ff6 100644 --- a/apps/studio/src/plugins/types.ts +++ b/apps/studio/src/plugins/types.ts @@ -26,6 +26,8 @@ export interface MetadataViewerProps { data?: any; /** Current view mode */ mode: ViewMode; + /** Package ID to filter metadata by (optional) */ + packageId?: string; } /** Context passed to action handlers */ diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index c4694ad651..93fb191f4d 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -359,10 +359,15 @@ export class ObjectStackClient { * Get a specific metadata item by type and name * @param type - Metadata type (e.g., 'object', 'plugin') * @param name - Item name (snake_case identifier) + * @param options - Optional filters (e.g., packageId to scope by package) */ - getItem: async (type: string, name: string) => { + getItem: async (type: string, name: string, options?: { packageId?: string }) => { const route = this.getRoute('metadata'); - const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`); + const params = new URLSearchParams(); + if (options?.packageId) params.set('package', options.packageId); + const qs = params.toString(); + const url = `${this.baseUrl}${route}/${type}/${name}${qs ? `?${qs}` : ''}`; + const res = await this.fetch(url); return this.unwrapResponse(res); }, diff --git a/packages/runtime/src/http-dispatcher.ts b/packages/runtime/src/http-dispatcher.ts index f881648790..814389603b 100644 --- a/packages/runtime/src/http-dispatcher.ts +++ b/packages/runtime/src/http-dispatcher.ts @@ -384,12 +384,14 @@ export class HttpDispatcher { // /metadata/:type/:name if (parts.length === 2) { const [type, name] = parts; + // Extract optional package filter from query string + const packageId = query?.package || undefined; // PUT /metadata/:type/:name (Save) if (method === 'PUT' && body) { // Try to get the protocol service directly const protocol = await this.resolveService('protocol'); - + if (protocol && typeof protocol.saveMetaItem === 'function') { try { const result = await protocol.saveMetaItem({ type, name, item: body }); @@ -398,7 +400,7 @@ export class HttpDispatcher { return { handled: true, response: this.error(e.message, 400) }; } } - + // Fallback to broker if protocol not available (legacy) if (broker) { try { @@ -430,12 +432,12 @@ export class HttpDispatcher { // If type is singular (e.g. 'app'), use it directly // If plural (e.g. 'apps'), slice it const singularType = type.endsWith('s') ? type.slice(0, -1) : type; - + // Try Protocol Service First (Preferred) const protocol = await this.resolveService('protocol'); if (protocol && typeof protocol.getMetaItem === 'function') { try { - const data = await protocol.getMetaItem({ type: singularType, name }); + const data = await protocol.getMetaItem({ type: singularType, name, packageId }); return { handled: true, response: this.success(data) }; } catch (e: any) { // Protocol might throw if not found or not supported