/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type { Simplify } from 'type-fest'; import type { ClientHandlerFn, ClientHandlerOpts, CommonHooks, Config, RenderPlugin, ServerHandlerFn, } from '../types.ts'; export function createApp

[]>({ RootLayout, appRenderer, plugins }: ClientHandlerOpts

) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const req = new Request(`${window.location.pathname}${window.location.search}`); const ctx = new Proxy({} as ExtractPluginsAppContext

, { get(_target, prop) { // @ts-expect-error ignore const store = window.__PAGE_CTX__?.appCtx || {}; return store[prop]; }, }); const serverHandler = (() => { throw new Error( 'The server handler should not be called on the client. Something is wrong, make sure you are not calling `appHandler.server()` in code that is included in the client.', ); }) as ServerHandlerFn; const clientHandler: ClientHandlerFn = async ({ renderProps }) => { const appCtx: Record = {}; const commonHooks = { extendCtx: [] as NonNullable[], renderApp: [] as NonNullable[], wrapApp: [] as NonNullable[], }; for (const p of plugins ?? []) { if (p.hooksForReq) { const hooks = await p?.hooksForReq({ req, renderProps, ctx: appCtx }); if (!hooks) continue; if (hooks.common) { for (const name in hooks.common) { const hook = hooks.common[name as keyof CommonHooks]; if (!hook) continue; commonHooks[name as keyof CommonHooks]!.push(hook as any); } } } } for (const fn of commonHooks.extendCtx ?? []) { Object.assign(appCtx, fn() || {}); } // @ts-expect-error ignore window.__PAGE_CTX__ = { appCtx }; let AppComp = appRenderer ? await appRenderer({ req, renderProps }) : undefined; for (const fn of commonHooks.renderApp ?? []) { if (AppComp) { throw new Error('Only one plugin can implement app:render. app:wrap might be what you are looking for.'); } AppComp = await fn(); break; } const wrappers: ((props: { children: () => Config['jsxElement'] }) => Config['jsxElement'])[] = []; for (const fn of commonHooks.wrapApp ?? []) { wrappers.push(fn()); } const renderApp = () => { if (!AppComp) { throw new Error('No plugin implemented renderApp'); } let finalApp: Config['jsxElement']; if (wrappers.length) { const wrapFn = (w: typeof wrappers): Config['jsxElement'] => { const [child, ...remainingWrappers] = w; if (!child) return AppComp!(); return child({ children: () => wrapFn(remainingWrappers) }); }; finalApp = wrapFn(wrappers); } else { finalApp = AppComp(); } return RootLayout ? RootLayout({ children: finalApp }) : finalApp; }; return renderApp; }; return { ctx, serverHandler, clientHandler, }; } /** * Have to duplicate these extract types in client and server entry, or downstream packages don't work correctly */ type Flatten = { [K in keyof T]: T[K] extends object ? T[K] : never; }[keyof T]; type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; type ExtractPluginsAppContext[]> = Simplify< UnionToIntersection< Flatten<{ [K in T[number]['id']]: ExtractPluginAppContext; }> > >; type ExtractPluginAppContext[], K extends T[number]['id']> = ExtractGenericArg1< Extract >; type ExtractGenericArg1 = T extends RenderPlugin ? X : never;