/** * 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 { Effect, pipe } from 'effect'; import type { Scope } from 'effect'; import type { Signal, ReadonlySignal, WatchOptions, EffectOptions, EffectHandle, OnCleanup, } from '../types/index.js'; import type { Component } from '../render/node.js'; import { signal } from '../reactivity/index.js'; import { computed } from '../reactivity/computed.js'; import { watch as standaloneWatch, watchMultiple as standaloneWatchMultiple, } from '../effects/index.js'; import { watchEffect as standaloneEffect } from '../effects/effect.js'; import { createComponentLifecycleSync, type ComponentLifecycle, } from './lifecycle.js'; import { useCallback, useMemo } from './hooks.js'; import { getLayerContext, getLayerComponent, getLayerService, isLayerRuntimeReady, type LayerContext, type TypedLayerContext, } from '../layers/context.js'; import type { EffuseLayerRegistry, EffuseServiceRegistry, EffuseComponentRegistry, LayerPropsOf, LayerProvidesOf, } from '../layers/types.js'; import { LayerRuntimeNotReadyError, RouterNotConfiguredError, } from '../layers/errors.js'; import { StoreGetterNotConfiguredError, mapEffuseErrors } from '../errors.js'; export type ExposedValues = object; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface EffuseRegistry {} type RouterType = EffuseRegistry extends { router: infer R } ? R : unknown; export interface ScriptContext
{ readonly props: Readonly
;
expose: (values: ExposedValues) => void;
signal: typeof signal;
computed: (
props: P,
storeGetter?: (name: string) => unknown
): { context: ScriptContext ; state: ScriptState = {
props: Object.freeze({ ...props }),
expose: (values: ExposedValues): void => {
Object.assign(state.exposed, values);
},
signal,
computed,
store: (name: string): unknown => {
if (isLayerRuntimeReady()) {
const layerService = getLayerService(name);
if (layerService !== undefined) {
return layerService;
}
}
if (!getStore) {
throw new StoreGetterNotConfiguredError({});
}
return getStore(name);
},
router: (() => {
if (!globalRouter) {
return new Proxy({} as object, {
get: () => {
throw new RouterNotConfiguredError();
},
}) as RouterType;
}
return globalRouter as RouterType;
})(),
onMount: (callback): void => {
lifecycle.onMount(callback);
},
onUnmount: (callback): void => {
lifecycle.onUnmount(callback);
},
onBeforeMount: (callback): void => {
lifecycle.onBeforeMount(callback);
},
onBeforeUnmount: (callback): void => {
lifecycle.onBeforeUnmount(callback);
},
watch: ['useLayer'],
useStore: (key: string): unknown => {
if (!isLayerRuntimeReady()) {
if (getStore) {
return getStore(key);
}
return undefined;
}
return getLayerService(key);
},
useService: (key: string): unknown => {
if (!isLayerRuntimeReady()) {
return undefined;
}
return getLayerService(key);
},
useLayerProps: ( ['useLayerProps'],
useLayerProvider: ( ['useLayerProvider'],
useComponent: (name: string): Component | undefined => {
if (!isLayerRuntimeReady()) {
return undefined;
}
return getLayerComponent(name) as Component | undefined;
},
};
return { context, state };
};
export const runMountCallbacks =