@owlmeans/client 0.1.18-rc.40 → 0.1.18-rc.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ imports everything from here. Server code never uses this package; its equivalen
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- bun add @owlmeans/client@^0.1.18-rc.40
17
+ bun add @owlmeans/client@^0.1.18-rc.41
18
18
  ```
19
19
 
20
20
  `react` and `@remix-run/router` are peer dependencies.
@@ -273,7 +273,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
273
273
  your project's skill store (`.agents/skills/`):
274
274
 
275
275
  ```sh
276
- npx @owlmeans/agent-skills@^0.1.18-rc.38
276
+ npx @owlmeans/agent-skills@^0.1.18-rc.39
277
277
  ```
278
278
 
279
279
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/client",
4
- "version": "0.1.18-rc.40",
5
- "generatedAt": "2026-09-23T19:57:40.682Z",
4
+ "version": "0.1.18-rc.41",
5
+ "generatedAt": "2026-09-24T14:00:35.038Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: client
3
- description: How to use @owlmeans/client — the platform-agnostic React client framework (web and native) — makeClientContext, App/Router, useNavigate/Navigator, useEntrypoint/RoutedComponent, useStoreModel/useStoreList, useValue, the modal and debug services. Auto-invoked when importing client framework primitives, navigating between screens, or reading client state from React.
3
+ description: How to use @owlmeans/client — the platform-agnostic React client framework (web and native) — makeClientContext, App/Router, useNavigate/Navigator, useEntrypoint/RoutedComponent, useStoreModel/useStoreList, useValue, lazyComponent/lazyHandler code-splitting and chunk-failure recovery (retryImport, isChunkLoadError, recoverFromChunkError), the modal and debug services. Auto-invoked when importing client framework primitives, navigating between screens, or reading client state from React.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/client
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client": "^0.1.18-rc.40"` in `dependencies`
11
+ **Install:** `"@owlmeans/client": "^0.1.18-rc.41"` in `dependencies`
12
12
 
13
13
  The React substrate `@owlmeans/web-client` (browser) and the native equivalent are built on. A
14
14
  cross-platform package imports from here; an application normally imports from the platform
@@ -36,6 +36,9 @@ package, which re-exports what it needs — **except the hooks below, which are
36
36
  | `useEntrypoint<T>()` | The `EntrypointContextParams` of the screen currently rendering — `{ alias, path, params, context }` |
37
37
  | `RoutedComponent<Extra>` | Type of a component bound to a frontend protocol |
38
38
  | `handler(Component, preprender?)` | Wrap a React component as an entrypoint handler |
39
+ | `lazyComponent(load, exportName, opts?)` / `lazyHandler(load, exportName, opts?)` | A code-split component with a static `.preload()`; and `handler(lazyComponent(...))` with `.preload` carried through. Types `LazyComponent`, `LazyHandler`, `LazyComponentOptions`, `LazyErrorRenderer` — see Code-splitting |
40
+ | `retryImport(load, opts?)` / `isChunkLoadError(error)` | Run a dynamic `import()` again while it fails to FETCH; tell a fetch failure from a module that loaded and broke. `RetryImportOptions` — see Chunk failures |
41
+ | `reloadOnce(key, windowMs)` / `recoverFromChunkError()` | The guarded page reload, and the one every chunk-failure path in a tab shares — see Chunk failures |
39
42
  | `useStoreModel` / `useStoreList` | React hooks over a `@owlmeans/state` resource — one record by id, or a live query |
40
43
  | `useValue(loader, deps?, forceDefault?)` / `UseValueParams<T>` | Render an async result. The second argument is the **dependency list**, not a default — see Async values |
41
44
  | `useToggle(opened?)` / `Toggleable` | An open/close/toggle handle, which is what a modal surface binds to |
@@ -43,7 +46,7 @@ package, which re-exports what it needs — **except the hooks below, which are
43
46
  | `ModalBodyProps` / `useSetupModalNavigator()` | `{ modal?: ModalService }` — the props a modal body is rendered with; and the hook that lets a body navigate |
44
47
  | `appendDebugService` / `createDebugService` / `appendStateDebug(ctx, alias)` / `DebugService` | The debug menu — `context.debug()` |
45
48
  | `ClientError`, `ComponentError`, `ComponentPropError`, `ComponentPropUndefined` | The client error family, registered with `ResilientError` |
46
- | `DEF_MODAL_ALIAS` (`modal`), `DEF_DEBUG_ALIAS` (`debug`), `DEBUGGER_FLAG` / `DEBUG_CONFIG_KEY` (`debugger`) | Constants |
49
+ | `DEF_MODAL_ALIAS` (`modal`), `DEF_DEBUG_ALIAS` (`debug`), `DEBUGGER_FLAG` / `DEBUG_CONFIG_KEY` (`debugger`), `DEF_IMPORT_RETRY_ATTEMPTS` (2), `DEF_IMPORT_RETRY_DELAYS_MS` (500, 1500), `CHUNK_RELOAD_KEY` (`owlmeans:chunk-reload`), `CHUNK_RELOAD_WINDOW_MS` (60 s) | Constants |
47
50
 
48
51
  ## Subpath Exports
49
52
 
@@ -94,6 +97,91 @@ A screen's guards are its own plus every ancestor's, taken from `getGuards()`. A
94
97
  open screen; when the list is non-empty and no guard matches, the renderer throws
95
98
  `AuthorizationError('frontend-guard')`.
96
99
 
100
+ ## Code-splitting a screen or component
101
+
102
+ `lazyComponent(load, exportName, opts?)` turns a dynamic `import()` into a component whose chunk
103
+ loads on first render, with the `Suspense` boundary INSIDE it — the fallback replaces only this
104
+ component and the layout around it stays mounted. `lazyHandler` is `handler(lazyComponent(...))`
105
+ with `.preload` carried through, so it binds exactly like `handler(Component)`. Both are
106
+ re-exported by `@owlmeans/web-client` and `@owlmeans/web-panel` next to `handler`; the
107
+ chunk-failure tools below by `@owlmeans/web-client`.
108
+
109
+ ```tsx
110
+ import { lazyComponent, lazyHandler } from '@owlmeans/client'
111
+
112
+ // Module scope — never inside a render, a hook or an entrypoint handler factory.
113
+ export const reportsScreen = lazyHandler(
114
+ () => import('./screens/reports.js'), 'ReportsScreen', { fallback: <Spinner /> }
115
+ )
116
+ const Chart = lazyComponent(() => import('./chart.js'), 'Chart', {
117
+ fallback: props => <Skeleton height={props.height} />,
118
+ error: (props, error, retry) => <ChartUnavailable onRetry={retry} />,
119
+ })
120
+
121
+ // Prefetch on intent: the screen then renders without its fallback.
122
+ <a onMouseEnter={() => void reportsScreen.preload()} onFocus={() => void reportsScreen.preload()}>
123
+ ```
124
+
125
+ - **Module scope only.** The route renderer (`utils/route.tsx`) wraps the resolved screen in a
126
+ fresh `memo(...)` on every render, so the route subtree remounts on each navigation. A lazy
127
+ object made at module scope is already resolved by then and renders synchronously; one created
128
+ during a render or inside a handler factory is a new `React.lazy` each time and re-suspends — the
129
+ fallback flashes on every visit.
130
+ - **`preload()`** starts or joins the load and resolves to the component. Once loaded, every later
131
+ render resolves in the same tick — no re-suspend.
132
+ - **`fallback`** is a node or `fallback(props)`. **`error`** is a node or a `LazyErrorRenderer`
133
+ `(props, error, retry) => ReactNode`; `retry()` resets the piece's boundary and renders the
134
+ recreated lazy, which loads the chunk again.
135
+ - **`retry`** — the load runs through `retryImport` by default; pass `RetryImportOptions` to tune
136
+ it or `false` to load once.
137
+ - **An `exportName` the module does not export** rejects with a `SyntaxError` — never retried.
138
+
139
+ ### Chunk failures
140
+
141
+ A lazy piece ALWAYS carries its own error boundary, so a failed chunk never unmounts what is around
142
+ it:
143
+
144
+ | The piece fails with | `error` given | `error` omitted |
145
+ |---|---|---|
146
+ | a chunk-load failure (`isChunkLoadError`), after `retryImport` gave up | with `reload` (default for `lazyHandler`): the guarded reload starts, `fallback` stays, and `error` renders once the guard refuses; without it: `error` renders in place | `recoverFromChunkError()` starts the guarded reload; `fallback` stays in place |
147
+ | anything else (a module that loaded and broke, its own render) | `error` renders in place | propagates to the nearest boundary above, as if the piece had none |
148
+
149
+ Give every piece a deliberate `error`: a leaf that has a plain rendering of the same content (a
150
+ formatter, a highlighter) degrades to it; anything else shows a notice with a retry. A whole screen
151
+ (`lazyHandler`) reloads once before its notice (`reload: true` by default) — it has nothing to
152
+ degrade to.
153
+
154
+ - **Retry scope.** `retryImport` covers a TRANSIENT fetch failure — a blip, an edge answering 404
155
+ or 5xx for a moment. Chromium keeps a failed module fetch for the document's lifetime and rejects
156
+ every later `import()` of that URL at once, so a retry imports the URL the error names with a
157
+ fresh `t` parameter (`chunkUrlOf` + `cacheBustedUrl`, `bustCache` on by default; same-origin
158
+ http(s) URLs only): a new URL, fetched again. That recovers a built chunk. It cannot recover a
159
+ DEV-served module: React Fast Refresh makes every module import itself by its own URL, so the
160
+ busted copy depends on the remembered failure — only a new document loads it, which is what
161
+ `reload` and the guarded reload are for. Safari names no URL; its retries repeat `load`.
162
+ - **A failed load stays failed for the instance that saw it** until its `retry()`: React re-renders
163
+ that instance while recovering from the error, and a fresh load there would suspend again
164
+ forever. A NEW mount — a navigation back, another place in the tree — takes the recreated lazy and
165
+ loads again; so does `preload()`.
166
+ - **`isChunkLoadError(error)`** is true for a browser's failed dynamic import (Chromium "Failed to
167
+ fetch dynamically imported module", Safari "Importing a module script failed", Firefox "error
168
+ loading dynamically imported module"), Vite's "Unable to preload CSS", webpack's `ChunkLoadError`,
169
+ a `vite:preloadError` event, and an element's `error` event. It is false for a `SyntaxError`
170
+ about a missing export and for a throw while the module evaluated — loading those again changes
171
+ nothing.
172
+ - **`retryImport(load, opts?)`** runs `load` again while `shouldRetry(error)` (default
173
+ `isChunkLoadError`) holds, `attempts` (2) more times at most, pausing `delaysMs[i]` before retry
174
+ `i` (500 ms, 1500 ms; the last entry repeats), and rethrows the last failure.
175
+ - **`reloadOnce(key, windowMs)`** reloads the page at most once per `windowMs` per tab, keeping the
176
+ time in `sessionStorage` under `key`; never while offline and never without storage (with no
177
+ guard kept, a failure that survives the reload would reload forever); a platform with no page to
178
+ reload does nothing. It answers whether a reload started.
179
+ - **`recoverFromChunkError()`** is `reloadOnce(CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS)` — the ONE
180
+ guard a tab shares. An application that also reloads on `vite:preloadError` calls it rather than
181
+ keeping a guard of its own, so one failure never reloads twice.
182
+
183
+ Source of truth: `src/lazy.tsx` and `src/lazy-retry.ts` in this package.
184
+
97
185
  ## Client state
98
186
 
99
187
  State lives on the context as a `@owlmeans/state` resource; these hooks subscribe to it.
package/build/consts.d.ts CHANGED
@@ -2,4 +2,12 @@ export declare const DEF_MODAL_ALIAS = "modal";
2
2
  export declare const DEF_DEBUG_ALIAS = "debug";
3
3
  export declare const DEBUGGER_FLAG = "debugger";
4
4
  export declare const DEBUG_CONFIG_KEY = "debugger";
5
+ /** How many times `retryImport` loads a chunk again after its first failure. */
6
+ export declare const DEF_IMPORT_RETRY_ATTEMPTS = 2;
7
+ /** The pause before each retry, in order; the last one repeats. */
8
+ export declare const DEF_IMPORT_RETRY_DELAYS_MS: readonly number[];
9
+ /** The `sessionStorage` key `recoverFromChunkError` keeps the time of its last reload under. */
10
+ export declare const CHUNK_RELOAD_KEY = "owlmeans:chunk-reload";
11
+ /** At most one chunk-recovery reload per this window, per tab. */
12
+ export declare const CHUNK_RELOAD_WINDOW_MS = 60000;
5
13
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,UAAU,CAAA;AACtC,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC,eAAO,MAAM,aAAa,aAAa,CAAA;AACvC,eAAO,MAAM,gBAAgB,aAAa,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,UAAU,CAAA;AACtC,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC,eAAO,MAAM,aAAa,aAAa,CAAA;AACvC,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAC1C,mEAAmE;AACnE,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAAgB,CAAA;AAExE,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,0BAA0B,CAAA;AACvD,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,QAAS,CAAA"}
package/build/consts.js CHANGED
@@ -2,4 +2,12 @@ export const DEF_MODAL_ALIAS = 'modal';
2
2
  export const DEF_DEBUG_ALIAS = 'debug';
3
3
  export const DEBUGGER_FLAG = 'debugger';
4
4
  export const DEBUG_CONFIG_KEY = 'debugger';
5
+ /** How many times `retryImport` loads a chunk again after its first failure. */
6
+ export const DEF_IMPORT_RETRY_ATTEMPTS = 2;
7
+ /** The pause before each retry, in order; the last one repeats. */
8
+ export const DEF_IMPORT_RETRY_DELAYS_MS = [500, 1500];
9
+ /** The `sessionStorage` key `recoverFromChunkError` keeps the time of its last reload under. */
10
+ export const CHUNK_RELOAD_KEY = 'owlmeans:chunk-reload';
11
+ /** At most one chunk-recovery reload per this window, per tab. */
12
+ export const CHUNK_RELOAD_WINDOW_MS = 60_000;
5
13
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAE1C,gFAAgF;AAChF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAA;AAC1C,mEAAmE;AACnE,MAAM,CAAC,MAAM,0BAA0B,GAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AAExE,gGAAgG;AAChG,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAA;AACvD,kEAAkE;AAClE,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAA"}
package/build/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export * from './context.js';
2
2
  export * from './components/index.js';
3
3
  export * from './services/index.js';
4
4
  export * from './helper.js';
5
+ export * from './lazy.js';
6
+ export * from './lazy-retry.js';
5
7
  export * from './navigate.js';
6
8
  export * from './entrypoint.js';
7
9
  export * from './router.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
package/build/index.js CHANGED
@@ -2,6 +2,8 @@ export * from './context.js';
2
2
  export * from './components/index.js';
3
3
  export * from './services/index.js';
4
4
  export * from './helper.js';
5
+ export * from './lazy.js';
6
+ export * from './lazy-retry.js';
5
7
  export * from './navigate.js';
6
8
  export * from './entrypoint.js';
7
9
  export * from './router.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
@@ -0,0 +1,47 @@
1
+ import type { RetryImportOptions } from './types.js';
2
+ /**
3
+ * Whether `error` is a chunk that could not be FETCHED: a browser's failed dynamic import, Vite's
4
+ * stylesheet preload failure, webpack's `ChunkLoadError`, Vite's `vite:preloadError` event, or
5
+ * the `error` event of the element a loader fetched through. A module that was fetched and then
6
+ * failed — a missing export, a throw while it evaluated — is not one: loading it again changes
7
+ * nothing.
8
+ */
9
+ export declare const isChunkLoadError: (error: unknown) => boolean;
10
+ /**
11
+ * The URL of the chunk a failed dynamic import tried to fetch, when the browser's error names it
12
+ * and it is an http(s) URL of this page's origin — never an arbitrary URL out of a message. `null`
13
+ * otherwise (Safari, a non-browser platform, another origin).
14
+ */
15
+ export declare const chunkUrlOf: (error: unknown) => string | null;
16
+ /**
17
+ * `href` with a fresh `t` parameter: a URL the browser has not seen, so it fetches the module
18
+ * again. `t` is the parameter Vite's dev server already gives module URLs; a static host ignores it.
19
+ */
20
+ export declare const cacheBustedUrl: (href: string, now?: number) => string;
21
+ /**
22
+ * Run `load` — a dynamic `import()` — and, while it rejects with a chunk-load failure, run it again
23
+ * after a pause, `attempts` more times at most. Any other rejection is rethrown at once.
24
+ *
25
+ * This covers a TRANSIENT failure — a network blip, an edge answering 404 or 5xx for a moment.
26
+ * A browser remembers a failed module fetch for the document's lifetime (Chromium does) and
27
+ * rejects every later `import()` of that URL at once, so a retry imports the URL the error names
28
+ * with a cache-busting parameter instead (`bustCache`, default on): a new URL, fetched again. Where
29
+ * the error names no URL (Safari), `load` runs again as is; what always recovers is a new
30
+ * document, which `recoverFromChunkError` starts, once and guarded.
31
+ */
32
+ export declare const retryImport: <M>(load: () => Promise<M>, opts?: RetryImportOptions) => Promise<M>;
33
+ /**
34
+ * Reload the page, at most once per `windowMs` in this tab — the time of the last reload is kept
35
+ * in `sessionStorage` under `key`. Never while offline (the reload would land on the browser's own
36
+ * error page), and never without storage: with nowhere to keep the guard, a failure that survives
37
+ * the reload would reload forever. A platform with no page to reload does nothing. Answers whether
38
+ * a reload was started.
39
+ */
40
+ export declare const reloadOnce: (key: string, windowMs: number) => boolean;
41
+ /**
42
+ * The guarded reload a chunk that failed for good ends in: `reloadOnce` under one key every caller
43
+ * in the tab shares, so a lazy boundary and an application's own `vite:preloadError` listener
44
+ * never reload twice for one failure.
45
+ */
46
+ export declare const recoverFromChunkError: () => boolean;
47
+ //# sourceMappingURL=lazy-retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-retry.d.ts","sourceRoot":"","sources":["../src/lazy-retry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAQpD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,UAAW,OAAO,KAAG,OAUjD,CAAA;AAOD;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,MAAM,GAAG,IAgBpD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,SAAU,MAAM,QAAO,MAAM,KAAgB,MAIvE,CAAA;AAKD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,QAAQ,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,kBAAkB,KAAG,OAAO,CAAC,CAAC,CAsBjG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,MAAM,YAAY,MAAM,KAAG,OAoB1D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAO,OAA+D,CAAA"}
@@ -0,0 +1,128 @@
1
+ import { CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS, DEF_IMPORT_RETRY_ATTEMPTS, DEF_IMPORT_RETRY_DELAYS_MS } from './consts.js';
2
+ /**
3
+ * What a browser says when a dynamic `import()` could not fetch its module — Chromium, Safari and
4
+ * Firefox, in that order — and what Vite's preload helper says when a chunk's stylesheet could not.
5
+ */
6
+ const CHUNK_FAILURE = /failed to fetch dynamically imported module|importing a module script failed|error loading dynamically imported module|unable to preload css/i;
7
+ /**
8
+ * Whether `error` is a chunk that could not be FETCHED: a browser's failed dynamic import, Vite's
9
+ * stylesheet preload failure, webpack's `ChunkLoadError`, Vite's `vite:preloadError` event, or
10
+ * the `error` event of the element a loader fetched through. A module that was fetched and then
11
+ * failed — a missing export, a throw while it evaluated — is not one: loading it again changes
12
+ * nothing.
13
+ */
14
+ export const isChunkLoadError = (error) => {
15
+ if (error == null || typeof error !== 'object') {
16
+ return false;
17
+ }
18
+ if (typeof Event !== 'undefined' && error instanceof Event) {
19
+ return error.type === 'vite:preloadError' || error.type === 'error';
20
+ }
21
+ const { name, message } = error;
22
+ return name === 'ChunkLoadError' || (typeof message === 'string' && CHUNK_FAILURE.test(message));
23
+ };
24
+ const pause = (ms) => new Promise(resolve => setTimeout(resolve, ms));
25
+ /** The module URL a Chromium or Firefox import failure names; Safari's message names none. */
26
+ const FAILED_URL = /dynamically imported module:?\s+(\S+)/i;
27
+ /**
28
+ * The URL of the chunk a failed dynamic import tried to fetch, when the browser's error names it
29
+ * and it is an http(s) URL of this page's origin — never an arbitrary URL out of a message. `null`
30
+ * otherwise (Safari, a non-browser platform, another origin).
31
+ */
32
+ export const chunkUrlOf = (error) => {
33
+ const message = error?.message;
34
+ const found = typeof message === 'string' ? FAILED_URL.exec(message)?.[1] : undefined;
35
+ if (found == null) {
36
+ return null;
37
+ }
38
+ try {
39
+ const url = new URL(found);
40
+ const origin = typeof location !== 'undefined' ? location.origin : undefined;
41
+ if ((url.protocol !== 'https:' && url.protocol !== 'http:') || (origin != null && url.origin !== origin)) {
42
+ return null;
43
+ }
44
+ return url.href;
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ };
50
+ /**
51
+ * `href` with a fresh `t` parameter: a URL the browser has not seen, so it fetches the module
52
+ * again. `t` is the parameter Vite's dev server already gives module URLs; a static host ignores it.
53
+ */
54
+ export const cacheBustedUrl = (href, now = Date.now()) => {
55
+ const url = new URL(href);
56
+ url.searchParams.set('t', String(now));
57
+ return url.href;
58
+ };
59
+ const importUrl = (url) => import(/* @vite-ignore */ /* webpackIgnore: true */ url);
60
+ /**
61
+ * Run `load` — a dynamic `import()` — and, while it rejects with a chunk-load failure, run it again
62
+ * after a pause, `attempts` more times at most. Any other rejection is rethrown at once.
63
+ *
64
+ * This covers a TRANSIENT failure — a network blip, an edge answering 404 or 5xx for a moment.
65
+ * A browser remembers a failed module fetch for the document's lifetime (Chromium does) and
66
+ * rejects every later `import()` of that URL at once, so a retry imports the URL the error names
67
+ * with a cache-busting parameter instead (`bustCache`, default on): a new URL, fetched again. Where
68
+ * the error names no URL (Safari), `load` runs again as is; what always recovers is a new
69
+ * document, which `recoverFromChunkError` starts, once and guarded.
70
+ */
71
+ export const retryImport = async (load, opts) => {
72
+ const attempts = Math.max(0, opts?.attempts ?? DEF_IMPORT_RETRY_ATTEMPTS);
73
+ const delays = opts?.delaysMs ?? DEF_IMPORT_RETRY_DELAYS_MS;
74
+ const shouldRetry = opts?.shouldRetry ?? isChunkLoadError;
75
+ const bust = opts?.bustCache !== false;
76
+ const importBusted = opts?.importUrl ?? importUrl;
77
+ let next = load;
78
+ for (let attempt = 0;; attempt++) {
79
+ try {
80
+ return await next();
81
+ }
82
+ catch (error) {
83
+ if (attempt >= attempts || !shouldRetry(error)) {
84
+ throw error;
85
+ }
86
+ const url = bust ? chunkUrlOf(error) : null;
87
+ if (url != null) {
88
+ next = async () => await importBusted(cacheBustedUrl(url));
89
+ }
90
+ await pause(delays[Math.min(attempt, delays.length - 1)] ?? 0);
91
+ }
92
+ }
93
+ };
94
+ /**
95
+ * Reload the page, at most once per `windowMs` in this tab — the time of the last reload is kept
96
+ * in `sessionStorage` under `key`. Never while offline (the reload would land on the browser's own
97
+ * error page), and never without storage: with nowhere to keep the guard, a failure that survives
98
+ * the reload would reload forever. A platform with no page to reload does nothing. Answers whether
99
+ * a reload was started.
100
+ */
101
+ export const reloadOnce = (key, windowMs) => {
102
+ const location = typeof window !== 'undefined' ? window.location : undefined;
103
+ if (typeof location?.reload !== 'function') {
104
+ return false;
105
+ }
106
+ if (typeof navigator !== 'undefined' && navigator.onLine === false) {
107
+ return false;
108
+ }
109
+ try {
110
+ const now = Date.now();
111
+ if (now - Number(window.sessionStorage.getItem(key) ?? 0) < windowMs) {
112
+ return false;
113
+ }
114
+ window.sessionStorage.setItem(key, String(now));
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ location.reload();
120
+ return true;
121
+ };
122
+ /**
123
+ * The guarded reload a chunk that failed for good ends in: `reloadOnce` under one key every caller
124
+ * in the tab shares, so a lazy boundary and an application's own `vite:preloadError` listener
125
+ * never reload twice for one failure.
126
+ */
127
+ export const recoverFromChunkError = () => reloadOnce(CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS);
128
+ //# sourceMappingURL=lazy-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-retry.js","sourceRoot":"","sources":["../src/lazy-retry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { ComponentExport, ExportProps, LazyComponent, LazyComponentOptions, LazyHandler } from './types.js';
2
+ /**
3
+ * Wrap an async module import as a stable lazily-loaded component with a `.preload()`.
4
+ *
5
+ * MUST be called at module scope, never inside a render, a hook body or an entrypoint handler
6
+ * factory — the OwlMeans route renderer (`utils/route.tsx`) remounts the whole route subtree on
7
+ * every navigation (a fresh component identity per render), so a lazy object created during
8
+ * render would re-suspend on every navigation instead of rendering instantly once loaded.
9
+ *
10
+ * The `Suspense` boundary sits inside the returned component, so the fallback replaces only this
11
+ * component — the layout around it stays mounted while the chunk loads.
12
+ *
13
+ * Once loaded, later renders resolve synchronously (no re-suspend), so a component whose chunk
14
+ * was already preloaded never shows its fallback. A load that fails to fetch is retried in place
15
+ * (`retryImport`, tuned or turned off by `opts.retry`); one that still fails is never cached: the
16
+ * internal `React.lazy` is recreated, so the next mount, `retry()` or `preload()` loads for real.
17
+ *
18
+ * The component always carries its own error boundary, so a chunk that failed for good never
19
+ * unmounts what is around it: `opts.error` renders in its place, or — without one — the guarded
20
+ * reload (`recoverFromChunkError`) starts and the fallback stays. Any other error without
21
+ * `opts.error` goes on to the nearest boundary above, as if there were none.
22
+ */
23
+ export declare const lazyComponent: <M, K extends ComponentExport<M>>(load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions) => LazyComponent<ExportProps<M, K>>;
24
+ /**
25
+ * `handler(lazyComponent(...))`, carrying `.preload()` through — bind it to a screen exactly like
26
+ * `handler(Component)`. The same module-scope rule applies: call it where the entrypoints are
27
+ * declared, never inside a render. A screen whose chunk still fails after its retries starts the
28
+ * guarded reload before showing its `error` (`reload` defaults to `true` here): a whole screen has
29
+ * nothing to degrade to, and in a dev-served app only a new document can load it again.
30
+ */
31
+ export declare const lazyHandler: <M, K extends ComponentExport<M>>(load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions) => LazyHandler<ExportProps<M, K>>;
32
+ //# sourceMappingURL=lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAC/E,MAAM,YAAY,CAAA;AAKnB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,QACrD,MAAM,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,oBAAoB,KACjE,aAAa,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAgDjC,CAAA;AA+DD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,QACnD,MAAM,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,oBAAoB,KACjE,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAK/B,CAAA"}
package/build/lazy.js ADDED
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { lazy, Suspense, Component, useState } from 'react';
3
+ import { handler } from './helper.js';
4
+ import { isChunkLoadError, recoverFromChunkError, retryImport } from './lazy-retry.js';
5
+ /**
6
+ * Wrap an async module import as a stable lazily-loaded component with a `.preload()`.
7
+ *
8
+ * MUST be called at module scope, never inside a render, a hook body or an entrypoint handler
9
+ * factory — the OwlMeans route renderer (`utils/route.tsx`) remounts the whole route subtree on
10
+ * every navigation (a fresh component identity per render), so a lazy object created during
11
+ * render would re-suspend on every navigation instead of rendering instantly once loaded.
12
+ *
13
+ * The `Suspense` boundary sits inside the returned component, so the fallback replaces only this
14
+ * component — the layout around it stays mounted while the chunk loads.
15
+ *
16
+ * Once loaded, later renders resolve synchronously (no re-suspend), so a component whose chunk
17
+ * was already preloaded never shows its fallback. A load that fails to fetch is retried in place
18
+ * (`retryImport`, tuned or turned off by `opts.retry`); one that still fails is never cached: the
19
+ * internal `React.lazy` is recreated, so the next mount, `retry()` or `preload()` loads for real.
20
+ *
21
+ * The component always carries its own error boundary, so a chunk that failed for good never
22
+ * unmounts what is around it: `opts.error` renders in its place, or — without one — the guarded
23
+ * reload (`recoverFromChunkError`) starts and the fallback stays. Any other error without
24
+ * `opts.error` goes on to the nearest boundary above, as if there were none.
25
+ */
26
+ export const lazyComponent = (load, exportName, opts) => {
27
+ let loaded;
28
+ let loading;
29
+ const fetchModule = () => opts?.retry === false ? load() : retryImport(load, opts?.retry);
30
+ const runLoad = () => loading ??= fetchModule().then(module => {
31
+ const Comp = module[exportName];
32
+ if (Comp == null) {
33
+ throw new SyntaxError(`Lazy module has no component export "${exportName}"`);
34
+ }
35
+ return loaded = Comp;
36
+ }).catch((error) => {
37
+ loading = undefined;
38
+ current = createLazy();
39
+ throw error;
40
+ });
41
+ const preload = () => loaded != null ? Promise.resolve(loaded) : runLoad();
42
+ const createLazy = () => lazy(() => {
43
+ const ready = loaded;
44
+ if (ready != null) {
45
+ // Already resolved: hand React a thenable that settles synchronously, so its lazy
46
+ // initializer reads the module in the same tick and never suspends.
47
+ const settled = { then: (resolve) => resolve({ default: ready }) };
48
+ return settled;
49
+ }
50
+ return runLoad().then(Comp => ({ default: Comp }));
51
+ });
52
+ let current = createLazy();
53
+ const Lazy = (props) => {
54
+ // The lazy THIS instance renders, taken when it mounts: a failed load stays failed for it
55
+ // while React re-renders it to recover from the error, until `retry` swaps in the recreated
56
+ // lazy. A new mount — a navigation back, another place — takes the recreated one at once.
57
+ const [Current, setCurrent] = useState(() => current);
58
+ const fallback = (typeof opts?.fallback === 'function' ? opts.fallback(props) : opts?.fallback) ?? null;
59
+ return _jsx(LazyErrorBoundary, { error: opts?.error, reload: opts?.reload === true, fallback: fallback, props: props, onRetry: () => setCurrent(() => current), children: _jsx(Suspense, { fallback: fallback, children: _jsx(Current, { ...props }) }) });
60
+ };
61
+ Lazy.displayName = `Lazy(${exportName})`;
62
+ return Object.assign(Lazy, { preload });
63
+ };
64
+ class LazyErrorBoundary extends Component {
65
+ state = { failed: false, error: undefined };
66
+ static getDerivedStateFromError(error) {
67
+ return { failed: true, error };
68
+ }
69
+ componentDidCatch(error) {
70
+ // A chunk that failed for good is recovered by a new document: always without a surface of its
71
+ // own, and with one when asked (`reload`) — a whole screen rather than a piece that degrades.
72
+ // The reload is guarded (once a minute per tab); when the guard refuses, the surface stays.
73
+ if (isChunkLoadError(error) && (this.props.error === undefined || this.props.reload)) {
74
+ if (recoverFromChunkError()) {
75
+ this.setState({ reloading: true });
76
+ }
77
+ }
78
+ }
79
+ /** Render the children again, over the recreated lazy, which loads anew. */
80
+ retry = () => {
81
+ this.setState({ failed: false, error: undefined, reloading: false });
82
+ this.props.onRetry();
83
+ };
84
+ render() {
85
+ if (!this.state.failed) {
86
+ return this.props.children;
87
+ }
88
+ const { error, fallback, props } = this.props;
89
+ if (this.state.reloading === true) {
90
+ return fallback;
91
+ }
92
+ if (error !== undefined) {
93
+ return typeof error === 'function' ? error(props, this.state.error, this.retry) : error;
94
+ }
95
+ if (isChunkLoadError(this.state.error)) {
96
+ return fallback;
97
+ }
98
+ // Not a chunk failure and no surface for it: the application's own boundary decides.
99
+ throw this.state.error;
100
+ }
101
+ }
102
+ /**
103
+ * `handler(lazyComponent(...))`, carrying `.preload()` through — bind it to a screen exactly like
104
+ * `handler(Component)`. The same module-scope rule applies: call it where the entrypoints are
105
+ * declared, never inside a render. A screen whose chunk still fails after its retries starts the
106
+ * guarded reload before showing its `error` (`reload` defaults to `true` here): a whole screen has
107
+ * nothing to degrade to, and in a dev-served app only a new document can load it again.
108
+ */
109
+ export const lazyHandler = (load, exportName, opts) => {
110
+ const LazyComp = lazyComponent(load, exportName, { ...opts, reload: opts?.reload ?? true });
111
+ const refed = handler(LazyComp);
112
+ return Object.assign(refed, { preload: LazyComp.preload });
113
+ };
114
+ //# sourceMappingURL=lazy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.tsx"],"names":[],"mappings":"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"}