/**
* Mandu Island - 선언적 Islands Architecture
*
* @example
* ```tsx
* import { island } from '@mandujs/core';
*
* export default island('visible', ({ name }) => {
* const [count, setCount] = useState(0);
* return ;
* });
* ```
*/
import type { ComponentType, ReactNode } from 'react';
import type { z } from 'zod';
// ============================================================================
// Types
// ============================================================================
/** 하이드레이션 타이밍 */
export type IslandHydrationStrategy =
| 'load' // 페이지 로드 즉시
| 'idle' // requestIdleCallback
| 'visible' // IntersectionObserver
| 'media' // 미디어 쿼리 매치 시
| 'never'; // SSR only, 하이드레이션 안 함
/** Island 옵션 */
export interface IslandOptions
{
/** 하이드레이션 전략 */
hydrate: IslandHydrationStrategy;
/** 미디어 쿼리 (hydrate: 'media' 일 때) */
media?: string;
/** SSR 폴백 컴포넌트 */
fallback?: ReactNode;
/** Props 스키마 (Zod) - 런타임 검증 */
props?: z.ZodType
;
/** Island 이름 (자동 생성됨) */
name?: string;
}
/** Island 컴포넌트 메타데이터 */
export interface IslandMeta {
__island: true;
__hydrate: IslandHydrationStrategy;
__media?: string;
__fallback?: ReactNode;
__name: string;
__propsSchema?: z.ZodType;
}
/** Island 컴포넌트 타입 */
export type IslandComponent
= ComponentType
& IslandMeta;
// ============================================================================
// Island Registry (서버/클라이언트 공용)
// ============================================================================
// Registry bag for heterogeneous React components. React's `ComponentType
`
// is contravariant on `P` — a `ComponentType<{id:string}>` is NOT assignable
// to `ComponentType>`. The correct bag type for a
// heterogeneous component map is `ComponentType`; narrowing happens at
// call sites via each island's own typed `IslandComponent
` signature.
// Same pattern used in client/island.ts `createPartialGroup()`.
// oxlint-disable no-explicit-any -- heterogeneous React component registry
type AnyIslandComponent = IslandComponent;
const islandRegistry = new Map();
let islandCounter = 0;
export function registerIsland(name: string, component: AnyIslandComponent): void {
islandRegistry.set(name, component);
}
export function getIsland(name: string): AnyIslandComponent | undefined {
return islandRegistry.get(name);
}
export function getAllIslands(): Map {
return islandRegistry;
}
// oxlint-enable no-explicit-any
// ============================================================================
// Client Island Types (setup/render 패턴)
// ============================================================================
/**
* Client Island 정의 타입 (setup/render 패턴)
* @template TServerData - SSR에서 전달받는 서버 데이터 타입
* @template TSetupResult - setup 함수가 반환하는 결과 타입
*/
export interface ClientIslandDefinition {
setup: (serverData: TServerData) => TSetupResult;
render: (props: TSetupResult) => ReactNode;
errorBoundary?: (error: Error, reset: () => void) => ReactNode;
loading?: () => ReactNode;
}
/** Compiled Client Island */
export interface CompiledClientIsland {
definition: ClientIslandDefinition;
__mandu_island: true;
__mandu_island_id?: string;
}
/** Type guard: is this a ClientIslandDefinition? */
function isClientIslandDefinition(arg: unknown): arg is ClientIslandDefinition {
if (arg === null || typeof arg !== 'object') return false;
if (!('setup' in arg) || !('render' in arg)) return false;
const candidate = arg as { setup?: unknown; render?: unknown };
return typeof candidate.setup === 'function' && typeof candidate.render === 'function';
}
// ============================================================================
// island() - 선언적 Island 생성 + Client Island 패턴
// ============================================================================
/**
* Island 컴포넌트 생성 (두 가지 패턴 지원)
*
* @example
* // 패턴 1: 선언적 (컴포넌트 래핑)
* export default island('visible', ({ name }) =>
,
* });
*
* @example
* // 패턴 1: 옵션과 함께
* export default island({
* hydrate: 'idle',
* fallback: ,
* props: z.object({ userId: z.string() }),
* }, ({ userId }) => {
* // ...
* });
*/
// Using `unknown` generic arguments on the internal impl types keeps the
// overloads below as the single source of truth for external type inference
// while avoiding `any` in the implementation. Callers always hit one of
// the overloads and never see this input/output type.
type IslandImplInput =
| IslandHydrationStrategy
| IslandOptions
| ClientIslandDefinition;
// Overload 1: Setup/Render client island pattern
export function island(
definition: ClientIslandDefinition
): CompiledClientIsland;
// Overload 2: Declarative with strategy string
export function island