@molecule/app-react-native 3.0.0 → 3.0.2

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 +105 -49
  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-27T23:36:39.690Z
6
+ Generated: 2026-10-31T13:49:36.606Z
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
  /**
@@ -138,12 +144,23 @@ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegi
138
144
  * Updates the cached user object (state + persistent storage) without
139
145
  * hitting the network. Intended for local refreshes after a per-app
140
146
  * mutation (e.g., the user just PATCHed their own profile and the
141
- * server returned the canonical row). Does NOT change tokens.
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.
142
152
  *
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.
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.
147
164
  */
148
165
  setUser(user: T | null): void;
149
166
  /**
@@ -158,10 +175,13 @@ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegi
158
175
  * which would violate the storage contract and make a stored bearer token
159
176
  * JS-readable (XSS-exfiltratable). Pass `null` to clear it.
160
177
  *
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.
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.
165
185
  */
166
186
  setAccessToken(token: string | null): void;
167
187
  /**
@@ -186,7 +206,16 @@ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegi
186
206
  */
187
207
  login(credentials: LoginCredentials): Promise<AuthResult<T>>;
188
208
  /**
189
- * Logs out the current user.
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.
190
219
  */
191
220
  logout(): Promise<void>;
192
221
  /**
@@ -238,37 +267,6 @@ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegi
238
267
  * until the cookie restore decides.
239
268
  *
240
269
  * 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
270
  // … truncated — the full declaration is in node_modules/@molecule/app-react-native/dist
273
271
  ```
274
272
 
@@ -357,9 +355,20 @@ interface FormController<T extends FormValuesConstraint<T> = Record<string, unkn
357
355
  * `defaultValues`, or the values a previous `reset(values)` established.
358
356
  * Passing `values` merges them over the current baseline and makes the
359
357
  * result the new baseline, so a form reset to freshly loaded or saved data
360
- * reads clean (`isDirty === false`).
358
+ * reads clean (`isDirty === false`). A changed non-null `id` replaces the
359
+ * record and cancels a pending submit.
361
360
  */
362
361
  reset(values?: Partial<T>): void
362
+ /**
363
+ * Applies externally loaded values without clearing submission state.
364
+ *
365
+ * The new clean baseline is the original `defaultValues` plus this latest
366
+ * record, never a merge of earlier records. With `keepDirtyValues`, edited
367
+ * fields retain their values while pristine fields take the loaded values.
368
+ * A changed non-null `id` still replaces the record and cancels a pending
369
+ * submit. Equal-content baselines cause no update.
370
+ */
371
+ updateValues(values: Partial<T>, resetOptions?: FormResetOptions): void
363
372
  /**
364
373
  * Handles form submission.
365
374
  */
@@ -382,6 +391,21 @@ interface FormController<T extends FormValuesConstraint<T> = Record<string, unkn
382
391
  }
383
392
  ```
384
393
 
394
+ #### `FormResetOptions`
395
+
396
+ Options controlling how externally supplied values replace a form's baseline.
397
+
398
+ ```typescript
399
+ interface FormResetOptions {
400
+ /**
401
+ * Keep fields whose current value differs from their baseline when `values`
402
+ * changes. Pristine fields still take the new values. False by default, so a
403
+ * new record can replace edits from the previous record.
404
+ */
405
+ keepDirtyValues?: boolean
406
+ }
407
+ ```
408
+
385
409
  #### `FormOptions`
386
410
 
387
411
  Form creation options.
@@ -393,6 +417,14 @@ interface FormOptions<T extends FormValuesConstraint<T>> {
393
417
  * is created. Mutating this object afterwards does not change the baseline.
394
418
  */
395
419
  defaultValues?: Partial<T>
420
+ /**
421
+ * Externally loaded values, merged over `defaultValues` as the clean
422
+ * baseline. Framework bindings also apply later content changes; use
423
+ * `resetOptions.keepDirtyValues` to preserve edits while pristine fields fill.
424
+ */
425
+ values?: Partial<T>
426
+ /** Controls how framework bindings apply changed `values`. */
427
+ resetOptions?: FormResetOptions
396
428
  /**
397
429
  * Validation mode.
398
430
  */
@@ -869,8 +901,11 @@ interface UseFormResult<T extends FormValuesConstraint<T>> {
869
901
  getError: (name: keyof T) => string | undefined
870
902
  setError: (name: keyof T, error: string | undefined) => void
871
903
  clearErrors: () => void
872
- handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (event?: React.FormEvent) => void
904
+ handleSubmit: (
905
+ onSubmit: (values: T) => void | Promise<void>,
906
+ ) => (event?: React.FormEvent) => Promise<void>
873
907
  reset: (values?: Partial<T>) => void
908
+ updateValues: (values: Partial<T>, resetOptions?: FormResetOptions) => void
874
909
  validate: () => Promise<boolean>
875
910
  }
876
911
  ```
@@ -1466,7 +1501,9 @@ interface UseAuthResult<
1466
1501
  * The session state as one value — render from this instead of combining
1467
1502
  * `initialized`/`authenticated`/`loading`. A stored token AND user make it
1468
1503
  * `'authenticated'` on the first render; only `'loading'` means "no decision
1469
- * yet", so a route guard waits on that and nothing else.
1504
+ * yet", so a route guard waits on that and nothing else. `'unavailable'` keeps
1505
+ * a session candidate for retry and can have a null profile; redirect only on
1506
+ * `'unauthenticated'`.
1470
1507
  */
1471
1508
  status: AuthState<T>['status']
1472
1509
  state: AuthState<T>
@@ -1486,8 +1523,10 @@ interface UseAuthResult<
1486
1523
  * `authenticated` read signed out.
1487
1524
  */
1488
1525
  setSession: AuthClient<T>['setSession']
1526
+ /** Whether a session is authenticated locally; this can be false while unavailable. */
1489
1527
  isAuthenticated: boolean
1490
1528
  isLoading: boolean
1529
+ /** The cached profile, which can be null while an unavailable candidate is retained. */
1491
1530
  user: T | null
1492
1531
  }
1493
1532
  ```
@@ -2011,6 +2050,12 @@ function useAuthClient<
2011
2050
 
2012
2051
  Hook for authentication state and actions.
2013
2052
 
2053
+ `status === 'loading'` is undecided: wait for it and redirect only on
2054
+ `status === 'unauthenticated'`. `'unavailable'` keeps the session candidate
2055
+ for retry, but `isAuthenticated` can be false and `user` can be null while a
2056
+ token/cookie-only restore has no cached profile. A restore failure other than
2057
+ 401/403 never signs out.
2058
+
2014
2059
  Pass the app's user type — the SAME type used for the auth client and its
2015
2060
  config (`createJWTAuthClient<MyUser>`, `AuthClientConfig<MyUser>`) — so
2016
2061
  `user` and `setSession`/`setUser` are typed for the pages:
@@ -2214,7 +2259,7 @@ function useForm<T extends FormValuesConstraint<T> = Record<string, unknown>>(
2214
2259
  ): UseFormResult<T>
2215
2260
  ```
2216
2261
 
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.
2262
+ - `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
2263
 
2219
2264
  **Returns:** Form state and methods
2220
2265
 
@@ -3261,6 +3306,17 @@ const useAsyncState: <T>(initialState: T) => [T, AsyncSetState<T>, AsyncExtendSt
3261
3306
 
3262
3307
  Hook that wraps an async function with state tracking.
3263
3308
 
3309
+ For one UI action, pass `state.status === 'pending'` to `Button loading`
3310
+ from `@molecule/app-ui-react` (or to a native control's `disabled` prop).
3311
+ The control then refuses input while the request is pending and re-enables
3312
+ when the tracked call settles. Handle the returned promise's rejection;
3313
+ the hook also records the failure in `state.error`.
3314
+
3315
+ This hook tracks the latest call: it does not serialize invocations or
3316
+ cancel their network requests. An explicit reset/cancel changes the tracked
3317
+ state, so it is not proof that an outstanding request has stopped. Use an
3318
+ app-owned queue when every click must be accepted in order.
3319
+
3264
3320
  ```typescript
3265
3321
  const usePromise: <T extends (...args: any[]) => Promise<any>>(
3266
3322
  asyncFn: T,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-react-native",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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.0",
36
+ "@molecule/app-react": "3.1.0",
37
37
  "@types/node": "26.6.5",
38
38
  "react": "19.3.1",
39
39
  "typescript": "6.0.3",