@molecule/app-bonds-default-react 2.0.0 → 3.0.0

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
@@ -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-24T08:39:00.105Z
6
+ Generated: 2026-10-25T20:03:32.757Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-bonds-default-react
@@ -113,7 +113,9 @@ this same wiring. Pass `stripApiPrefix: true` when the app uses
113
113
  when the API reads another scheme than `Bearer`: it is an option of THIS
114
114
  constructor (`createDefaultHttpClientWithAuthBearer({ authScheme: 'Token', … })`,
115
115
  or `createFetchClient({ baseURL, authScheme: 'Token' })` directly), never a
116
- property of the client it returns.
116
+ property of the client it returns. The client is built with the same scheme, so
117
+ a token the client also holds — the one a default auth bond's token sync gives
118
+ it — is sent with that scheme too, not as `Bearer`.
117
119
 
118
120
  ```typescript
119
121
  function createDefaultHttpClientWithAuthBearer(opts: {
@@ -128,19 +130,16 @@ function createDefaultHttpClientWithAuthBearer(opts: {
128
130
  #### `createDefaultAuthClient(authConfig)`
129
131
 
130
132
  Builds the default JWT auth client + wires it into `@molecule/app-auth`.
131
-
132
- Replaces the 16-line per-app `bonds/auth-default.ts` that 93 fleet
133
- apps shipped byte-identically. Apps pass their own `authConfig`
134
- (which lives in `src/config.ts`).
133
+ Apps pass their own `authConfig` (which lives in `src/config.ts`).
135
134
 
136
135
  `TUser` is the profile the app's pages read, and may be any JSON object type —
137
- including a profile with NO `id` (`{ username, email, bio, image }`), exactly
138
- like the factory beneath this wrapper. Never add a synthetic `id` to satisfy a
139
- type: nothing in the client reads it, and the fabricated value then travels into
140
- storage, `getUser()` and every page. Omit the type parameter and the default is
141
- `UserProfile`, whose `id: string` stays required — the profile is never inferred
142
- from `authConfig` (no member of it, and not from an annotated config variable),
143
- so an untyped wrapper call keeps the default contract.
136
+ including a profile with NO `id` (`{ username, email, bio, image }`), as in the
137
+ factory it wraps. Never add a synthetic `id` to satisfy a type: nothing in the
138
+ client reads it, and the fabricated value then travels into storage, `getUser()`
139
+ and every page. Omit the type parameter and the default is `UserProfile`, whose
140
+ `id: string` stays required — the profile is never inferred from `authConfig`
141
+ (not even from an annotated config variable), so an untyped wrapper call keeps
142
+ the default contract.
144
143
 
145
144
  Type the config with the SAME type (`const authConfig: AuthClientConfig<MyUser> = { … }`)
146
145
  so its `mapAuthResponse` is checked against `MyUser`, and name that type at the
@@ -151,8 +150,8 @@ argument — that is a 1.8 caller shape this major changes (see
151
150
  `AuthClientConfig`'s migration note in `@molecule/app-auth`).
152
151
 
153
152
  `TRegister` is the app's own registration fields (`{ username: string }` for a
154
-
155
- _(description truncated — read the full text in `node_modules/@molecule/app-bonds-default-react/dist/auth.d.ts`.)_
153
+ RealWorld-shaped API), passed on to `@molecule/app-auth`'s factory so
154
+ `authClient.register({ username, email, password })` type-checks with no cast.
156
155
 
157
156
  ```typescript
158
157
  function createDefaultAuthClient(authConfig: NoInfer<AuthClientConfigOptions<TUser>>): {
@@ -164,26 +163,28 @@ function createDefaultAuthClient(authConfig: NoInfer<AuthClientConfigOptions<TUs
164
163
  #### `createDefaultAuthClientWithHttpSync(authConfig)`
165
164
 
166
165
  Variant of `createDefaultAuthClient` that also keeps the bonded
167
- `@molecule/app-http` client's bearer token in sync with auth events
168
- (login / register / refresh / logout). Required by apps whose http
169
- client must carry the JWT on every request — without this wiring,
170
- authed endpoints return 401 after page reloads or token refresh.
166
+ `@molecule/app-http` client's bearer token in sync with the auth client's session.
167
+ Required by apps whose http client must carry the JWT on every request — without
168
+ this wiring, authed endpoints return 401 after page reloads or token refresh.
169
+
170
+ The sync is a LIVE read: `@molecule/app-http`'s `setAuthTokenSource` registers the auth
171
+ client's token, and every client the app bonds — one bonded after this setup too (the
172
+ scaffold wires auth BEFORE http, so a token copied into the client present at setup was
173
+ never sent) — reads it on each request. A session adopted with no
174
+ auth event (`useAuth().setSession()`, an OAuth callback's `setAccessToken()`, the same
175
+ profile under a new token) is therefore sent by the next request, not the previous
176
+ account's bearer. A transport without `setAuthTokenResolver` is followed through a request
177
+ interceptor (see `setAuthTokenSource` for which request first carries the change), and
178
+ auth events still push the token to it.
179
+
180
+ A cookie session (no body token) resolves to `null`, so the request carries NO
181
+ `Authorization` header — a kept bearer would answer as the PRIOR session.
182
+ Requests also wait for the session restore, so a page that fetches viewer data on mount
183
+ cannot race it (see {@link registerAuthGate}).
171
184
 
172
185
  The profile type is `createDefaultAuthClient`'s: a JSON object type of named
173
186
  fields (see `AuthProfileShape`), including an app profile with no `id`.
174
187
 
175
- Hydrates the HTTP client's token from the persisted auth state
176
- on setup, then listens for auth events to keep them aligned, and holds
177
- requests until the session restore settles so a page effect that fetches
178
- viewer-dependent data on mount cannot race it (see {@link registerAuthGate}).
179
-
180
- The hydrated token is delivered through `@molecule/app-http`'s token source, not
181
- through a client read at setup: the scaffold wires auth BEFORE http, so a client
182
- read here is the unbonded fallback and the restored session's token would land on a
183
- client nothing sends through (xr-molecule-flash-m156 — a reload sent every request
184
- with no `Authorization` header). The source is applied to the client WHENEVER it is
185
- bonded, so setup order no longer decides whether the first request is authorized.
186
-
187
188
  ```typescript
188
189
  function createDefaultAuthClientWithHttpSync(authConfig: NoInfer<AuthClientConfigOptions<TUser>>): {
189
190
  authClient: AuthClient<TUser, TRegister>
@@ -196,15 +197,18 @@ function createDefaultAuthClientWithHttpSync(authConfig: NoInfer<AuthClientConfi
196
197
  Most aggressive variant — bonds a fetch-based HTTP client with the
197
198
  given `baseURL` (so molecule pkg `useGet('/path')` calls hit
198
199
  `${baseURL}/path` instead of the SPA's catch-all route), AND keeps
199
- its bearer token in sync with auth events.
200
+ its bearer token in sync with the auth client's session, read on every
201
+ request: a session adopted with no auth event (`setSession()`,
202
+ `setAccessToken()`) is sent by the next request. The token uses the auth
203
+ config's `authScheme` (default `Bearer`); a client bonded after this setup
204
+ gets the same source and auth events.
200
205
 
201
206
  The auth client restores a stored token AND user SYNCHRONOUSLY, so the first
202
207
  render is the signed-in UI — do not gate a protected page (or a provider of
203
208
  yours) on a current-user request. This bond registers `whenRestored()` as the
204
209
  HTTP gate, so requests still wait for the background restore/verification to
205
210
  settle; only `useAuth().status === 'loading'` means "no decision yet"
206
- (xr-molecule-flash-m177: a page that waited on its own `/me` paid a round trip
207
- on every navigation).
211
+ (a page that waits on its own `/me` pays a round trip on every navigation).
208
212
 
209
213
  Used by apps where molecule packages render pricing / billing /
210
214
  other authed JSON-fetching screens that need both behaviors.
@@ -282,7 +286,7 @@ WITHOUT this, `createStore()` throws
282
286
  `@molecule/app-state: No provider set. Call setProvider() first` — nothing
283
287
  else in a scaffolded app wires the `state` bond, so the documented
284
288
  `createStore()` + `useStore()` pattern failed at the first render and every
285
- page came up blank while `tsc` passed (X1 flash-m65).
289
+ page came up blank while `tsc` passed.
286
290
 
287
291
  ```typescript
288
292
  function setupAppStateSimple(): void
@@ -343,7 +347,7 @@ function setupAllDefaultBonds(): void
343
347
 
344
348
  Peer dependencies:
345
349
 
346
- - `@molecule/app-auth` ^2.0.0
350
+ - `@molecule/app-auth` ^3.0.0
347
351
  - `@molecule/app-charts` ^1.0.1
348
352
  - `@molecule/app-charts-chartjs` ^1.0.1
349
353
  - `@molecule/app-client-search` ^1.0.0
@@ -356,7 +360,7 @@ Peer dependencies:
356
360
  - `@molecule/app-drag-drop-dndkit` ^1.0.1
357
361
  - `@molecule/app-fonts` ^1.0.1
358
362
  - `@molecule/app-fonts-arimo` ^1.0.1
359
- - `@molecule/app-http` ^1.0.1
363
+ - `@molecule/app-http` ^1.5.0
360
364
  - `@molecule/app-icons` ^1.0.1
361
365
  - `@molecule/app-icons-molecule` ^1.0.1
362
366
  - `@molecule/app-keyboard-shortcuts` ^1.0.1
package/dist/auth.d.ts CHANGED
@@ -30,7 +30,9 @@ export declare function createDefaultHttpClient(baseURL: string): {
30
30
  * when the API reads another scheme than `Bearer`: it is an option of THIS
31
31
  * constructor (`createDefaultHttpClientWithAuthBearer({ authScheme: 'Token', … })`,
32
32
  * or `createFetchClient({ baseURL, authScheme: 'Token' })` directly), never a
33
- * property of the client it returns.
33
+ * property of the client it returns. The client is built with the same scheme, so
34
+ * a token the client also holds — the one a default auth bond's token sync gives
35
+ * it — is sent with that scheme too, not as `Bearer`.
34
36
  */
35
37
  export declare function createDefaultHttpClientWithAuthBearer(opts: {
36
38
  baseURL: string;
@@ -51,19 +53,16 @@ export declare function createDefaultHttpClientWithAuthBearer(opts: {
51
53
  };
52
54
  /**
53
55
  * Builds the default JWT auth client + wires it into `@molecule/app-auth`.
54
- *
55
- * Replaces the 16-line per-app `bonds/auth-default.ts` that 93 fleet
56
- * apps shipped byte-identically. Apps pass their own `authConfig`
57
- * (which lives in `src/config.ts`).
56
+ * Apps pass their own `authConfig` (which lives in `src/config.ts`).
58
57
  *
59
58
  * `TUser` is the profile the app's pages read, and may be any JSON object type —
60
- * including a profile with NO `id` (`{ username, email, bio, image }`), exactly
61
- * like the factory beneath this wrapper. Never add a synthetic `id` to satisfy a
62
- * type: nothing in the client reads it, and the fabricated value then travels into
63
- * storage, `getUser()` and every page. Omit the type parameter and the default is
64
- * `UserProfile`, whose `id: string` stays required — the profile is never inferred
65
- * from `authConfig` (no member of it, and not from an annotated config variable),
66
- * so an untyped wrapper call keeps the default contract.
59
+ * including a profile with NO `id` (`{ username, email, bio, image }`), as in the
60
+ * factory it wraps. Never add a synthetic `id` to satisfy a type: nothing in the
61
+ * client reads it, and the fabricated value then travels into storage, `getUser()`
62
+ * and every page. Omit the type parameter and the default is `UserProfile`, whose
63
+ * `id: string` stays required — the profile is never inferred from `authConfig`
64
+ * (not even from an annotated config variable), so an untyped wrapper call keeps
65
+ * the default contract.
67
66
  *
68
67
  * Type the config with the SAME type (`const authConfig: AuthClientConfig<MyUser> = { … }`)
69
68
  * so its `mapAuthResponse` is checked against `MyUser`, and name that type at the
@@ -93,25 +92,27 @@ export declare function createDefaultAuthClient<TUser extends AuthProfileConstra
93
92
  };
94
93
  /**
95
94
  * Variant of `createDefaultAuthClient` that also keeps the bonded
96
- * `@molecule/app-http` client's bearer token in sync with auth events
97
- * (login / register / refresh / logout). Required by apps whose http
98
- * client must carry the JWT on every request — without this wiring,
99
- * authed endpoints return 401 after page reloads or token refresh.
95
+ * `@molecule/app-http` client's bearer token in sync with the auth client's session.
96
+ * Required by apps whose http client must carry the JWT on every request — without
97
+ * this wiring, authed endpoints return 401 after page reloads or token refresh.
98
+ *
99
+ * The sync is a LIVE read: `@molecule/app-http`'s `setAuthTokenSource` registers the auth
100
+ * client's token, and every client the app bonds — one bonded after this setup too (the
101
+ * scaffold wires auth BEFORE http, so a token copied into the client present at setup was
102
+ * never sent) — reads it on each request. A session adopted with no
103
+ * auth event (`useAuth().setSession()`, an OAuth callback's `setAccessToken()`, the same
104
+ * profile under a new token) is therefore sent by the next request, not the previous
105
+ * account's bearer. A transport without `setAuthTokenResolver` is followed through a request
106
+ * interceptor (see `setAuthTokenSource` for which request first carries the change), and
107
+ * auth events still push the token to it.
108
+ *
109
+ * A cookie session (no body token) resolves to `null`, so the request carries NO
110
+ * `Authorization` header — a kept bearer would answer as the PRIOR session.
111
+ * Requests also wait for the session restore, so a page that fetches viewer data on mount
112
+ * cannot race it (see {@link registerAuthGate}).
100
113
  *
101
114
  * The profile type is `createDefaultAuthClient`'s: a JSON object type of named
102
115
  * fields (see `AuthProfileShape`), including an app profile with no `id`.
103
- *
104
- * Hydrates the HTTP client's token from the persisted auth state
105
- * on setup, then listens for auth events to keep them aligned, and holds
106
- * requests until the session restore settles so a page effect that fetches
107
- * viewer-dependent data on mount cannot race it (see {@link registerAuthGate}).
108
- *
109
- * The hydrated token is delivered through `@molecule/app-http`'s token source, not
110
- * through a client read at setup: the scaffold wires auth BEFORE http, so a client
111
- * read here is the unbonded fallback and the restored session's token would land on a
112
- * client nothing sends through (xr-molecule-flash-m156 — a reload sent every request
113
- * with no `Authorization` header). The source is applied to the client WHENEVER it is
114
- * bonded, so setup order no longer decides whether the first request is authorized.
115
116
  */
116
117
  export declare function createDefaultAuthClientWithHttpSync<TUser extends AuthProfileConstraint<TUser> = UserProfile, TRegister extends RegisterExtraShape<TRegister> = Record<never, never>>(authConfig: NoInfer<AuthClientConfigOptions<TUser>>): {
117
118
  authClient: AuthClient<TUser, TRegister>;
@@ -121,15 +122,18 @@ export declare function createDefaultAuthClientWithHttpSync<TUser extends AuthPr
121
122
  * Most aggressive variant — bonds a fetch-based HTTP client with the
122
123
  * given `baseURL` (so molecule pkg `useGet('/path')` calls hit
123
124
  * `${baseURL}/path` instead of the SPA's catch-all route), AND keeps
124
- * its bearer token in sync with auth events.
125
+ * its bearer token in sync with the auth client's session, read on every
126
+ * request: a session adopted with no auth event (`setSession()`,
127
+ * `setAccessToken()`) is sent by the next request. The token uses the auth
128
+ * config's `authScheme` (default `Bearer`); a client bonded after this setup
129
+ * gets the same source and auth events.
125
130
  *
126
131
  * The auth client restores a stored token AND user SYNCHRONOUSLY, so the first
127
132
  * render is the signed-in UI — do not gate a protected page (or a provider of
128
133
  * yours) on a current-user request. This bond registers `whenRestored()` as the
129
134
  * HTTP gate, so requests still wait for the background restore/verification to
130
135
  * settle; only `useAuth().status === 'loading'` means "no decision yet"
131
- * (xr-molecule-flash-m177: a page that waited on its own `/me` paid a round trip
132
- * on every navigation).
136
+ * (a page that waits on its own `/me` pays a round trip on every navigation).
133
137
  *
134
138
  * Used by apps where molecule packages render pricing / billing /
135
139
  * other authed JSON-fetching screens that need both behaviors.
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,uBAAuB,EAEvB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAuCpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IACxD,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAMA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG;IACF,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAoBA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,GAClD;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAMA;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,GAClD;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CA4BA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sCAAsC,CACpD,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,EACnD,kBAAkB,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACjE;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CA2BA"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,uBAAuB,EAEvB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAsCpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IACxD,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAMA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG;IACF,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAuBA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,GAClD;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAMA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,GAClD;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAkCA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sCAAsC,CACpD,KAAK,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG,WAAW,EACxD,SAAS,SAAS,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAEtE,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,EACnD,kBAAkB,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACjE;IACD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACxC,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B,CAmCA"}
package/dist/auth.js CHANGED
@@ -7,9 +7,8 @@ import { createFetchClient, getClient as getHttpClient, setAuthGate, setAuthToke
7
7
  * flag, a feed, a `favorited` state) sends its request before `initialize()` has
8
8
  * restored the session, so it leaves with no `Authorization` header and the server
9
9
  * answers for an anonymous viewer. Localhost usually hides it — the restore wins
10
- * the race against the first effect there — and the deployed URL exposes it, which
11
- * is how X1 xr-molecule-flash-m95 spent ~25 min treating a "Follow" button on a
12
- * followed user as database leftovers.
10
+ * the race against the first effect there — and the deployed URL exposes it, where a
11
+ * "Follow" button on a followed user reads as stale database state.
13
12
  *
14
13
  * `whenRestored()` resolves immediately when no restore is in flight, so a request
15
14
  * made after boot never waits at all; it holds only while the restore is actually
@@ -62,13 +61,18 @@ export function createDefaultHttpClient(baseURL) {
62
61
  * when the API reads another scheme than `Bearer`: it is an option of THIS
63
62
  * constructor (`createDefaultHttpClientWithAuthBearer({ authScheme: 'Token', … })`,
64
63
  * or `createFetchClient({ baseURL, authScheme: 'Token' })` directly), never a
65
- * property of the client it returns.
64
+ * property of the client it returns. The client is built with the same scheme, so
65
+ * a token the client also holds — the one a default auth bond's token sync gives
66
+ * it — is sent with that scheme too, not as `Bearer`.
66
67
  */
67
68
  export function createDefaultHttpClientWithAuthBearer(opts) {
68
69
  const authScheme = opts.authScheme ?? 'Bearer';
69
70
  const httpClient = createFetchClient({
70
71
  baseURL: opts.baseURL,
71
72
  withCredentials: opts.withCredentials,
73
+ // The client's own token (a default auth bond's token sync) overrides the interceptor's
74
+ // header, so it must carry the same scheme.
75
+ authScheme,
72
76
  });
73
77
  httpClient.addRequestInterceptor((config) => {
74
78
  if (opts.stripApiPrefix && typeof config.url === 'string' && config.url.startsWith('/api/')) {
@@ -87,19 +91,16 @@ export function createDefaultHttpClientWithAuthBearer(opts) {
87
91
  }
88
92
  /**
89
93
  * Builds the default JWT auth client + wires it into `@molecule/app-auth`.
90
- *
91
- * Replaces the 16-line per-app `bonds/auth-default.ts` that 93 fleet
92
- * apps shipped byte-identically. Apps pass their own `authConfig`
93
- * (which lives in `src/config.ts`).
94
+ * Apps pass their own `authConfig` (which lives in `src/config.ts`).
94
95
  *
95
96
  * `TUser` is the profile the app's pages read, and may be any JSON object type —
96
- * including a profile with NO `id` (`{ username, email, bio, image }`), exactly
97
- * like the factory beneath this wrapper. Never add a synthetic `id` to satisfy a
98
- * type: nothing in the client reads it, and the fabricated value then travels into
99
- * storage, `getUser()` and every page. Omit the type parameter and the default is
100
- * `UserProfile`, whose `id: string` stays required — the profile is never inferred
101
- * from `authConfig` (no member of it, and not from an annotated config variable),
102
- * so an untyped wrapper call keeps the default contract.
97
+ * including a profile with NO `id` (`{ username, email, bio, image }`), as in the
98
+ * factory it wraps. Never add a synthetic `id` to satisfy a type: nothing in the
99
+ * client reads it, and the fabricated value then travels into storage, `getUser()`
100
+ * and every page. Omit the type parameter and the default is `UserProfile`, whose
101
+ * `id: string` stays required — the profile is never inferred from `authConfig`
102
+ * (not even from an annotated config variable), so an untyped wrapper call keeps
103
+ * the default contract.
103
104
  *
104
105
  * Type the config with the SAME type (`const authConfig: AuthClientConfig<MyUser> = { … }`)
105
106
  * so its `mapAuthResponse` is checked against `MyUser`, and name that type at the
@@ -132,25 +133,27 @@ export function createDefaultAuthClient(authConfig) {
132
133
  }
133
134
  /**
134
135
  * Variant of `createDefaultAuthClient` that also keeps the bonded
135
- * `@molecule/app-http` client's bearer token in sync with auth events
136
- * (login / register / refresh / logout). Required by apps whose http
137
- * client must carry the JWT on every request — without this wiring,
138
- * authed endpoints return 401 after page reloads or token refresh.
136
+ * `@molecule/app-http` client's bearer token in sync with the auth client's session.
137
+ * Required by apps whose http client must carry the JWT on every request — without
138
+ * this wiring, authed endpoints return 401 after page reloads or token refresh.
139
+ *
140
+ * The sync is a LIVE read: `@molecule/app-http`'s `setAuthTokenSource` registers the auth
141
+ * client's token, and every client the app bonds — one bonded after this setup too (the
142
+ * scaffold wires auth BEFORE http, so a token copied into the client present at setup was
143
+ * never sent) — reads it on each request. A session adopted with no
144
+ * auth event (`useAuth().setSession()`, an OAuth callback's `setAccessToken()`, the same
145
+ * profile under a new token) is therefore sent by the next request, not the previous
146
+ * account's bearer. A transport without `setAuthTokenResolver` is followed through a request
147
+ * interceptor (see `setAuthTokenSource` for which request first carries the change), and
148
+ * auth events still push the token to it.
149
+ *
150
+ * A cookie session (no body token) resolves to `null`, so the request carries NO
151
+ * `Authorization` header — a kept bearer would answer as the PRIOR session.
152
+ * Requests also wait for the session restore, so a page that fetches viewer data on mount
153
+ * cannot race it (see {@link registerAuthGate}).
139
154
  *
140
155
  * The profile type is `createDefaultAuthClient`'s: a JSON object type of named
141
156
  * fields (see `AuthProfileShape`), including an app profile with no `id`.
142
- *
143
- * Hydrates the HTTP client's token from the persisted auth state
144
- * on setup, then listens for auth events to keep them aligned, and holds
145
- * requests until the session restore settles so a page effect that fetches
146
- * viewer-dependent data on mount cannot race it (see {@link registerAuthGate}).
147
- *
148
- * The hydrated token is delivered through `@molecule/app-http`'s token source, not
149
- * through a client read at setup: the scaffold wires auth BEFORE http, so a client
150
- * read here is the unbonded fallback and the restored session's token would land on a
151
- * client nothing sends through (xr-molecule-flash-m156 — a reload sent every request
152
- * with no `Authorization` header). The source is applied to the client WHENEVER it is
153
- * bonded, so setup order no longer decides whether the first request is authorized.
154
157
  */
155
158
  export function createDefaultAuthClientWithHttpSync(authConfig) {
156
159
  const authClient = createJWTAuthClient(authConfig);
@@ -164,7 +167,9 @@ export function createDefaultAuthClientWithHttpSync(authConfig) {
164
167
  // Resolve the HTTP client per sync, never once here. `setClient` runs this
165
168
  // source, so the token reaches the client the scaffold bonds later as well as
166
169
  // one already bonded; a client kept from this moment would be the throwaway
167
- // fallback the later `setHttpDefault()` replaces.
170
+ // fallback the later `setHttpDefault()` replaces. The client reads the source
171
+ // itself on every request, so a session adopted by `setSession()` (no auth
172
+ // event) or re-adopted for the same profile (no state change) is sent at once.
168
173
  setHttpAuthTokenSource(() => authClient.getAccessToken());
169
174
  authClient.addEventListener((event) => {
170
175
  if (event.type === 'login' || event.type === 'register' || event.type === 'refresh') {
@@ -173,6 +178,10 @@ export function createDefaultAuthClientWithHttpSync(authConfig) {
173
178
  // keeping a previously-set bearer here would pin the http client to the
174
179
  // PRIOR session (e.g. a guest's) — the server prefers the Authorization
175
180
  // header over the fresh cookie, so every request 401s as the old user.
181
+ //
182
+ // A client that implements `setAuthTokenResolver` already resolves the source
183
+ // per request; this push is what carries the same change to a transport that
184
+ // does not (and keeps `getAuthToken()` current there).
176
185
  getHttpClient().setAuthToken(authClient.getAccessToken());
177
186
  }
178
187
  else if (event.type === 'logout') {
@@ -186,15 +195,18 @@ export function createDefaultAuthClientWithHttpSync(authConfig) {
186
195
  * Most aggressive variant — bonds a fetch-based HTTP client with the
187
196
  * given `baseURL` (so molecule pkg `useGet('/path')` calls hit
188
197
  * `${baseURL}/path` instead of the SPA's catch-all route), AND keeps
189
- * its bearer token in sync with auth events.
198
+ * its bearer token in sync with the auth client's session, read on every
199
+ * request: a session adopted with no auth event (`setSession()`,
200
+ * `setAccessToken()`) is sent by the next request. The token uses the auth
201
+ * config's `authScheme` (default `Bearer`); a client bonded after this setup
202
+ * gets the same source and auth events.
190
203
  *
191
204
  * The auth client restores a stored token AND user SYNCHRONOUSLY, so the first
192
205
  * render is the signed-in UI — do not gate a protected page (or a provider of
193
206
  * yours) on a current-user request. This bond registers `whenRestored()` as the
194
207
  * HTTP gate, so requests still wait for the background restore/verification to
195
208
  * settle; only `useAuth().status === 'loading'` means "no decision yet"
196
- * (xr-molecule-flash-m177: a page that waited on its own `/me` paid a round trip
197
- * on every navigation).
209
+ * (a page that waits on its own `/me` pays a round trip on every navigation).
198
210
  *
199
211
  * Used by apps where molecule packages render pricing / billing /
200
212
  * other authed JSON-fetching screens that need both behaviors.
@@ -214,22 +226,30 @@ export function createDefaultAuthClientWithFetchClient(authConfig, fetchClientOp
214
226
  const httpClient = createFetchClient({
215
227
  baseURL: fetchClientOptions.baseURL,
216
228
  withCredentials: fetchClientOptions.withCredentials ?? true,
229
+ // The scheme the auth config names for this token (`Authorization: Token <jwt>`).
230
+ authScheme: authConfig.authScheme,
217
231
  });
218
232
  // Registered BEFORE the bond: `setHttpClient` applies the restored session's
219
233
  // token, and the same source reaches any client an app bonds later instead of
220
- // this one (xr-molecule-flash-m156 — a client captured at setup cannot).
234
+ // this one (xr-molecule-flash-m156 — a client captured at setup cannot). The
235
+ // client resolves the source on every request, so a session adopted by
236
+ // `setSession()` or `setAccessToken()` after this point is sent immediately.
221
237
  setHttpAuthTokenSource(() => authClient.getAccessToken());
222
238
  setHttpClient(httpClient);
223
239
  setClient(authClient);
224
240
  registerAuthGate(authClient);
225
241
  authClient.addEventListener((event) => {
242
+ // The client bonded NOW, resolved per event: an app that bonds its own client after
243
+ // this setup sends through that one, never through the client created above.
226
244
  if (event.type === 'login' || event.type === 'register' || event.type === 'refresh') {
227
245
  // Mirror the token exactly, including null — see the identical note in
228
246
  // createDefaultAuthClientWithHttpSync (stale bearer = stuck prior session).
229
- httpClient.setAuthToken(authClient.getAccessToken());
247
+ // The per-request read of the source above is the guarantee; this push keeps
248
+ // `getAuthToken()` current on a transport without `setAuthTokenResolver`.
249
+ getHttpClient().setAuthToken(authClient.getAccessToken());
230
250
  }
231
251
  else if (event.type === 'logout') {
232
- httpClient.setAuthToken(null);
252
+ getHttpClient().setAuthToken(null);
233
253
  }
234
254
  });
235
255
  },
package/dist/setup.d.ts CHANGED
@@ -29,7 +29,7 @@ export declare function setupAppRoutingReactRouter(): void;
29
29
  * `@molecule/app-state: No provider set. Call setProvider() first` — nothing
30
30
  * else in a scaffolded app wires the `state` bond, so the documented
31
31
  * `createStore()` + `useStore()` pattern failed at the first render and every
32
- * page came up blank while `tsc` passed (X1 flash-m65).
32
+ * page came up blank while `tsc` passed.
33
33
  */
34
34
  export declare function setupAppStateSimple(): void;
35
35
  /** Wires `@molecule/app-storage-localstorage` to `@molecule/app-storage`. */
package/dist/setup.js CHANGED
@@ -68,7 +68,7 @@ export function setupAppRoutingReactRouter() {
68
68
  * `@molecule/app-state: No provider set. Call setProvider() first` — nothing
69
69
  * else in a scaffolded app wires the `state` bond, so the documented
70
70
  * `createStore()` + `useStore()` pattern failed at the first render and every
71
- * page came up blank while `tsc` passed (X1 flash-m65).
71
+ * page came up blank while `tsc` passed.
72
72
  */
73
73
  export function setupAppStateSimple() {
74
74
  setState(defaultStateProvider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-bonds-default-react",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Default app-side bond wirings for React+Tailwind+react-router+localStorage+Arimo font+molecule icons. 5 setup functions consolidating 665 byte-identical per-app bond wiring files across the fleet.",
5
5
  "homepage": "https://www.molecule.dev/packages/app-bonds-default-react",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "test": "vitest run",
12
- "test:watch": "vitest"
12
+ "test:watch": "vitest",
13
+ "typecheck:tests": "tsc -p tsconfig.tests.json"
13
14
  },
14
15
  "exports": {
15
16
  ".": {
@@ -38,7 +39,7 @@
38
39
  "directory": "packages/app/infrastructure/bonds-default-react"
39
40
  },
40
41
  "devDependencies": {
41
- "@molecule/app-auth": "2.0.0",
42
+ "@molecule/app-auth": "3.0.0",
42
43
  "@molecule/app-charts-chartjs": "1.0.3",
43
44
  "@molecule/app-client-search": "1.0.3",
44
45
  "@molecule/app-client-search-minisearch": "1.0.4",
@@ -50,7 +51,7 @@
50
51
  "@molecule/app-drag-drop-dndkit": "1.0.3",
51
52
  "@molecule/app-fonts": "1.2.2",
52
53
  "@molecule/app-fonts-arimo": "1.0.5",
53
- "@molecule/app-http": "1.4.1",
54
+ "@molecule/app-http": "1.5.0",
54
55
  "@molecule/app-icons": "1.0.3",
55
56
  "@molecule/app-icons-molecule": "1.0.3",
56
57
  "@molecule/app-keyboard-shortcuts": "1.0.3",
@@ -59,7 +60,7 @@
59
60
  "@molecule/app-realtime": "1.0.4",
60
61
  "@molecule/app-realtime-socketio": "1.1.1",
61
62
  "@molecule/app-routing": "1.0.4",
62
- "@molecule/app-routing-react-router": "1.1.1",
63
+ "@molecule/app-routing-react-router": "1.1.2",
63
64
  "@molecule/app-state": "1.0.4",
64
65
  "@molecule/app-storage": "1.0.4",
65
66
  "@molecule/app-storage-localstorage": "1.0.3",
@@ -80,7 +81,7 @@
80
81
  "vitest": "4.1.12"
81
82
  },
82
83
  "peerDependencies": {
83
- "@molecule/app-auth": "^2.0.0",
84
+ "@molecule/app-auth": "^3.0.0",
84
85
  "@molecule/app-charts": "^1.0.1",
85
86
  "@molecule/app-charts-chartjs": "^1.0.1",
86
87
  "@molecule/app-client-search": "^1.0.0",
@@ -93,7 +94,7 @@
93
94
  "@molecule/app-drag-drop-dndkit": "^1.0.1",
94
95
  "@molecule/app-fonts": "^1.0.1",
95
96
  "@molecule/app-fonts-arimo": "^1.0.1",
96
- "@molecule/app-http": "^1.0.1",
97
+ "@molecule/app-http": "^1.5.0",
97
98
  "@molecule/app-icons": "^1.0.1",
98
99
  "@molecule/app-icons-molecule": "^1.0.1",
99
100
  "@molecule/app-keyboard-shortcuts": "^1.0.1",