import { type Accessor, type Context, createComponent, createContext, createEffect, createMemo, createRoot, getOwner, type JSX, onCleanup, type ParentProps, useContext, } from "solid-js"; import { type Bunja, type BunjaStore, createBunjaStore, createReadScopeFn, createScope, type HashFn, type ReadScope, type Scope, type ScopeValuePair, } from "./bunja.ts"; type MaybeAccessor = T | Accessor; type AccessedValue = T extends Accessor ? U : T; function access(maybeAccessor: MaybeAccessor): T { if (typeof maybeAccessor !== "function") return maybeAccessor; return (maybeAccessor as Accessor)(); } export const BunjaStoreContext: Context = createContext( createBunjaStore({ wrapInstance: createRoot }), ); export function BunjaStoreProvider( props: ParentProps, ): JSX.Element { const owner = getOwner(); const bunjaStore = createBunjaStore({ wrapInstance: (impl) => createRoot(impl, owner), }); onCleanup(() => bunjaStore.dispose()); return createComponent(BunjaStoreContext.Provider, { get value() { return bunjaStore; }, get children() { return props.children; }, }); } export const scopeContextMap: Map< Scope, Context> > = new Map(); export function bindScope( scope: Scope, context: Context>, ): void { scopeContextMap.set( scope as Scope, context as Context>, ); } export function createScopeFromContext( context: Context, hash?: HashFn>, ): Scope> { const scope = createScope>(hash); bindScope(scope, context as Context>>); return scope; } const defaultReadScope: ReadScope = (scope: Scope) => { const context = scopeContextMap.get(scope as Scope)!; return access(useContext(context)) as T; }; export function useBunja( bunja: MaybeAccessor>, scopeValuePairs?: MaybeAccessor[]>, ): Accessor { const store = useContext(BunjaStoreContext); const readScope = createMemo(() => { const pairs = access(scopeValuePairs); return pairs ? createReadScopeFn(pairs, defaultReadScope) : defaultReadScope; }); const entry = createMemo(() => store.get(access(bunja), readScope())); createEffect(() => { const cleanup = entry().mount(); onCleanup(() => setTimeout(cleanup)); }); return () => entry().value; }