import type { World } from 'leviar'; import type { SceneContext, CommandResult } from '../core/SceneContext'; import type { UIRuntimeEntry } from '../core/UIRegistry'; import type { IHookallSync } from 'hookall'; /** `module.onBoot()`에 등록하는 비동기 초기화 콜백 */ export type BootCallback = (world: World) => Promise; export type SetStateFn = (partial: Partial | ((prev: Readonly) => Partial)) => void; /** hookall이 요구하는 리스너 서명 제약 */ export type ListenerSignature = { [K in keyof M]: (...args: any) => any; }; /** 훅 맵이 없는 경우의 기본 타입 */ export type DefaultHook = Record; /** * NovelModule의 내부 메타데이터. * Novel 엔진이 이 메타를 읽어 명령 실행 및 View 재생성에 활용합니다. */ export interface NovelModuleMeta = any> { readonly __isModule: true; /** 스키마 기본값 (타입 추론 전용, 런타임 미사용) */ readonly __schemaDefault: TSchema; /** defineCommand로 등록된 커맨드 핸들러 (없으면 null) */ readonly __handler: ((params: any, ctx: SceneContext) => Generator) | null; /** defineView로 등록된 View 빌더 (없으면 null) */ readonly __viewBuilder: ((ctx: SceneContext, state: TSchema) => UIRuntimeEntry) | null; /** onBoot()로 등록된 비동기 초기화 콜백 (없으면 null) */ readonly __bootFn: BootCallback | null; /** Novel 엔진이 등록 시 주입하는 모듈 key. `novel.boot()` 이전에는 null */ readonly __key: string | null; /** Novel 엔진이 등록 시 key를 주입합니다 */ __setKey(key: string): void; } /** * `define()`이 반환하는 모듈 객체 타입. * * - `defineCommand`: 커맨드 핸들러 등록 (Controller) * - `defineView`: View 빌더 등록 (View) * - `hooker`: 모듈 고유의 훅 시스템 (`IHookallSync`) * - schema: 공유 상태 (Model) */ export type NovelModule = any, THook extends ListenerSignature = DefaultHook> = NovelModuleMeta & { /** 이 모듈에 등록된 훅 시스템. `useHookallSync(module.hooker)` 형태로 사용합니다. */ readonly hooker: IHookallSync; /** * 커맨드 핸들러를 등록합니다. * - `cmd`: `{ type: 'key', ...TCmd }` 에서 type을 제외한 커맨드 속성 * - `ctx`: SceneContext * - `state`: 현재 공유 상태 (Readonly) * - `setState`: 부분적으로 상태를 병합하고 뷰를 갱신하는 함수 */ defineCommand(handler: (cmd: TCmd, ctx: SceneContext, state: Readonly, setState: SetStateFn) => Generator): NovelModule; defineView(builder: (ctx: SceneContext, state: Readonly, setState: SetStateFn) => UIRuntimeEntry): NovelModule; /** * 모듈이 Novel world에 등록될 때 딱 한 번 호출되는 비동기 초기화 콜백을 등록합니다. * `novel.boot()` 호출 시 실행됩니다. * * @example * myModule.onBoot(async (world) => { * await world.loader.load({ myAsset: '/path/to/asset.png' }) * world.particleManager.create('explosion', { ... }) * }) */ onBoot(callback: BootCallback): NovelModule; }; /** * MVC 구조의 Novel 모듈을 정의하는 팩토리입니다. * * - `schema`: 공유 상태 초깃값 (Model) * - `.defineCommand(handler)`: 커맨드 핸들러 등록 (Controller). 핸들러 내부에서 setState() 호출 시 _onUpdate() 자동 호출. * - `.defineView(builder)`: View 빌더 등록. * - `.hooker`: 이 모듈의 훅 시스템 (`IHookallSync`). * * @example * // 모듈 정의 * const myModule = define({ ... }) * * // 훅 방출 (모듈 내부) * myModule.hooker.trigger('my:event', initialValue, (val) => val, ...params) * * // 훅 구독 (외부) * defineHook(config)({ 'my:event': (val, ...params) => val }) */ export declare function define = Record, THook extends ListenerSignature = DefaultHook>(schema?: TSchema): NovelModule; /** * 유니온 타입을 인터섹션 타입으로 변환합니다. * @internal */ type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never; /** * `NovelConfig`의 `modules`에서 각 모듈의 `THook` 타입을 유니온으로 추출합니다. * @internal */ type ModuleHooksUnion>> = { [K in keyof TModules]: TModules[K] extends NovelModule ? THook : DefaultHook; }[keyof TModules]; /** * `NovelConfig`에서 사용 가능한 모든 훅 타입을 추출합니다. * `NovelHook`과 각 모듈의 `THook`을 합친 인터섹션 타입입니다. * @internal */ export type AllHooksOf = TConfig extends { modules?: infer TMods; } ? [TMods] extends [Record>] ? NovelHookRef & UnionToIntersection> : NovelHookRef : NovelHookRef; /** * Novel 레벨 훅 참조 타입 (Novel.ts에서 NovelHook을 임포트하지 않기 위한 플레이스홀더). * 실제 타입은 Novel.ts의 NovelHook과 동일한 구조여야 합니다. * @internal */ export type NovelHookRef = { 'novel:save': (value: any) => any; 'novel:load': (value: any) => any; 'novel:next': (value: boolean) => boolean; 'novel:scene': (value: string) => string; 'novel:var': (payload: { name: string; oldValue: any; newValue: any; }, ctx: SceneContext | undefined) => { name: string; oldValue: any; newValue: any; }; }; /** * `defineHook()`이 반환하는 씬 스코프 훅 디스크립터. * 씬 시작 시 훅을 등록하고, 씬 종료 시 해제합니다. * `defineScene`의 `hooks` 필드에 전달하십시오. */ export interface SceneHookDescriptor { /** @internal 씬 시작 시 Novel 엔진이 호출합니다. */ readonly _register: (novel: any) => void; /** @internal 씬 종료/전환 시 Novel 엔진이 호출합니다. */ readonly _unregister: (novel: any) => void; } type HookReturn = T extends (...args: any) => infer R ? R : never; /** hookall HookallCallbackParams 추출 헬퍼 */ type HookParams = T extends (initialValue: any, ...params: infer R) => any ? R : never; export type SceneHookMethods> = { onBefore?: (value: HookReturn[K]>, ...params: HookParams[K]>) => HookReturn[K]>; onAfter?: (value: HookReturn[K]>, ...params: HookParams[K]>) => HookReturn[K]>; onceBefore?: (value: HookReturn[K]>, ...params: HookParams[K]>) => HookReturn[K]>; onceAfter?: (value: HookReturn[K]>, ...params: HookParams[K]>) => HookReturn[K]>; }; export type SceneHookMap = { [K in keyof AllHooksOf]?: SceneHookMethods; }; /** * 씬 스코프로 모듈/novel 훅을 구독하는 헬퍼입니다. * 반환값을 `defineScene`의 `hooks` 필드에 전달하면, * 씬 시작 시 자동으로 훅이 등록되고, 씬 종료/전환 시 자동으로 해제됩니다. * * 훅 키와 콜백 타입은 `config.modules`에 등록된 각 모듈의 `THook`과 * `NovelHook`에서 자동으로 추론됩니다. * * @example * defineScene({ * config, * hooks: defineHook(config)({ * 'dialogue:text': { * onBefore: (value) => ({ ...value, text: value.text.toUpperCase() }), * }, * 'novel:next': { * onAfter: (value) => value, * } * }), * }, [ ... ]) * * @param config - `defineNovelConfig()`로 생성된 NovelConfig * @returns 훅 맵을 받는 함수를 반환합니다. */ export declare function defineHook>; }>(config: TConfig): (hookMap: SceneHookMap) => SceneHookDescriptor; export {};