@molecule/app-react-native 3.0.1 → 3.0.3

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.
Files changed (2) hide show
  1. package/README.md +228 -176
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-28T08:44:21.978Z
6
+ Generated: 2026-11-04T14:20:07.525Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-react-native
@@ -72,7 +72,10 @@ Reactive authentication state snapshot (initialized, authenticated, user, loadin
72
72
  interface AuthState<T = UserProfile> {
73
73
  /**
74
74
  * The session's state as one value — render from this instead of combining
75
- * `initialized`/`authenticated`/`loading`. See {@link AuthStatus}.
75
+ * `initialized`/`authenticated`/`loading`. `'loading'` is undecided, so wait
76
+ * and redirect only on `'unauthenticated'`; `'unavailable'` keeps the candidate
77
+ * for retry and can have a null profile. A restore failure other than 401/403
78
+ * never signs out. See {@link AuthStatus}.
76
79
  */
77
80
  status: AuthStatus
78
81
  /**
@@ -80,11 +83,14 @@ interface AuthState<T = UserProfile> {
80
83
  */
81
84
  initialized: boolean
82
85
  /**
83
- * Whether the user is authenticated.
86
+ * Whether the client has an authenticated session. This is not a server
87
+ * confirmation flag: during `'unavailable'` it can be false for a retained
88
+ * token/cookie-only candidate with no cached profile. Use `status` for redirects.
84
89
  */
85
90
  authenticated: boolean
86
91
  /**
87
- * Current user (if authenticated).
92
+ * Current user, or null when none is cached. In particular, an `'unavailable'`
93
+ * token/cookie-only restore has a retained candidate but no profile yet.
88
94
  */
89
95
  user: T | null
90
96
  /**
@@ -105,173 +111,65 @@ Provides login/logout/register flows, token management, profile
105
111
  updates, and auth state subscription.
106
112
 
107
113
  ```typescript
108
- interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegister> = Record<never, never>> {
109
- /**
110
- * Returns the current authentication state snapshot.
111
- *
112
- * READING the state starts the client's one session restore when nobody has called
113
- * {@link initialize} yet — lazily and exactly once, sharing the promise an explicit
114
- * `initialize()` returns. A hook (`useAuth()`), a subscriber, `isAuthenticated()` and
115
- * `getUser()` are reads too, so a client whose entry file dropped the template's
116
- * `authClient.initialize()` line still restores instead of reporting `'loading'`
117
- * forever.
118
- */
119
- getState(): AuthState<T>;
120
- /**
121
- * Returns whether the user is currently authenticated.
122
- */
123
- isAuthenticated(): boolean;
124
- /**
125
- * Gets the current user.
126
- */
127
- getUser(): T | null;
128
- /**
129
- * Optional ownership marker for the current session. Compare values only within
130
- * this client: replacing or ending a session changes it, including re-adopting
131
- * the same token and user. Profile updates and same-session refreshes keep it.
132
- * Consumers can use it to discard stale async work without treating editable
133
- * user fields as an immutable account identifier. Providers without this
134
- * capability require the consumer's own stable identity check.
135
- */
136
- getSessionRevision?(): number;
137
- /**
138
- * Updates the cached user object (state + persistent storage) without
139
- * hitting the network. Intended for local refreshes after a per-app
140
- * mutation (e.g., the user just PATCHed their own profile and the
141
- * server returned the canonical row). Does NOT change tokens.
142
- *
143
- * This does not sign the client in: `authenticated` and `status` are
144
- * unchanged, so an anonymous client stays anonymous. Use
145
- * {@link login} / {@link register} for a credential exchange, or
146
- * {@link setSession} when the token was issued outside the client.
147
- */
148
- setUser(user: T | null): void;
149
- /**
150
- * Gets the current access token.
151
- */
152
- getAccessToken(): string | null;
153
- /**
154
- * Stores the access token in the configured token storage adapter (in a
155
- * browser, Web Storage; `'memory'` where no browser store exists). Use this
156
- * to seed the token after an out-of-band exchange (e.g. the OAuth
157
- * code→token redirect) instead of writing to `localStorage` directly,
158
- * which would violate the storage contract and make a stored bearer token
159
- * JS-readable (XSS-exfiltratable). Pass `null` to clear it.
160
- *
161
- * This changes token storage only; it does not sign the client in
162
- * (`authenticated` and `status` are unchanged). Use {@link login} /
163
- * {@link register} for a credential exchange, or call {@link setSession}
164
- * with the token and the user to establish an already-issued session.
165
- */
166
- setAccessToken(token: string | null): void;
167
- /**
168
- * Adopts a session an out-of-band exchange already established: stores the
169
- * access token (and the refresh token when given), caches the user, and
170
- * sets the COMPLETE state — `initialized`, `authenticated`, `status:
171
- * 'authenticated'`, no error — in one update, with one subscriber
172
- * notification. Every hook reading the shared client sees the user as
173
- * signed in immediately.
174
- *
175
- * This is the way to seed an OAuth/custom sign-in. Calling
176
- * {@link setAccessToken} and {@link setUser} instead does NOT sign the
177
- * client in.
178
- */
179
- setSession(session: AuthSessionSeed<T>): void;
180
- /**
181
- * Gets the refresh token.
182
- */
183
- getRefreshToken(): string | null;
184
- /**
185
- * Logs in with credentials.
186
- */
187
- login(credentials: LoginCredentials): Promise<AuthResult<T>>;
188
- /**
189
- * Logs out the current user.
190
- */
191
- logout(): Promise<void>;
192
- /**
193
- * Registers a new user. Accepts the base fields plus the app's own fields
194
- * (`TRegister`) at the top level — see {@link RegisterData}.
195
- */
196
- register(data: RegisterData<TRegister>): Promise<AuthResult<T>>;
197
- /**
198
- * Refreshes the access token.
199
- */
200
- refresh(): Promise<AuthResult<T>>;
201
- /**
202
- * Requests a password reset.
203
- */
204
- requestPasswordReset(data: PasswordResetRequest): Promise<void>;
205
- /**
206
- * Confirms a password reset.
207
- */
208
- confirmPasswordReset(data: PasswordResetConfirm): Promise<void>;
209
- /**
210
- * Updates the current user's profile.
211
- * Returns the saved profile: the answer applied onto the signed-in user field
212
- * by field, with or without an `id` — a field it carries replaces the value
213
- * (`null` clears it), one it omits or leaves `undefined` is kept, so a field
214
- * cleared by omission must arrive as `null`. A non-object answer is returned
215
- * as it came. An answer for a replaced or ended session leaves the current one
216
- * untouched.
217
- */
218
- updateProfile(data: Partial<T>): Promise<T>;
219
- /**
220
- * Changes the current user's password.
221
- */
222
- changePassword(oldPassword: string, newPassword: string): Promise<void>;
223
- /**
224
- * Initializes auth state (e.g., from stored tokens).
225
- *
226
- * LAZY and IDEMPOTENT: the first read of the state ({@link getState},
227
- * {@link subscribe}, `useAuth()`) starts this same restore when nobody has called
228
- * `initialize()`, and every later call shares the one promise instead of sending a
229
- * second request. An app that keeps the template's explicit call behaves exactly as
230
- * before; an app that loses that line still restores its session.
231
- *
232
- * With a stored token AND a stored user it marks the session `'authenticated'`
233
- * SYNCHRONOUSLY — before its first `await` — so the first render is the
234
- * signed-in UI and no page waits on a verification round trip. Any check
235
- * (a refresh, the cookie-restore probe) settles behind that render, and only an
236
- * explicit 401/403 ends the session; a network/5xx/404 failure keeps it and
237
- * reports `'unavailable'`. With no stored user the client stays `'loading'`
238
- * until the cookie restore decides.
239
- *
240
- * Do not put this call (or a `/me` request of your own) in front of the first
241
- * render, and do not render a loading screen over a session it just restored:
242
- * `whenRestored()` already resolves as soon as the restore has settled, and the
243
- * HTTP gate built on it holds requests for exactly that window.
244
- */
245
- initialize(): Promise<void>;
246
- /**
247
- * Re-checks a restored session against the server, in the background — the
248
- * boot-time validation an app with a stored token needs when a revoked or
249
- * expired token must sign the user out. Call it AFTER the first render
250
- * (`initialize()` restores synchronously from storage; this costs one request).
251
- *
252
- * - The server confirms the user: the session is refreshed and {@link AuthState.status}
253
- * stays `'authenticated'`.
254
- * - The server answers 401 or 403: the token is dead — the stored session is
255
- * cleared and the status becomes `'unauthenticated'`.
256
- * - The server cannot confirm the session — a network failure, a 5xx, or a 404
257
- * from a misconfigured {@link AuthClientConfig.currentUserEndpoint}: the
258
- * session (token AND user) is KEPT and the status becomes `'unavailable'`, so
259
- * the app can retry instead of signing the user out over a bad connection or a
260
- * route that does not exist. The 404 error names `currentUserEndpoint`.
261
- *
262
- * Do not hand-write this beside the client: a parallel token/user store for a
263
- * "validating"/"unavailable" flag is the race this method removes.
264
- *
265
- * Optional: a provider with no restorable session has nothing to validate.
266
- */
267
- validateSession?(): Promise<void>;
268
- /**
269
- * Resolves when the session restore started by `initialize()` has settled — the point
270
- * after which the client knows whether this visitor is signed in.
271
- *
272
- // … truncated — the full declaration is in node_modules/@molecule/app-react-native/dist
114
+ interface AuthClient<
115
+ T = UserProfile,
116
+ TRegister extends RegisterExtraShape<TRegister> = Record<never, never>,
117
+ > {
118
+ /** Returns the current authentication state snapshot. */
119
+ getState(): AuthState<T>
120
+ /** Returns whether the user is currently authenticated. */
121
+ isAuthenticated(): boolean
122
+ /** Gets the current user. */
123
+ getUser(): T | null
124
+ /** Optional ownership marker for the current session. */
125
+ getSessionRevision?(): number
126
+ /** Updates the cached user object (state + persistent storage) without hitting the network. */
127
+ setUser(user: T | null): void
128
+ /** Gets the current access token. */
129
+ getAccessToken(): string | null
130
+ /** Stores the access token in the configured token storage adapter (in a browser, Web Storage; `'memory'` where no browser store exists). */
131
+ setAccessToken(token: string | null): void
132
+ /** Adopts a session an out-of-band exchange already established: stores the access token (and the refresh token when given), caches the user, and sets the COMPLET… */
133
+ setSession(session: AuthSessionSeed<T>): void
134
+ /** Gets the refresh token. */
135
+ getRefreshToken(): string | null
136
+ /** Logs in with credentials. */
137
+ login(credentials: LoginCredentials): Promise<AuthResult<T>>
138
+ /** Logs out the current user. */
139
+ logout(): Promise<void>
140
+ /** Registers a new user. */
141
+ register(data: RegisterData<TRegister>): Promise<AuthResult<T>>
142
+ /** Refreshes the access token. */
143
+ refresh(): Promise<AuthResult<T>>
144
+ /** Requests a password reset. */
145
+ requestPasswordReset(data: PasswordResetRequest): Promise<void>
146
+ /** Confirms a password reset. */
147
+ confirmPasswordReset(data: PasswordResetConfirm): Promise<void>
148
+ /** Updates the current user's profile. */
149
+ updateProfile(data: Partial<T>): Promise<T>
150
+ /** Changes the current user's password. */
151
+ changePassword(oldPassword: string, newPassword: string): Promise<void>
152
+ /** Initializes auth state (e.g., from stored tokens). */
153
+ initialize(): Promise<void>
154
+ /** Re-checks a restored session against the server, in the background — the boot-time validation an app with a stored token needs when a revoked or expired token… */
155
+ validateSession?(): Promise<void>
156
+ /** Resolves when the session restore started by `initialize()` has settled — the point after which the client knows whether this visitor is signed in. */
157
+ whenRestored?(): Promise<void>
158
+ /** Subscribes to auth state changes. */
159
+ subscribe(callback: (state: AuthState<T>) => void): () => void
160
+ /** Subscribes to auth state changes (alias for subscribe). */
161
+ onAuthChange(callback: (state: AuthState<T>) => void): () => void
162
+ /** Gets the current access token (alias for getAccessToken). */
163
+ getToken?(): string | null
164
+ /** Adds an auth event listener. */
165
+ addEventListener(listener: AuthEventListener<T>): () => void
166
+ /** Destroys the auth client. */
167
+ destroy(): void
168
+ }
273
169
  ```
274
170
 
171
+ _(Member comments are shortened to their first sentence to fit; the full declaration is in `node_modules/@molecule/app-react-native/dist`.)_
172
+
275
173
  #### `FormController`
276
174
 
277
175
  Form controller interface.
@@ -357,9 +255,20 @@ interface FormController<T extends FormValuesConstraint<T> = Record<string, unkn
357
255
  * `defaultValues`, or the values a previous `reset(values)` established.
358
256
  * Passing `values` merges them over the current baseline and makes the
359
257
  * result the new baseline, so a form reset to freshly loaded or saved data
360
- * reads clean (`isDirty === false`).
258
+ * reads clean (`isDirty === false`). A changed non-null `id` replaces the
259
+ * record and cancels a pending submit.
361
260
  */
362
261
  reset(values?: Partial<T>): void
262
+ /**
263
+ * Applies externally loaded values without clearing submission state.
264
+ *
265
+ * The new clean baseline is the original `defaultValues` plus this latest
266
+ * record, never a merge of earlier records. With `keepDirtyValues`, edited
267
+ * fields retain their values while pristine fields take the loaded values.
268
+ * A changed non-null `id` still replaces the record and cancels a pending
269
+ * submit. Equal-content baselines cause no update.
270
+ */
271
+ updateValues(values: Partial<T>, resetOptions?: FormResetOptions): void
363
272
  /**
364
273
  * Handles form submission.
365
274
  */
@@ -382,6 +291,21 @@ interface FormController<T extends FormValuesConstraint<T> = Record<string, unkn
382
291
  }
383
292
  ```
384
293
 
294
+ #### `FormResetOptions`
295
+
296
+ Options controlling how externally supplied values replace a form's baseline.
297
+
298
+ ```typescript
299
+ interface FormResetOptions {
300
+ /**
301
+ * Keep fields whose current value differs from their baseline when `values`
302
+ * changes. Pristine fields still take the new values. False by default, so a
303
+ * new record can replace edits from the previous record.
304
+ */
305
+ keepDirtyValues?: boolean
306
+ }
307
+ ```
308
+
385
309
  #### `FormOptions`
386
310
 
387
311
  Form creation options.
@@ -393,6 +317,14 @@ interface FormOptions<T extends FormValuesConstraint<T>> {
393
317
  * is created. Mutating this object afterwards does not change the baseline.
394
318
  */
395
319
  defaultValues?: Partial<T>
320
+ /**
321
+ * Externally loaded values, merged over `defaultValues` as the clean
322
+ * baseline. Framework bindings also apply later content changes; use
323
+ * `resetOptions.keepDirtyValues` to preserve edits while pristine fields fill.
324
+ */
325
+ values?: Partial<T>
326
+ /** Controls how framework bindings apply changed `values`. */
327
+ resetOptions?: FormResetOptions
396
328
  /**
397
329
  * Validation mode.
398
330
  */
@@ -869,8 +801,11 @@ interface UseFormResult<T extends FormValuesConstraint<T>> {
869
801
  getError: (name: keyof T) => string | undefined
870
802
  setError: (name: keyof T, error: string | undefined) => void
871
803
  clearErrors: () => void
872
- handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (event?: React.FormEvent) => void
804
+ handleSubmit: (
805
+ onSubmit: (values: T) => void | Promise<void>,
806
+ ) => (event?: React.FormEvent) => Promise<void>
873
807
  reset: (values?: Partial<T>) => void
808
+ updateValues: (values: Partial<T>, resetOptions?: FormResetOptions) => void
874
809
  validate: () => Promise<boolean>
875
810
  }
876
811
  ```
@@ -894,7 +829,8 @@ Result of useHttp hook.
894
829
  ```typescript
895
830
  interface UseHttpResult<T> extends UseHttpState<T> {
896
831
  /**
897
- * Runs the request and resolves with its data, or `null` on failure.
832
+ * Runs the request and resolves with its own data, or `null` on failure,
833
+ * even when a newer request or `reset` has superseded its displayed state.
898
834
  *
899
835
  * This function is IDENTITY-STABLE for as long as `method`, `url` and the HTTP
900
836
  * client do not change, so it is safe in a `useEffect` dependency array. Its
@@ -921,11 +857,14 @@ interface UseHttpOptions<T> extends RequestConfig {
921
857
  */
922
858
  immediate?: boolean
923
859
  /**
924
- * Callback when request succeeds.
860
+ * Callback for each request that succeeds while mounted, including a request
861
+ * superseded by another `execute` or `reset`. Read `data` from the hook when
862
+ * only the newest request's result should drive the UI.
925
863
  */
926
864
  onSuccess?: (data: T) => void
927
865
  /**
928
- * Callback when request fails.
866
+ * Callback for each request that fails while mounted, including a request
867
+ * superseded by another `execute` or `reset`.
929
868
  */
930
869
  onError?: (error: Error) => void
931
870
  }
@@ -965,6 +904,27 @@ interface UseOAuthReturn {
965
904
  }
966
905
  ```
967
906
 
907
+ #### `UseOptimisticValueResult`
908
+
909
+ What {@link useOptimisticValue} returns: the state to render and the two ways to change it.
910
+
911
+ ```typescript
912
+ interface UseOptimisticValueResult<T> extends OptimisticValueState<T> {
913
+ /**
914
+ * Shows `next` at once and saves it, after any save already running.
915
+ *
916
+ * @param next - The value the person chose.
917
+ */
918
+ set: (next: T) => void
919
+ /**
920
+ * Like `set`, computed from the value shown now (`update((on) => !on)`).
921
+ *
922
+ * @param change - Computes the next value from the value shown now.
923
+ */
924
+ update: (change: (current: T) => T) => void
925
+ }
926
+ ```
927
+
968
928
  #### `UsePasswordResetReturn`
969
929
 
970
930
  Return type for usePasswordReset hook.
@@ -1287,6 +1247,19 @@ interface ProviderProps {
1287
1247
  }
1288
1248
  ```
1289
1249
 
1250
+ #### `SessionReadyProps`
1251
+
1252
+ Props for the `SessionReady` component.
1253
+
1254
+ ```typescript
1255
+ interface SessionReadyProps {
1256
+ /** What renders once the initial restore settles — signed in, signed out, or unavailable. */
1257
+ children: ReactNode
1258
+ /** What renders while the session restore is still undecided; default nothing. */
1259
+ fallback?: ReactNode
1260
+ }
1261
+ ```
1262
+
1290
1263
  #### `StateProviderProps`
1291
1264
 
1292
1265
  Props for state provider component.
@@ -1416,6 +1389,20 @@ Properties for molecule provider.
1416
1389
  interface MoleculeProviderProps extends ProviderProps {
1417
1390
  state?: StateProvider
1418
1391
  auth?: AuthClient<unknown>
1392
+ /**
1393
+ * Render the app only once `auth` leaves its initial loading status ({@link SessionReadyProps}).
1394
+ * After a full page load a returning visitor's session is restored by one request, and until
1395
+ * it answers `useAuth().user` is `null` on every page — a public page's signed-in action
1396
+ * (`if (!user) return`, a redirect to `/login`) then treats a signed-in visitor as signed out.
1397
+ * When restoration succeeds, every page's first render reads the restored user. An
1398
+ * `'unavailable'` session also renders, but remains unconfirmed and may have a null user.
1399
+ * A visitor with no session to restore settles in microtasks without a request.
1400
+ * Ignored without `auth`. Synchronous SSR renders the fallback for a loading client:
1401
+ * initialize a request-scoped client first, or leave this disabled for SSR.
1402
+ */
1403
+ awaitSession?: boolean
1404
+ /** Rendered instead of the app while {@link MoleculeProviderProps.awaitSession} waits; default nothing. */
1405
+ sessionFallback?: ReactNode
1419
1406
  theme?: ThemeProvider
1420
1407
  router?: Router
1421
1408
  i18n?: I18nProvider
@@ -1466,7 +1453,9 @@ interface UseAuthResult<
1466
1453
  * The session state as one value — render from this instead of combining
1467
1454
  * `initialized`/`authenticated`/`loading`. A stored token AND user make it
1468
1455
  * `'authenticated'` on the first render; only `'loading'` means "no decision
1469
- * yet", so a route guard waits on that and nothing else.
1456
+ * yet", so a route guard waits on that and nothing else. `'unavailable'` keeps
1457
+ * a session candidate for retry and can have a null profile; redirect only on
1458
+ * `'unauthenticated'`.
1470
1459
  */
1471
1460
  status: AuthState<T>['status']
1472
1461
  state: AuthState<T>
@@ -1486,8 +1475,10 @@ interface UseAuthResult<
1486
1475
  * `authenticated` read signed out.
1487
1476
  */
1488
1477
  setSession: AuthClient<T>['setSession']
1478
+ /** Whether a session is authenticated locally; this can be false while unavailable. */
1489
1479
  isAuthenticated: boolean
1490
1480
  isLoading: boolean
1481
+ /** The cached profile, which can be null while an unavailable candidate is retained. */
1491
1482
  user: T | null
1492
1483
  }
1493
1484
  ```
@@ -2011,6 +2002,12 @@ function useAuthClient<
2011
2002
 
2012
2003
  Hook for authentication state and actions.
2013
2004
 
2005
+ `status === 'loading'` is undecided: wait for it and redirect only on
2006
+ `status === 'unauthenticated'`. `'unavailable'` keeps the session candidate
2007
+ for retry, but `isAuthenticated` can be false and `user` can be null while a
2008
+ token/cookie-only restore has no cached profile. A restore failure other than
2009
+ 401/403 never signs out.
2010
+
2014
2011
  Pass the app's user type — the SAME type used for the auth client and its
2015
2012
  config (`createJWTAuthClient<MyUser>`, `AuthClientConfig<MyUser>`) — so
2016
2013
  `user` and `setSession`/`setUser` are typed for the pages:
@@ -2214,7 +2211,7 @@ function useForm<T extends FormValuesConstraint<T> = Record<string, unknown>>(
2214
2211
  ): UseFormResult<T>
2215
2212
  ```
2216
2213
 
2217
- - `options` — Form options. `defaultValues`, `mode` and the form-level `validate` are forwarded to the provider; omit the options entirely for a form whose fields are registered with their own defaults.
2214
+ - `options` — Form options. `defaultValues`, `mode` and the form-level `validate` are forwarded to the provider. When `values` changes by content, it becomes the new baseline; `resetOptions.keepDirtyValues` retains edited fields while pristine fields take the loaded values. Omit the options entirely for a form whose fields are registered with their own defaults.
2218
2215
 
2219
2216
  **Returns:** Form state and methods
2220
2217
 
@@ -3114,6 +3111,8 @@ function MoleculeProvider({
3114
3111
  workspace,
3115
3112
  editor,
3116
3113
  preview,
3114
+ awaitSession,
3115
+ sessionFallback,
3117
3116
  }: MoleculeProviderProps): React.ReactElement
3118
3117
  ```
3119
3118
 
@@ -3121,6 +3120,38 @@ function MoleculeProvider({
3121
3120
 
3122
3121
  **Returns:** The rendered combined provider element.
3123
3122
 
3123
+ #### `SessionReady(props)`
3124
+
3125
+ Renders its children once `useAuth().status` has left `'loading'` (signed in, signed out,
3126
+ or `'unavailable'`), and `fallback` until then. It never redirects. An unavailable session
3127
+ is unconfirmed and may still have `user === null`; handle that status separately from sign-out.
3128
+
3129
+ After a full page load a returning visitor's session is restored by one request. Until it
3130
+ answers, `useAuth().user` is `null` everywhere, so a public page that renders a signed-in action
3131
+ at once lets that action run against "signed out": a Follow button whose handler starts with
3132
+ `if (!user) return` ignores the click, one that redirects to `/login` signs the visitor out of
3133
+ the flow. Wrapping the routes (or passing `awaitSession` to `MoleculeProvider`) makes every
3134
+ page's first render read the restored user when restoration succeeds.
3135
+
3136
+ A visitor with no session to restore settles in microtasks without a request. Must render
3137
+ inside the auth context (`AuthProvider`, or `MoleculeProvider` with `auth`). A client that
3138
+ nobody initialized starts its restore on this first read.
3139
+ The bundled JWT client bounds each auth request (default 10 seconds) and settles a timeout
3140
+ as `'unavailable'`. Custom clients must also settle their initial session status.
3141
+
3142
+ Synchronous SSR renders the fallback while a client is loading. Initialize a request-scoped
3143
+ client before rendering to include its decided content, or leave the hold disabled for SSR.
3144
+
3145
+ Route-level redirects for signed-in-only pages stay with `AuthGuard` (`@molecule/app-ui-react`).
3146
+
3147
+ ```typescript
3148
+ function SessionReady({ children, fallback }: SessionReadyProps): ReactNode
3149
+ ```
3150
+
3151
+ - `props` — {@link SessionReadyProps}.
3152
+
3153
+ **Returns:** The children once the session is decided, otherwise the fallback.
3154
+
3124
3155
  ### Constants
3125
3156
 
3126
3157
  #### `DEFAULT_AGENT_NAME`
@@ -3257,6 +3288,27 @@ const useAsyncState: <T>(initialState: T) => [T, AsyncSetState<T>, AsyncExtendSt
3257
3288
 
3258
3289
  **Returns:** Tuple of [state, asyncSetState, asyncExtendState]
3259
3290
 
3291
+ #### `useOptimisticValue`
3292
+
3293
+ A server-backed value — a favorite, follow, like or bookmark button, a rating, a quantity, a
3294
+ setting switch — that changes on the first render after a tap and is saved one change at a
3295
+ time, in order. A tap during a save is saved right after it; only a failure of the newest
3296
+ change puts the server value back. See `createOptimisticValue` in `@molecule/app-state`.
3297
+
3298
+ `value` is the server's value from props or a query. A new `value` is taken only when it
3299
+ differs (by `equals`) from the one the hook last received, and only while nothing is being
3300
+ saved, so a parent re-render or a refetch of older data never flips the button mid-save. The
3301
+ latest `commit` and `onError` are always used. Give the component a `key` per record, so one
3302
+ record's pending change never shows on another.
3303
+
3304
+ ```typescript
3305
+ const useOptimisticValue: <T>(options: OptimisticValueOptions<T>) => UseOptimisticValueResult<T>
3306
+ ```
3307
+
3308
+ - `options` — The server's current value and how to save a change.
3309
+
3310
+ **Returns:** The value to render, whether a save is pending, the newest failure, and `set`/`update`.
3311
+
3260
3312
  #### `usePromise`
3261
3313
 
3262
3314
  Hook that wraps an async function with state tracking.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-react-native",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "React Native framework bindings for molecule.dev",
5
5
  "homepage": "https://www.molecule.dev/packages/app-react-native",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "directory": "packages/app/frameworks/react-native"
34
34
  },
35
35
  "devDependencies": {
36
- "@molecule/app-react": "3.0.1",
36
+ "@molecule/app-react": "3.3.1",
37
37
  "@types/node": "26.6.5",
38
38
  "react": "19.3.1",
39
39
  "typescript": "6.0.3",