Skip to main content

host-pattern

Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include "add a domain", "new domain", "host implementation", "host pattern", "createPluginAPI".

소스 정보

저장소
nukeop/nuclear
최근 소스 활동
2026년 3월 17일 21:41
감지된 SKILL.md 언어
영어
스타
18,705
포크
1,340

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
host-pattern
description
Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include "add a domain", "new domain", "host implementation", "host pattern", "createPluginAPI".
# Host pattern Every feature area follows the same three-layer structure: 1. Host type - the contract (SDK, no implementation) 2. API class - what plugins actually call (SDK, wraps the host) 3. Host implementation - bridges the API to player internals (player) Plugins call methods on an API class (e.g. `api.Queue.addToQueue()`), which holds a reference to the host and delegates to it. Plugins never touch a host directly. ## Files to create/modify ### SDK (`packages/plugin-sdk/`) | Action | File | What | |--------|------|------| | Create | `src/types/yourDomain.ts` | `YourDomainHost` interface + related types | | Create | `src/api/yourDomain.ts` | `YourDomainAPI` class | | Modify | `src/api/index.ts` | Add `yourDomainHost` option + `YourDomain` field to `NuclearAPI` | | Modify | `src/index.ts` | Export types and API class | ### Player (`packages/player/`) | Action | File | What | |--------|------|------| | Create | `src/services/yourDomainHost.ts` | Host implementation + singleton export | | Modify | `src/services/plugins/createPluginAPI.ts` | Pass singleton to `NuclearPluginAPI` | | Modify | `src/services/logger.ts` | Add domain to `LOG_SCOPES` (needed for `reportError`) | If the domain needs shared model types: create `packages/model/src/yourDomain.ts` and re-export from `packages/model/src/index.ts`. ## API class pattern Every API class follows this structure. ```typescript // packages/plugin-sdk/src/api/yourDomain.ts export class YourDomainAPI { #host?: YourDomainHost; constructor(host?: YourDomainHost) { this.#host = host; } #withHost<T>(fn: (host: YourDomainHost) => T): T { const host = this.#host; if (!host) { throw new Error('YourDomain host not available'); } return fn(host); } yourMethod(arg: SomeType) { return this.#withHost((host) => host.yourMethod(arg)); } } ``` Reference: `packages/plugin-sdk/src/api/queue.ts`, `packages/plugin-sdk/src/api/dashboard.ts` ## Connecting to NuclearAPI ```typescript // packages/plugin-sdk/src/api/index.ts import type { YourDomainHost } from '../types/yourDomain'; import { YourDomainAPI } from './yourDomain'; // Add to class: readonly YourDomain: YourDomainAPI; // Add to constructor opts type: yourDomainHost?: YourDomainHost; // Add to constructor body: this.YourDomain = new YourDomainAPI(opts?.yourDomainHost); ``` ```typescript // packages/player/src/services/plugins/createPluginAPI.ts import { yourDomainHost } from '../../services/yourDomainHost'; // Add to NuclearPluginAPI constructor call: yourDomainHost, ``` ## Host implementation Hosts bridge the SDK contract to whatever backs the domain. Most commonly a Zustand store: ```typescript // packages/player/src/services/yourDomainHost.ts import type { YourDomainHost } from '@nuclearplayer/plugin-sdk'; import { useYourDomainStore } from '../stores/yourDomainStore'; export const createYourDomainHost = (): YourDomainHost => ({ yourMethod: (arg) => useYourDomainStore.getState().doThing(arg), getState: () => useYourDomainStore.getState().value, }); export const yourDomainHost = createYourDomainHost(); ``` A host can also have access to the **provider registry** (metadata, streaming, dashboard), resolving which registered provider to call. Two patterns: **Single provider** (user picks one in Sources): ```typescript const getProvider = (providerId?: string) => providersHost.get<YourProvider>( providerId ?? providersHost.getActive('yourkind'), 'yourkind', ); export const createYourDomainHost = (): YourDomainHost => ({ async fetch(query, providerId?) { const provider = getProvider(providerId); if (!provider) throw new Error('No provider available'); return provider.fetch(query); }, }); ``` **Fan-out** (aggregate across all providers - dashboard): ```typescript export const createYourDomainHost = (): YourDomainHost => ({ async fetchAll() { const providers = providersHost.list('yourkind') as YourProvider[]; const results = await Promise.allSettled(providers.map(async (provider) => { const items = await provider.fetch(); return { providerId: provider.id, providerName: provider.name, items }; })); return results.filter(isFulfilled).map((r) => r.value); }, }); ``` Reference: `packages/player/src/services/metadataHost.ts` (single), `packages/player/src/services/dashboardHost.ts` (fan-out) ## Error handling - **Store-backed hosts**: let errors propagate naturally. - **Provider-backed, single provider** (metadata, streaming): throw errors so the user sees failures. - **Provider-backed, fan-out** (dashboard): catch per-provider so one failure doesn't block others. Log via `reportError`, return partial results. - `MissingCapabilityError`: always a plugin bug. Single-provider domains throw it; fan-out domains log it and skip the offending provider.
GitHub에서 보기