@molecule/app-react-native 3.0.2 → 3.0.4

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 +168 -161
  2. package/package.json +2 -8
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-31T13:49:36.606Z
6
+ Generated: 2026-11-04T14:20:07.525Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-react-native
@@ -111,165 +111,65 @@ Provides login/logout/register flows, token management, profile
111
111
  updates, and auth state subscription.
112
112
 
113
113
  ```typescript
114
- interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegister> = Record<never, never>> {
115
- /**
116
- * Returns the current authentication state snapshot.
117
- *
118
- * READING the state starts the client's one session restore when nobody has called
119
- * {@link initialize} yet — lazily and exactly once, sharing the promise an explicit
120
- * `initialize()` returns. A hook (`useAuth()`), a subscriber, `isAuthenticated()` and
121
- * `getUser()` are reads too, so a client whose entry file dropped the template's
122
- * `authClient.initialize()` line still restores instead of reporting `'loading'`
123
- * forever.
124
- */
125
- getState(): AuthState<T>;
126
- /**
127
- * Returns whether the user is currently authenticated.
128
- */
129
- isAuthenticated(): boolean;
130
- /**
131
- * Gets the current user.
132
- */
133
- getUser(): T | null;
134
- /**
135
- * Optional ownership marker for the current session. Compare values only within
136
- * this client: replacing or ending a session changes it, including re-adopting
137
- * the same token and user. Profile updates and same-session refreshes keep it.
138
- * Consumers can use it to discard stale async work without treating editable
139
- * user fields as an immutable account identifier. Providers without this
140
- * capability require the consumer's own stable identity check.
141
- */
142
- getSessionRevision?(): number;
143
- /**
144
- * Updates the cached user object (state + persistent storage) without
145
- * hitting the network. Intended for local refreshes after a per-app
146
- * mutation (e.g., the user just PATCHed their own profile and the
147
- * server returned the canonical row). A non-null profile does NOT change
148
- * tokens or sign the client in: `authenticated` and `status` are unchanged,
149
- * so an anonymous client stays anonymous. Use {@link login} /
150
- * {@link register} for a credential exchange, or {@link setSession} when
151
- * the token was issued outside the client.
152
- *
153
- * Passing `null` on a signed-in client, or while any session operation is
154
- * pending, ends the local session: it clears the access token, refresh token
155
- * and cached user, stops automatic refresh, and settles `authenticated: false`
156
- * with `status: 'unauthenticated'` in one notification. Every older operation
157
- * loses session ownership, and a persistent sign-out marker prevents a reload
158
- * from restoring its credentials or a late cookie. A late credential exchange
159
- * is revoked best-effort when a logout endpoint is configured, without blocking
160
- * the caller. This method does not otherwise call the server for a settled
161
- * session; use {@link logout} for explicit server revocation. On a settled
162
- * anonymous client, `null` only clears the cached profile and leaves tokens
163
- * and auth state unchanged.
164
- */
165
- setUser(user: T | null): void;
166
- /**
167
- * Gets the current access token.
168
- */
169
- getAccessToken(): string | null;
170
- /**
171
- * Stores the access token in the configured token storage adapter (in a
172
- * browser, Web Storage; `'memory'` where no browser store exists). Use this
173
- * to seed the token after an out-of-band exchange (e.g. the OAuth
174
- * code→token redirect) instead of writing to `localStorage` directly,
175
- * which would violate the storage contract and make a stored bearer token
176
- * JS-readable (XSS-exfiltratable). Pass `null` to clear it.
177
- *
178
- * This changes token storage only: passing `null` does not by itself sign a
179
- * signed-in client out, and a non-null token does not sign an anonymous
180
- * client in (`authenticated` and `status` are unchanged). Use {@link logout}
181
- * to end a session, or {@link setUser} with `null` for local-only sign-out.
182
- * Use {@link login} / {@link register} for a credential exchange, or call
183
- * {@link setSession} with the token and user to establish an already-issued
184
- * session.
185
- */
186
- setAccessToken(token: string | null): void;
187
- /**
188
- * Adopts a session an out-of-band exchange already established: stores the
189
- * access token (and the refresh token when given), caches the user, and
190
- * sets the COMPLETE state — `initialized`, `authenticated`, `status:
191
- * 'authenticated'`, no error — in one update, with one subscriber
192
- * notification. Every hook reading the shared client sees the user as
193
- * signed in immediately.
194
- *
195
- * This is the way to seed an OAuth/custom sign-in. Calling
196
- * {@link setAccessToken} and {@link setUser} instead does NOT sign the
197
- * client in.
198
- */
199
- setSession(session: AuthSessionSeed<T>): void;
200
- /**
201
- * Gets the refresh token.
202
- */
203
- getRefreshToken(): string | null;
204
- /**
205
- * Logs in with credentials.
206
- */
207
- login(credentials: LoginCredentials): Promise<AuthResult<T>>;
208
- /**
209
- * Logs out the current user. The server request is best-effort: a missing
210
- * logout route (404), any other HTTP failure, or a network error never makes
211
- * this method reject. It always clears the access token, refresh token and
212
- * cached user, stops automatic refresh, and publishes one settled
213
- * `authenticated: false`, `status: 'unauthenticated'` transition. Every pending
214
- * session operation loses ownership; a persistent marker prevents reload from
215
- * restoring an older session, and late credentials are revoked best-effort.
216
- *
217
- * When the API has no logout route, set {@link AuthClientConfigOptions.logoutEndpoint}
218
- * to `null` (or `''`) to skip the request. Local sign-out still happens.
219
- */
220
- logout(): Promise<void>;
221
- /**
222
- * Registers a new user. Accepts the base fields plus the app's own fields
223
- * (`TRegister`) at the top level — see {@link RegisterData}.
224
- */
225
- register(data: RegisterData<TRegister>): Promise<AuthResult<T>>;
226
- /**
227
- * Refreshes the access token.
228
- */
229
- refresh(): Promise<AuthResult<T>>;
230
- /**
231
- * Requests a password reset.
232
- */
233
- requestPasswordReset(data: PasswordResetRequest): Promise<void>;
234
- /**
235
- * Confirms a password reset.
236
- */
237
- confirmPasswordReset(data: PasswordResetConfirm): Promise<void>;
238
- /**
239
- * Updates the current user's profile.
240
- * Returns the saved profile: the answer applied onto the signed-in user field
241
- * by field, with or without an `id` — a field it carries replaces the value
242
- * (`null` clears it), one it omits or leaves `undefined` is kept, so a field
243
- * cleared by omission must arrive as `null`. A non-object answer is returned
244
- * as it came. An answer for a replaced or ended session leaves the current one
245
- * untouched.
246
- */
247
- updateProfile(data: Partial<T>): Promise<T>;
248
- /**
249
- * Changes the current user's password.
250
- */
251
- changePassword(oldPassword: string, newPassword: string): Promise<void>;
252
- /**
253
- * Initializes auth state (e.g., from stored tokens).
254
- *
255
- * LAZY and IDEMPOTENT: the first read of the state ({@link getState},
256
- * {@link subscribe}, `useAuth()`) starts this same restore when nobody has called
257
- * `initialize()`, and every later call shares the one promise instead of sending a
258
- * second request. An app that keeps the template's explicit call behaves exactly as
259
- * before; an app that loses that line still restores its session.
260
- *
261
- * With a stored token AND a stored user it marks the session `'authenticated'`
262
- * SYNCHRONOUSLY — before its first `await` — so the first render is the
263
- * signed-in UI and no page waits on a verification round trip. Any check
264
- * (a refresh, the cookie-restore probe) settles behind that render, and only an
265
- * explicit 401/403 ends the session; a network/5xx/404 failure keeps it and
266
- * reports `'unavailable'`. With no stored user the client stays `'loading'`
267
- * until the cookie restore decides.
268
- *
269
- * Do not put this call (or a `/me` request of your own) in front of the first
270
- // … 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
+ }
271
169
  ```
272
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
+
273
173
  #### `FormController`
274
174
 
275
175
  Form controller interface.
@@ -929,7 +829,8 @@ Result of useHttp hook.
929
829
  ```typescript
930
830
  interface UseHttpResult<T> extends UseHttpState<T> {
931
831
  /**
932
- * 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.
933
834
  *
934
835
  * This function is IDENTITY-STABLE for as long as `method`, `url` and the HTTP
935
836
  * client do not change, so it is safe in a `useEffect` dependency array. Its
@@ -956,11 +857,14 @@ interface UseHttpOptions<T> extends RequestConfig {
956
857
  */
957
858
  immediate?: boolean
958
859
  /**
959
- * 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.
960
863
  */
961
864
  onSuccess?: (data: T) => void
962
865
  /**
963
- * Callback when request fails.
866
+ * Callback for each request that fails while mounted, including a request
867
+ * superseded by another `execute` or `reset`.
964
868
  */
965
869
  onError?: (error: Error) => void
966
870
  }
@@ -1000,6 +904,27 @@ interface UseOAuthReturn {
1000
904
  }
1001
905
  ```
1002
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
+
1003
928
  #### `UsePasswordResetReturn`
1004
929
 
1005
930
  Return type for usePasswordReset hook.
@@ -1322,6 +1247,19 @@ interface ProviderProps {
1322
1247
  }
1323
1248
  ```
1324
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
+
1325
1263
  #### `StateProviderProps`
1326
1264
 
1327
1265
  Props for state provider component.
@@ -1451,6 +1389,20 @@ Properties for molecule provider.
1451
1389
  interface MoleculeProviderProps extends ProviderProps {
1452
1390
  state?: StateProvider
1453
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
1454
1406
  theme?: ThemeProvider
1455
1407
  router?: Router
1456
1408
  i18n?: I18nProvider
@@ -3159,6 +3111,8 @@ function MoleculeProvider({
3159
3111
  workspace,
3160
3112
  editor,
3161
3113
  preview,
3114
+ awaitSession,
3115
+ sessionFallback,
3162
3116
  }: MoleculeProviderProps): React.ReactElement
3163
3117
  ```
3164
3118
 
@@ -3166,6 +3120,38 @@ function MoleculeProvider({
3166
3120
 
3167
3121
  **Returns:** The rendered combined provider element.
3168
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
+
3169
3155
  ### Constants
3170
3156
 
3171
3157
  #### `DEFAULT_AGENT_NAME`
@@ -3302,6 +3288,27 @@ const useAsyncState: <T>(initialState: T) => [T, AsyncSetState<T>, AsyncExtendSt
3302
3288
 
3303
3289
  **Returns:** Tuple of [state, asyncSetState, asyncExtendState]
3304
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
+
3305
3312
  #### `usePromise`
3306
3313
 
3307
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.2",
3
+ "version": "3.0.4",
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.1.0",
36
+ "@molecule/app-react": "3.4.0",
37
37
  "@types/node": "26.6.5",
38
38
  "react": "19.3.1",
39
39
  "typescript": "6.0.3",
@@ -48,18 +48,12 @@
48
48
  "react-native-safe-area-context": ">=4.0.0"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
- "@molecule/app-react": {
52
- "optional": true
53
- },
54
51
  "@molecule/app-lifecycle": {
55
52
  "optional": true
56
53
  },
57
54
  "@molecule/app-keyboard": {
58
55
  "optional": true
59
56
  },
60
- "react": {
61
- "optional": true
62
- },
63
57
  "react-native": {
64
58
  "optional": true
65
59
  },