/** * MIT License * * Copyright (c) 2025 Chris M. Perez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { Predicate } from 'effect'; import type { BlueprintDef, BlueprintContext, EffuseChild, Component, } from '../render/node.js'; import type { ScriptContext, ExposedValues } from './script-context.js'; import { createScriptContext, runMountCallbacks } from './script-context.js'; import type { ComponentLifecycle } from './lifecycle.js'; import type { EffuseLayerRegistry, LayerPropsOf } from '../layers/types.js'; import type { LayerContext } from '../layers/context.js'; import { getLayerContext, isLayerRuntimeReady } from '../layers/context.js'; import { LayerRuntimeNotReadyError } from '../layers/errors.js'; interface PropsWithChildren { readonly children?: EffuseChild; } export type TemplateArgs = E & { readonly children?: EffuseChild; }; export interface LayerScriptContext< P, K extends keyof EffuseLayerRegistry, > extends ScriptContext

{ readonly layerProps: LayerPropsOf; readonly layer: LayerContext>; } export interface DefineOptionsWithInferredProps { name?: string; props: P; layer?: undefined; script: (ctx: ScriptContext

) => E | undefined; template: (exposed: TemplateArgs, props: Readonly

) => EffuseChild; } export interface DefineOptionsWithInferredPropsAndLayer< P, E extends ExposedValues, K extends keyof EffuseLayerRegistry, > { name?: string; props: P; layer: K; script: (ctx: LayerScriptContext) => E | undefined; template: (exposed: TemplateArgs, props: Readonly

) => EffuseChild; } export interface DefineOptions { name?: string; props?: undefined; layer?: undefined; script: (ctx: ScriptContext

) => E | undefined; template: (exposed: TemplateArgs, props: Readonly

) => EffuseChild; } export interface DefineOptionsWithLayer< P, E extends ExposedValues, K extends keyof EffuseLayerRegistry, > { name?: string; props?: undefined; layer: K; script: (ctx: LayerScriptContext) => E | undefined; template: (exposed: TemplateArgs, props: Readonly

) => EffuseChild; } interface DefineState { exposed: E; lifecycle: ComponentLifecycle; _template: (exposed: TemplateArgs, props: unknown) => EffuseChild; [key: string]: unknown; } export function define< P = Record, E extends ExposedValues = ExposedValues, >( options: DefineOptions | DefineOptionsWithInferredProps ): Component

; export function define< K extends keyof EffuseLayerRegistry, P = Record, E extends ExposedValues = ExposedValues, >( options: | DefineOptionsWithLayer | DefineOptionsWithInferredPropsAndLayer ): Component

; export function define< P = Record, E extends ExposedValues = ExposedValues, K extends keyof EffuseLayerRegistry = never, >( options: | DefineOptions | DefineOptionsWithLayer | DefineOptionsWithInferredProps | DefineOptionsWithInferredPropsAndLayer ): Component

{ const blueprint: BlueprintDef

= { _tag: 'Blueprint', name: (options as { name?: string }).name, state: (props: P) => { const { context, state } = createScriptContext(props); let scriptResult: E | undefined; const layerName = (options as { layer?: string }).layer; if (Predicate.isString(layerName)) { if (isLayerRuntimeReady()) { const layerContext = getLayerContext(layerName) as LayerContext< LayerPropsOf >; const extendedContext: LayerScriptContext = { ...context, layerProps: layerContext.props, layer: layerContext, }; scriptResult = ( options as unknown as DefineOptionsWithLayer ).script(extendedContext); } else { throw new LayerRuntimeNotReadyError({ layerName: layerName, }); } } else { scriptResult = (options as DefineOptions).script(context); } if (Predicate.isNotNullable(scriptResult)) { Object.assign(state.exposed, scriptResult); } queueMicrotask(() => { runMountCallbacks(state); }); return { exposed: state.exposed, lifecycle: state.lifecycle, _template: options.template, } as DefineState as unknown as Record; }, view: (ctx: BlueprintContext

) => { const state = ctx.state as unknown as DefineState; const propsWithChildren = ctx.props as unknown as PropsWithChildren; const exposedWithChildren: TemplateArgs = { ...state.exposed, children: propsWithChildren.children, }; return state._template(exposedWithChildren, ctx.props); }, }; return blueprint as unknown as Component

; } export type InferExposed = D extends DefineOptions ? E : never; export type InferProps = D extends DefineOptionsWithInferredProps ? P : D extends DefineOptions ? P : never; export type LayerPropsFor = LayerPropsOf;