Skip to Content
DevPluginPlugin API

플러그인 API

본 문서에서는 플러그인의 API를 정의하고, 사용하는 방법을 설명합니다. 플러그인의 API는 플러그인이 외부에 제공할 기능을 공개하는 영역입니다.

API 정의하기 (plugin.ts)

플러그인 API는 definePlugin<Api, State>(...)Api 타입과 api 객체로 정의합니다.

import { AppContext, definePlugin } from "@muvel/core" import { pluginId } from "./constants" export interface ExampleApi { open: (ctx: AppContext, id: string) => void close: (ctx: AppContext) => void } type ExampleState = { openedId: string | null } const plugin = definePlugin<ExampleApi, ExampleState>(pluginId, { name: "example", version: __VERSION__, dependencies: [], api: { open: (ctx, id) => { const state = ctx.get(plugin.id) state.openedId = id }, close: (ctx) => { const state = ctx.get(plugin.id) state.openedId = null }, }, setup: async () => ({ openedId: null }), dispose: async () => {}, })
  • API 메서드 시그니처는 interface로 먼저 선언해두면 재사용/추론이 쉽습니다.
  • 상태를 함께 쓸 경우 ctx.get(plugin.id)setup 반환 state에 접근합니다.
  • API가 다른 플러그인을 호출해야 하면, 해당 플러그인 ID를 dependencies에 선언하세요.

다른 플러그인 API 호출하기

다른 플러그인 API는 보통 아래 두 방식으로 호출합니다.

1) setup에서 바인딩해서 사용 (mgr.use)

setup: async (mgr, ctx) => { const modalApi = mgr.use(ctx, modalPluginId) const disposer = modalApi.register(novelSearchModal) return { disposer } }

novel-search 플러그인처럼 초기화 시점 등록 작업에 주로 사용합니다.

2) API 함수 내부에서 즉시 사용 (ctx.api)

api: { open: (ctx, novel, options = {}) => { const modalApi = ctx.api(modalPluginId) modalApi.open(novelSearchModal.id, { novel, ...options }) }, }

런타임 호출 시점에 다른 플러그인 기능을 위임할 때 유용합니다.

React에서 API 사용하기

클라이언트 React 컴포넌트에서는 usePluginApi(pluginId)를 사용합니다.

import { episodePluginId } from "@muvel-plugins/episode" import { usePluginApi } from "@muvel/core" const MyComponent = () => { const episodeApi = usePluginApi(episodePluginId) const episodeAtom = episodeApi.getEpisodeAtom() return null }
  • EpisodeTitleInput.tsx도 동일하게 usePluginApi(episodePluginId) 패턴을 사용합니다.
  • 플러그인별 API가 다르므로, 반드시 해당 플러그인 plugin.tsapi 정의를 먼저 확인하세요.
  • 환경에 따라 플러그인이 없을 수 있으면 useOptionalPluginApi 사용을 고려하세요.

API 타이핑 (Api / State / BoundApi)

definePlugin<Api, State, BoundApi> 형태로 제네릭을 지정할 수 있습니다.

  • Api: 플러그인 원본 API 타입 (ctx를 첫 인자로 받는 형태)
  • State: setup이 반환하는 상태 타입
  • BoundApi: 외부에서 사용할 API 타입. Api에서 첫 번째 인자인 ctx를 제거한 버전

BoundApi는 언제 쓰나?

  • 기본적으로는 직접 넣지 않아도 충분히 추론됩니다.
  • 다만 제네릭 메서드가 많거나, 함수 오버로드/복잡한 타입 조합이 있는 경우 추론이 불안정할 수 있습니다.
  • 이때 BoundApi를 명시적으로 선언하면 mgr.use(...), ctx.api(...), usePluginApi(...)에서 타입이 더 정확하게 잡힙니다.

예시: Api와 BoundApi를 분리해 명시

import { AppContext, definePlugin } from "@muvel/core" interface ModalApi { open: <T>(ctx: AppContext, id: ModalId<T>, props: T) => void close: (ctx: AppContext, id: ModalId) => void } interface BoundModalApi { open: <T>(id: ModalId<T>, props: T) => void close: (id: ModalId) => void } const plugin = definePlugin< ModalApi, { store: StoreApi<ModalStore>; disposer: () => void }, BoundModalApi >(pluginId, { // ... })

핵심은 아래처럼 대응됩니다.

  • Api.open(ctx, id, props) -> BoundApi.open(id, props)
  • Api.close(ctx, id) -> BoundApi.close(id)

타이핑 실무 팁

  • API 시그니처는 interface로 먼저 분리하고 definePlugin에 제네릭으로 연결하세요.
  • BoundApi를 직접 선언할 때는 Api와 파라미터 순서를 정확히 맞추고, ctx만 제거하세요.
  • 제네릭 API는 open: <T>(...)처럼 선언해 호출부 타입 안정성을 유지하세요.
  • 가능한 한 as 단언보다 타입 파라미터/인터페이스 설계로 해결하는 것을 권장합니다.

체크리스트

  • api에 공개할 기능이 정말 외부 사용 대상인지 확인
  • 다른 플러그인 API 호출 시 dependencies 누락 여부 확인
  • setup 등록 로직과 dispose 정리 로직 짝 맞추기
  • React 사용부에서 usePluginApi/useOptionalPluginApi 선택이 맞는지 확인
Last updated on