{"version":3,"file":"lazy-with-retry.cjs","names":[],"sources":["../../src/auth/lazy-with-retry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { ComponentType } from \"react\";\n\n/**\n * Any React component, whatever props it declares.\n *\n * Props sit in a *parameter* position, so a bound over them is contravariant:\n * `ComponentType<unknown>` reads as \"a component that accepts every possible\n * props object\", and only a component that declares no props at all satisfies\n * it. A page typed `({ mode }: Props) => …` is rejected, and so is one whose\n * props are entirely optional. `any` is the only bound that admits every\n * component — which is why React itself declares `lazy` and\n * `LazyExoticComponent` as `ComponentType<any>`. `ComponentType<never>` is not\n * an escape: it fails React's own bound through\n * `ComponentClass.getDerivedStateFromProps`, where the props land back in a\n * covariant position.\n *\n * This relaxes the *bound* only. `T` is still inferred as the concrete\n * component, so the rendered element keeps checking its props and `preload()`\n * still resolves to the concrete module.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyComponent = ComponentType<any>;\n\nexport interface LazyWithRetryOptions {\n    /** Max attempts. Default: 3. */\n    retries?: number;\n    /** Initial delay (ms) before retrying. Default: 400. */\n    initialDelay?: number;\n    /**\n     * Reload the page after every retry fails. Helps when the stale chunk\n     * error is caused by an old `index.html` referencing deleted bundles.\n     * Default: true.\n     */\n    reloadOnFinalFailure?: boolean;\n}\n\n/** A lazy component that can also be fetched ahead of being rendered. */\nexport type PreloadableLazy<T extends AnyComponent> = ReturnType<typeof lazy<T>> & {\n    /**\n     * Start fetching the chunk now, before anything renders it.\n     *\n     * Call it on the interaction that makes the route likely — hovering the\n     * link, opening the menu that holds it, finishing the step before it — so\n     * the chunk is warm by the time the user commits and the suspense fallback\n     * never appears.\n     *\n     * Shares its work with the render path: whichever fires first performs the\n     * single fetch and the other awaits the same promise. Safe to call\n     * repeatedly.\n     *\n     * @returns The module, once loaded. Rejects when every retry failed; that\n     *   rejection is already handled internally, so a fire-and-forget call\n     *   never surfaces as an unhandled rejection.\n     */\n    preload: () => Promise<{ default: T }>;\n};\n\n/**\n * Wrap `React.lazy` with automatic retry and a `preload()` method.\n *\n * Common cause of failure: deployed-then-cached `index.html` references chunk\n * filenames that no longer exist. Retrying after a short delay typically picks\n * up the new bundle; a final `location.reload()` recovers from stale\n * `index.html`.\n *\n * The wrapped component keeps its own props: `T` is inferred from the module\n * the factory resolves to, so a page declaring required, optional or no props\n * all pass through and the rendered element is still checked against them. See\n * {@link AnyComponent} for why the constraint has to be written the way it is.\n *\n * @param factory Dynamic import of the module whose `default` is the component.\n * @param options Retry count, initial backoff and the final-failure reload.\n * @returns The lazy component, carrying an extra `preload()`.\n *\n * @example\n * const Settings = lazyWithRetry(() => import(\"./Settings\"));\n *\n * // Warm the chunk when the route becomes likely, not when it is needed.\n * <a href=\"/settings\" onMouseEnter={() => void Settings.preload()}>Settings</a>\n */\nexport function lazyWithRetry<T extends AnyComponent>(\n    factory: () => Promise<{ default: T }>,\n    options: LazyWithRetryOptions = {},\n): PreloadableLazy<T> {\n    const { retries = 3, initialDelay = 400, reloadOnFinalFailure = true } = options;\n\n    async function load(attempt = 1): Promise<{ default: T }> {\n        try {\n            return await factory();\n        } catch (error) {\n            if (attempt >= retries) {\n                if (reloadOnFinalFailure && typeof window !== \"undefined\") {\n                    window.location.reload();\n                }\n                throw error;\n            }\n            await new Promise((resolve) => setTimeout(resolve, initialDelay * 2 ** (attempt - 1)));\n            return load(attempt + 1);\n        }\n    }\n\n    let pending: Promise<{ default: T }> | null = null;\n\n    /**\n     * Run `load` at most once at a time, so preloading and rendering never\n     * fetch the same chunk twice.\n     *\n     * The internal `catch` does two jobs: it clears the memo, so a component\n     * that failed can be tried again once an error boundary resets, and it\n     * marks the promise as handled, so a `preload()` nobody awaited does not\n     * raise an unhandled rejection. Callers still get a promise that rejects.\n     */\n    function loadOnce(): Promise<{ default: T }> {\n        if (!pending) {\n            pending = load();\n            pending.catch(() => {\n                pending = null;\n            });\n        }\n        return pending;\n    }\n\n    const component = lazy(loadOnce) as PreloadableLazy<T>;\n    component.preload = loadOnce;\n    return component;\n}\n"],"mappings":"uBAiFA,SAAgB,EACZ,EACA,EAAgC,CAAC,EACf,CAClB,GAAM,CAAE,UAAU,EAAG,eAAe,IAAK,uBAAuB,IAAS,EAEzE,eAAe,EAAK,EAAU,EAA4B,CACtD,GAAI,CACA,OAAO,MAAM,EAAQ,CACzB,OAAS,EAAO,CACZ,GAAI,GAAW,EAIX,MAHI,GAAwB,OAAO,OAAW,KAC1C,OAAO,SAAS,OAAO,EAErB,EAGV,OADA,MAAM,IAAI,QAAS,GAAY,WAAW,EAAS,EAAe,IAAM,EAAU,EAAE,CAAC,EAC9E,EAAK,EAAU,CAAC,CAC3B,CACJ,CAEA,IAAI,EAA0C,KAW9C,SAAS,GAAoC,CAOzC,OANK,IACD,EAAU,EAAK,EACf,EAAQ,UAAY,CAChB,EAAU,IACd,CAAC,GAEE,CACX,CAEA,IAAM,GAAA,EAAY,EAAA,KAAA,CAAK,CAAQ,EAE/B,MADA,GAAU,QAAU,EACb,CACX"}