/** * Plugin Context Provider * * React context provider and hook for accessing plugin context. * Moved from @decocms/bindings to @decocms/mesh-sdk to consolidate * all plugin-facing React components in one package. */ import { createContext, useContext, type ReactNode } from "react"; import type { Binder, PluginContext, PluginContextPartial, } from "@decocms/bindings"; // Internal context stores the partial version (nullable connection fields) // The hook return type depends on the options passed const PluginContextInternal = createContext(null); export interface PluginContextProviderProps { value: PluginContext | PluginContextPartial; children: ReactNode; } /** * Provider component for plugin context. * Used by the mesh app layout to provide context to plugin routes. */ export function PluginContextProvider({ value, children, }: PluginContextProviderProps) { return ( {children} ); } /** * Options for usePluginContext hook. */ export interface UsePluginContextOptions { /** * Set to true when calling from an empty state component. * This returns nullable connection fields since no valid connection exists. */ partial?: boolean; } /** * Hook to access the plugin context with typed tool caller. * * @template TBinding - The binding type for typed tool calls * @param options - Optional settings * @param options.partial - Set to true in empty state components where connection may not exist * @throws Error if used outside of PluginContextProvider * @throws Error if connection is null but partial option is not set * * @example * ```tsx * // In route component (connection guaranteed by layout) * const { toolCaller, connection } = usePluginContext(); * const result = await toolCaller("COLLECTION_REGISTRY_APP_LIST", { limit: 20 }); * * // In empty state component (no connection available) * const { session, org } = usePluginContext({ partial: true }); * ``` */ export function usePluginContext(options: { partial: true; }): PluginContextPartial; export function usePluginContext< TBinding extends Binder = Binder, >(): PluginContext; export function usePluginContext( options?: UsePluginContextOptions, ): PluginContext | PluginContextPartial { const context = useContext(PluginContextInternal); if (!context) { throw new Error( "usePluginContext must be used within a PluginContextProvider", ); } // If partial mode, return as-is with nullable fields if (options?.partial) { return context as PluginContextPartial; } // Otherwise, assert that connection exists (routes should always have one) if (!context.connectionId || !context.connection || !context.toolCaller) { throw new Error( "usePluginContext requires a valid connection. Use { partial: true } in empty state components.", ); } return context as PluginContext; }