@molecule/app-angular 1.0.1 → 1.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.
package/README.md CHANGED
@@ -3,21 +3,21 @@ 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-08-04T01:50:02.002Z
6
+ Generated: 2026-10-21T17:02:30.648Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-angular
10
10
 
11
- > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
- > It is written to be read by coding agents as much as by people, and is generated from this
13
- > package's source — edit `src/index.ts` JSDoc, not this file.
14
-
15
11
  Angular framework bindings for molecule.dev.
16
12
 
17
13
  Provides Angular-specific services and providers for all molecule
18
14
  core interfaces. This package enables the use of molecule's framework-agnostic
19
15
  interfaces with Angular's idioms (services, DI, RxJS observables, etc.).
20
16
 
17
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
18
+ > It is written to be read by coding agents as much as by people, and is generated from this
19
+ > package's source — edit `src/index.ts` JSDoc, not this file.
20
+
21
21
  ## Quick Start
22
22
 
23
23
  ```typescript
@@ -86,131 +86,17 @@ npm install @molecule/app-angular @angular/core @molecule/app-auth @molecule/app
86
86
 
87
87
  ### Interfaces
88
88
 
89
- #### `AsyncStateManager`
90
-
91
- Async state manager.
92
-
93
- ```typescript
94
- interface AsyncStateManager<T> {
95
- state$: Observable<T>
96
- getState: () => T
97
- setState: (value: T | ((prev: T) => T) | Promise<T | ((prev: T) => T)>) => void
98
- extendState: (
99
- partial:
100
- Partial<T> | ((prev: T) => Partial<T>) | Promise<Partial<T> | ((prev: T) => Partial<T>)>,
101
- ) => void
102
- destroy: () => void
103
- }
104
- ```
105
-
106
- #### `AuthClient`
107
-
108
- Auth client interface that all auth bond packages must implement.
109
- Provides login/logout/register flows, token management, profile
110
- updates, and auth state subscription.
111
-
112
- ```typescript
113
- interface AuthClient<T = UserProfile> {
114
- /**
115
- * Returns the current authentication state snapshot.
116
- */
117
- getState(): AuthState<T>
118
- /**
119
- * Returns whether the user is currently authenticated.
120
- */
121
- isAuthenticated(): boolean
122
- /**
123
- * Gets the current user.
124
- */
125
- getUser(): T | null
126
- /**
127
- * Updates the cached user object (state + persistent storage) without
128
- * hitting the network. Intended for local refreshes after a per-app
129
- * mutation (e.g., the user just PATCHed their own profile and the
130
- * server returned the canonical row). Does NOT change tokens.
131
- */
132
- setUser(user: T | null): void
133
- /**
134
- * Gets the current access token.
135
- */
136
- getAccessToken(): string | null
137
- /**
138
- * Stores the access token in the configured token storage adapter (in-memory
139
- * by default). Use this to seed the token after an out-of-band exchange (e.g.
140
- * the OAuth code→token redirect) instead of writing to `localStorage` directly,
141
- * which would violate the in-memory-default storage contract and make the bearer
142
- * token JS-readable (XSS-exfiltratable). Pass `null` to clear it.
143
- */
144
- setAccessToken(token: string | null): void
145
- /**
146
- * Gets the refresh token.
147
- */
148
- getRefreshToken(): string | null
149
- /**
150
- * Logs in with credentials.
151
- */
152
- login(credentials: LoginCredentials): Promise<AuthResult<T>>
153
- /**
154
- * Logs out the current user.
155
- */
156
- logout(): Promise<void>
157
- /**
158
- * Registers a new user.
159
- */
160
- register(data: RegisterData): Promise<AuthResult<T>>
161
- /**
162
- * Refreshes the access token.
163
- */
164
- refresh(): Promise<AuthResult<T>>
165
- /**
166
- * Requests a password reset.
167
- */
168
- requestPasswordReset(data: PasswordResetRequest): Promise<void>
169
- /**
170
- * Confirms a password reset.
171
- */
172
- confirmPasswordReset(data: PasswordResetConfirm): Promise<void>
173
- /**
174
- * Updates the current user's profile.
175
- */
176
- updateProfile(data: Partial<T>): Promise<T>
177
- /**
178
- * Changes the current user's password.
179
- */
180
- changePassword(oldPassword: string, newPassword: string): Promise<void>
181
- /**
182
- * Initializes auth state (e.g., from stored tokens).
183
- */
184
- initialize(): Promise<void>
185
- /**
186
- * Subscribes to auth state changes.
187
- */
188
- subscribe(callback: (state: AuthState<T>) => void): () => void
189
- /**
190
- * Subscribes to auth state changes (alias for subscribe).
191
- */
192
- onAuthChange(callback: (state: AuthState<T>) => void): () => void
193
- /**
194
- * Gets the current access token (alias for getAccessToken).
195
- */
196
- getToken?(): string | null
197
- /**
198
- * Adds an auth event listener.
199
- */
200
- addEventListener(listener: AuthEventListener): () => void
201
- /**
202
- * Destroys the auth client.
203
- */
204
- destroy(): void
205
- }
206
- ```
207
-
208
89
  #### `AuthState`
209
90
 
210
91
  Reactive authentication state snapshot (initialized, authenticated, user, loading, and error).
211
92
 
212
93
  ```typescript
213
94
  interface AuthState<T = UserProfile> {
95
+ /**
96
+ * The session's state as one value — render from this instead of combining
97
+ * `initialized`/`authenticated`/`loading`. See {@link AuthStatus}.
98
+ */
99
+ status: AuthStatus
214
100
  /**
215
101
  * Whether auth state has been initialized.
216
102
  */
@@ -234,49 +120,176 @@ interface AuthState<T = UserProfile> {
234
120
  }
235
121
  ```
236
122
 
237
- #### `CapacitorAppManager`
238
-
239
- Capacitor app manager interface.
240
-
241
- ```typescript
242
- interface CapacitorAppManager {
243
- state$: Observable<CapacitorAppState>
244
- ready$: Observable<boolean>
245
- initialize: () => Promise<void>
246
- destroy: () => void
247
- }
248
- ```
249
-
250
- #### `ChangePasswordStateManager`
251
-
252
- Change password state manager.
253
-
254
- ```typescript
255
- interface ChangePasswordStateManager {
256
- state$: Observable<PromiseState<void>>
257
- getState: () => PromiseState<void>
258
- changePassword: (oldPassword: string, newPassword: string) => Promise<void>
259
- reset: () => void
260
- destroy: () => void
261
- }
262
- ```
263
-
264
- #### `DeviceService`
123
+ #### `AuthClient`
265
124
 
266
- Device service interface.
125
+ Auth client interface that all auth bond packages must implement.
126
+ Provides login/logout/register flows, token management, profile
127
+ updates, and auth state subscription.
267
128
 
268
129
  ```typescript
269
- interface DeviceService {
270
- deviceInfo: DeviceInfo
271
- screenInfo: ScreenInfo
272
- hardwareInfo: HardwareInfo
273
- featureSupport: FeatureSupport
274
- supports: (feature: keyof FeatureSupport) => boolean
275
- isOnline: () => boolean
276
- isStandalone: () => boolean
277
- language: string
278
- languages: string[]
279
- }
130
+ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegister> = Record<never, never>> {
131
+ /**
132
+ * Returns the current authentication state snapshot.
133
+ *
134
+ * READING the state starts the client's one session restore when nobody has called
135
+ * {@link initialize} yet — lazily and exactly once, sharing the promise an explicit
136
+ * `initialize()` returns. A hook (`useAuth()`), a subscriber, `isAuthenticated()` and
137
+ * `getUser()` are reads too, so a client whose entry file dropped the template's
138
+ * `authClient.initialize()` line still restores instead of reporting `'loading'`
139
+ * forever.
140
+ */
141
+ getState(): AuthState<T>;
142
+ /**
143
+ * Returns whether the user is currently authenticated.
144
+ */
145
+ isAuthenticated(): boolean;
146
+ /**
147
+ * Gets the current user.
148
+ */
149
+ getUser(): T | null;
150
+ /**
151
+ * Updates the cached user object (state + persistent storage) without
152
+ * hitting the network. Intended for local refreshes after a per-app
153
+ * mutation (e.g., the user just PATCHed their own profile and the
154
+ * server returned the canonical row). Does NOT change tokens.
155
+ *
156
+ * This does not sign the client in: `authenticated` and `status` are
157
+ * unchanged, so an anonymous client stays anonymous. Use
158
+ * {@link login} / {@link register} for a credential exchange, or
159
+ * {@link setSession} when the token was issued outside the client.
160
+ */
161
+ setUser(user: T | null): void;
162
+ /**
163
+ * Gets the current access token.
164
+ */
165
+ getAccessToken(): string | null;
166
+ /**
167
+ * Stores the access token in the configured token storage adapter (in a
168
+ * browser, Web Storage; `'memory'` where no browser store exists). Use this
169
+ * to seed the token after an out-of-band exchange (e.g. the OAuth
170
+ * code→token redirect) instead of writing to `localStorage` directly,
171
+ * which would violate the storage contract and make a stored bearer token
172
+ * JS-readable (XSS-exfiltratable). Pass `null` to clear it.
173
+ *
174
+ * This changes token storage only; it does not sign the client in
175
+ * (`authenticated` and `status` are unchanged). Use {@link login} /
176
+ * {@link register} for a credential exchange, or call {@link setSession}
177
+ * with the token and the user to establish an already-issued session.
178
+ */
179
+ setAccessToken(token: string | null): void;
180
+ /**
181
+ * Adopts a session an out-of-band exchange already established: stores the
182
+ * access token (and the refresh token when given), caches the user, and
183
+ * sets the COMPLETE state — `initialized`, `authenticated`, `status:
184
+ * 'authenticated'`, no error — in one update, with one subscriber
185
+ * notification. Every hook reading the shared client sees the user as
186
+ * signed in immediately.
187
+ *
188
+ * This is the way to seed an OAuth/custom sign-in. Calling
189
+ * {@link setAccessToken} and {@link setUser} instead does NOT sign the
190
+ * client in.
191
+ */
192
+ setSession(session: AuthSessionSeed<T>): void;
193
+ /**
194
+ * Gets the refresh token.
195
+ */
196
+ getRefreshToken(): string | null;
197
+ /**
198
+ * Logs in with credentials.
199
+ */
200
+ login(credentials: LoginCredentials): Promise<AuthResult<T>>;
201
+ /**
202
+ * Logs out the current user.
203
+ */
204
+ logout(): Promise<void>;
205
+ /**
206
+ * Registers a new user. Accepts the base fields plus the app's own fields
207
+ * (`TRegister`) at the top level — see {@link RegisterData}.
208
+ */
209
+ register(data: RegisterData<TRegister>): Promise<AuthResult<T>>;
210
+ /**
211
+ * Refreshes the access token.
212
+ */
213
+ refresh(): Promise<AuthResult<T>>;
214
+ /**
215
+ * Requests a password reset.
216
+ */
217
+ requestPasswordReset(data: PasswordResetRequest): Promise<void>;
218
+ /**
219
+ * Confirms a password reset.
220
+ */
221
+ confirmPasswordReset(data: PasswordResetConfirm): Promise<void>;
222
+ /**
223
+ * Updates the current user's profile.
224
+ */
225
+ updateProfile(data: Partial<T>): Promise<T>;
226
+ /**
227
+ * Changes the current user's password.
228
+ */
229
+ changePassword(oldPassword: string, newPassword: string): Promise<void>;
230
+ /**
231
+ * Initializes auth state (e.g., from stored tokens).
232
+ *
233
+ * LAZY and IDEMPOTENT: the first read of the state ({@link getState},
234
+ * {@link subscribe}, `useAuth()`) starts this same restore when nobody has called
235
+ * `initialize()`, and every later call shares the one promise instead of sending a
236
+ * second request. An app that keeps the template's explicit call behaves exactly as
237
+ * before; an app that loses that line still restores its session.
238
+ *
239
+ * With a stored token AND a stored user it marks the session `'authenticated'`
240
+ * SYNCHRONOUSLY — before its first `await` — so the first render is the
241
+ * signed-in UI and no page waits on a verification round trip. Any check
242
+ * (a refresh, the cookie-restore probe) settles behind that render, and only an
243
+ * explicit 401/403 ends the session; a network/5xx/404 failure keeps it and
244
+ * reports `'unavailable'`. With no stored user the client stays `'loading'`
245
+ * until the cookie restore decides.
246
+ *
247
+ * Do not put this call (or a `/me` request of your own) in front of the first
248
+ * render, and do not render a loading screen over a session it just restored:
249
+ * `whenRestored()` already resolves as soon as the restore has settled, and the
250
+ * HTTP gate built on it holds requests for exactly that window.
251
+ */
252
+ initialize(): Promise<void>;
253
+ /**
254
+ * Re-checks a restored session against the server, in the background — the
255
+ * boot-time validation an app with a stored token needs when a revoked or
256
+ * expired token must sign the user out. Call it AFTER the first render
257
+ * (`initialize()` restores synchronously from storage; this costs one request).
258
+ *
259
+ * - The server confirms the user: the session is refreshed and {@link AuthState.status}
260
+ * stays `'authenticated'`.
261
+ * - The server answers 401 or 403: the token is dead — the stored session is
262
+ * cleared and the status becomes `'unauthenticated'`.
263
+ * - The server cannot confirm the session — a network failure, a 5xx, or a 404
264
+ * from a misconfigured {@link AuthClientConfig.currentUserEndpoint}: the
265
+ * session (token AND user) is KEPT and the status becomes `'unavailable'`, so
266
+ * the app can retry instead of signing the user out over a bad connection or a
267
+ * route that does not exist. The 404 error names `currentUserEndpoint`.
268
+ *
269
+ * Do not hand-write this beside the client: a parallel token/user store for a
270
+ * "validating"/"unavailable" flag is the race this method removes.
271
+ *
272
+ * Optional: a provider with no restorable session has nothing to validate.
273
+ */
274
+ validateSession?(): Promise<void>;
275
+ /**
276
+ * Resolves when the session restore started by `initialize()` has settled — the point
277
+ * after which the client knows whether this visitor is signed in.
278
+ *
279
+ * Resolves IMMEDIATELY when no restore is in flight: before `initialize()` is ever
280
+ * called, and after it has finished. A restore that failed resolves too (it settled the
281
+ * question), so an HTTP client can await this before every request without risking one
282
+ * held forever. That is what makes the fetch-before-restore race impossible: a page that
283
+ * fetches viewer-dependent data on mount waits here instead of going out anonymously
284
+ * (see `setAuthGate` in `@molecule/app-http`).
285
+ *
286
+ * Optional: a provider that restores no session has nothing to wait for. Without it, an
287
+ * HTTP client bonded to that provider sends first-paint requests unauthenticated.
288
+ *
289
+ * A provider whose restore sends a request THROUGH the gated HTTP client must send that
290
+ * request with `skipAuthGate: true` (`RequestConfig` in `@molecule/app-http`): the gate
291
+ * waits on this promise, so holding the restore's own request makes the gate wait on
292
+ // … truncated — the full declaration is in node_modules/@molecule/app-angular/dist
280
293
  ```
281
294
 
282
295
  #### `FormController`
@@ -431,7 +444,8 @@ interface HttpClient {
431
444
  */
432
445
  defaultHeaders: Record<string, string>
433
446
  /**
434
- * Makes a generic HTTP request.
447
+ * Makes a generic HTTP request. `data` is a field of THIS config object — the same body
448
+ * `post(url, data, config)` takes as its second argument.
435
449
  */
436
450
  request<T = unknown>(config: FullRequestConfig): Promise<HttpResponse<T>>
437
451
  /**
@@ -439,15 +453,19 @@ interface HttpClient {
439
453
  */
440
454
  get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
441
455
  /**
442
- * Makes a POST request.
456
+ * Makes a POST request. `data` is the request BODY, not a config object: the wire body is
457
+ * `data` itself, so `post(url, { data: body })` sends `{"data":body}` — pass `body` directly.
458
+ * A config (headers, params) is the THIRD argument.
443
459
  */
444
460
  post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
445
461
  /**
446
- * Makes a PUT request.
462
+ * Makes a PUT request. `data` is the request BODY (second argument) — never `{ data: body }`,
463
+ * which sends `{"data":body}`; a config is the THIRD argument.
447
464
  */
448
465
  put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
449
466
  /**
450
- * Makes a PATCH request.
467
+ * Makes a PATCH request. `data` is the request BODY (second argument) — never `{ data: body }`,
468
+ * which sends `{"data":body}`; a config is the THIRD argument.
451
469
  */
452
470
  patch<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
453
471
  /**
@@ -486,18 +504,6 @@ interface HttpClient {
486
504
  }
487
505
  ```
488
506
 
489
- #### `HttpState`
490
-
491
- State for HTTP requests.
492
-
493
- ```typescript
494
- interface HttpState<T> {
495
- data: T | null
496
- loading: boolean
497
- error: Error | null
498
- }
499
- ```
500
-
501
507
  #### `I18nProvider`
502
508
 
503
509
  i18n provider interface.
@@ -756,244 +762,55 @@ interface LoggerProvider {
756
762
  }
757
763
  ```
758
764
 
759
- #### `LoginStateManager`
760
-
761
- Login state manager.
762
-
763
- ```typescript
764
- interface LoginStateManager<T = unknown> {
765
- state$: Observable<PromiseState<AuthResult<T>>>
766
- getState: () => PromiseState<AuthResult<T>>
767
- login: (credentials: LoginCredentials) => Promise<AuthResult<T>>
768
- reset: () => void
769
- destroy: () => void
770
- }
771
- ```
772
-
773
- #### `MoleculeModuleConfig`
765
+ #### `RouteLocation`
774
766
 
775
- Configuration for molecule Angular module.
767
+ Current URL decomposed into pathname, search string, hash, navigation state, and unique key.
776
768
 
777
769
  ```typescript
778
- interface MoleculeModuleConfig {
779
- state?: StateProvider
780
- auth?: AuthClient<unknown>
781
- theme?: ThemeProvider
782
- router?: Router
783
- i18n?: I18nProvider
784
- http?: HttpClient
785
- storage?: StorageProvider
786
- logger?: LoggerProvider
770
+ interface RouteLocation {
771
+ /**
772
+ * Current pathname.
773
+ */
774
+ pathname: string
775
+ /**
776
+ * Query string (including leading ?).
777
+ */
778
+ search: string
779
+ /**
780
+ * Hash (including leading #).
781
+ */
782
+ hash: string
783
+ /**
784
+ * State data passed with navigation.
785
+ */
786
+ state?: unknown
787
+ /**
788
+ * Unique key for this location.
789
+ */
790
+ key?: string
787
791
  }
788
792
  ```
789
793
 
790
- #### `MoleculeTokens`
791
-
792
- Injection tokens for molecule services.
793
-
794
- ```typescript
795
- interface MoleculeTokens {
796
- STATE_PROVIDER: symbol
797
- AUTH_CLIENT: symbol
798
- THEME_PROVIDER: symbol
799
- ROUTER: symbol
800
- I18N_PROVIDER: symbol
801
- HTTP_CLIENT: symbol
802
- STORAGE_PROVIDER: symbol
803
- LOGGER_PROVIDER: symbol
804
- }
805
- ```
794
+ #### `Router`
806
795
 
807
- #### `OAuthOptions`
796
+ Client-side router providing navigation, guards, route matching, and history control.
808
797
 
809
- OAuth configuration options.
798
+ All routing providers must implement this interface.
810
799
 
811
800
  ```typescript
812
- interface OAuthOptions {
813
- /** Base URL for the API server (e.g. `https://api.example.com`). */
814
- baseURL?: string
815
- /** List of supported OAuth provider names (e.g. `['github', 'google']`). */
816
- oauthProviders?: string[]
817
- /** Path prefix for OAuth initiation routes. Defaults to `/oauth`. */
818
- oauthEndpoint?: string
819
- /** Path for the OAuth login POST endpoint. Defaults to `/users/log-in/oauth`. */
820
- loginEndpoint?: string
821
- /** Called after a successful OAuth login (session established). */
822
- onSuccess?: () => void
801
+ interface Router {
823
802
  /**
824
- * Called with a failure message when the OAuth login fails. Failures are
825
- * also emitted on {@link OAuthStateManager.error$}.
803
+ * Returns the current route location (pathname, search, hash, state).
826
804
  */
827
- onError?: (error: string) => void
805
+ getLocation(): RouteLocation
828
806
  /**
829
- * Auth client used to establish the session after the code exchange. This
830
- * is THE way to wire session establishment in Angular: inject the
831
- * `AUTH_CLIENT` token and pass the client here. When omitted, the helper
832
- * falls back to the server-established httpOnly-cookie session (see
833
- * {@link OAuthStateManager.handleCallback}).
807
+ * Gets the current route params.
834
808
  */
835
- authClient?: AuthClient<unknown>
836
- }
837
- ```
838
-
839
- #### `OAuthStateManager`
840
-
841
- OAuth state manager.
842
-
843
- ```typescript
844
- interface OAuthStateManager {
845
- /** Observable of the configured OAuth provider names. */
846
- providers$: Observable<string[]>
809
+ getParams<T extends RouteParams = RouteParams>(): T
847
810
  /**
848
- * Observable of OAuth failure messages (from the callback code exchange).
849
- * Completed by {@link OAuthStateManager.destroy}.
811
+ * Gets the current query params.
850
812
  */
851
- error$: Observable<string>
852
- /** Returns the configured OAuth provider names. */
853
- getProviders: () => string[]
854
- /** Builds the OAuth initiation URL for a provider. */
855
- getOAuthUrl: (provider: string) => string
856
- /** Starts the full-page redirect flow for a provider. */
857
- redirect: (provider: string) => void
858
- /**
859
- * Handles the OAuth callback: exchanges the `code` URL parameter for a
860
- * session. Invoked automatically at creation (see {@link createOAuthState});
861
- * exposed for callers that need to re-run it manually. No-ops unless
862
- * running in a browser with a `code` URL parameter and a stashed provider.
863
- *
864
- * When an auth client was provided, the session is established locally
865
- * (`setAccessToken` + `setUser` + `initialize`). When no client is
866
- * available, the server has already established the httpOnly-cookie
867
- * session during the exchange, so a user-carrying response still counts
868
- * as success.
869
- */
870
- handleCallback: () => Promise<void>
871
- /** Completes the `providers$` and `error$` observables. */
872
- destroy: () => void
873
- }
874
- ```
875
-
876
- #### `PasswordResetStateManager`
877
-
878
- Password reset state manager.
879
-
880
- ```typescript
881
- interface PasswordResetStateManager {
882
- requestState$: Observable<PromiseState<void>>
883
- confirmState$: Observable<PromiseState<void>>
884
- getRequestState: () => PromiseState<void>
885
- getConfirmState: () => PromiseState<void>
886
- requestReset: (data: PasswordResetRequest) => Promise<void>
887
- confirmReset: (data: PasswordResetConfirm) => Promise<void>
888
- reset: () => void
889
- destroy: () => void
890
- }
891
- ```
892
-
893
- #### `PlatformService`
894
-
895
- Platform service interface.
896
-
897
- ```typescript
898
- interface PlatformService {
899
- platform: Platform
900
- isNative: boolean
901
- isMobile: boolean
902
- isDesktop: boolean
903
- isWeb: boolean
904
- isDevelopment: boolean
905
- isProduction: boolean
906
- isPlatform: (...platforms: Platform[]) => boolean
907
- }
908
- ```
909
-
910
- #### `PromiseStateManager`
911
-
912
- Promise state manager.
913
-
914
- ```typescript
915
- interface PromiseStateManager<T> {
916
- state$: Observable<PromiseState<T>>
917
- getState: () => PromiseState<T>
918
- call: (...args: any[]) => Promise<T>
919
- cancel: (message?: string) => void
920
- reset: () => void
921
- destroy: () => void
922
- }
923
- ```
924
-
925
- #### `PushService`
926
-
927
- Push service interface.
928
-
929
- ```typescript
930
- interface PushService {
931
- permission$: Observable<PermissionStatus | null>
932
- token$: Observable<PushToken | null>
933
- getPermission: () => PermissionStatus | null
934
- getToken: () => PushToken | null
935
- checkPermission: () => Promise<PermissionStatus>
936
- requestPermission: () => Promise<PermissionStatus>
937
- register: (options?: PushRegisterOptions) => Promise<PushToken>
938
- unregister: () => Promise<void>
939
- onNotificationReceived: (listener: NotificationReceivedListener) => () => void
940
- onNotificationAction: (listener: NotificationActionListener) => () => void
941
- onTokenChange: (listener: TokenChangeListener) => () => void
942
- setBadge: (count: number) => Promise<void>
943
- clearBadge: () => Promise<void>
944
- destroy: () => void
945
- }
946
- ```
947
-
948
- #### `RouteLocation`
949
-
950
- Current URL decomposed into pathname, search string, hash, navigation state, and unique key.
951
-
952
- ```typescript
953
- interface RouteLocation {
954
- /**
955
- * Current pathname.
956
- */
957
- pathname: string
958
- /**
959
- * Query string (including leading ?).
960
- */
961
- search: string
962
- /**
963
- * Hash (including leading #).
964
- */
965
- hash: string
966
- /**
967
- * State data passed with navigation.
968
- */
969
- state?: unknown
970
- /**
971
- * Unique key for this location.
972
- */
973
- key?: string
974
- }
975
- ```
976
-
977
- #### `Router`
978
-
979
- Client-side router providing navigation, guards, route matching, and history control.
980
-
981
- All routing providers must implement this interface.
982
-
983
- ```typescript
984
- interface Router {
985
- /**
986
- * Returns the current route location (pathname, search, hash, state).
987
- */
988
- getLocation(): RouteLocation
989
- /**
990
- * Gets the current route params.
991
- */
992
- getParams<T extends RouteParams = RouteParams>(): T
993
- /**
994
- * Gets the current query params.
995
- */
996
- getQuery(): QueryParams
813
+ getQuery(): QueryParams
997
814
  /**
998
815
  * Gets a specific query parameter.
999
816
  */
@@ -1079,17 +896,52 @@ interface Router {
1079
896
  }
1080
897
  ```
1081
898
 
1082
- #### `SignupStateManager`
899
+ #### `StoreConfig`
1083
900
 
1084
- Signup state manager.
901
+ Configuration for creating a store (initial state, optional name, and middleware chain).
1085
902
 
1086
903
  ```typescript
1087
- interface SignupStateManager<T = unknown> {
1088
- state$: Observable<PromiseState<AuthResult<T>>>
1089
- getState: () => PromiseState<AuthResult<T>>
1090
- signup: (data: RegisterData) => Promise<AuthResult<T>>
1091
- reset: () => void
1092
- destroy: () => void
904
+ interface StoreConfig<T> {
905
+ /**
906
+ * Initial state value.
907
+ */
908
+ initialState: T
909
+ /**
910
+ * Optional name for debugging.
911
+ */
912
+ name?: string
913
+ /**
914
+ * Optional middleware functions.
915
+ */
916
+ middleware?: StoreMiddleware<T>[]
917
+ }
918
+ ```
919
+
920
+ #### `Store`
921
+
922
+ Reactive state container with getState, setState, subscribe, and destroy.
923
+
924
+ All state management providers must implement this interface.
925
+
926
+ ```typescript
927
+ interface Store<T> {
928
+ /**
929
+ * Gets the current state.
930
+ */
931
+ getState(): T
932
+ /**
933
+ * Sets the state (partial or via updater function).
934
+ */
935
+ setState(partial: Partial<T> | ((state: T) => Partial<T>)): void
936
+ /**
937
+ * Subscribes to state changes.
938
+ * Returns an unsubscribe function.
939
+ */
940
+ subscribe(listener: StateListener<T>): () => void
941
+ /**
942
+ * Destroys the store and cleans up subscriptions.
943
+ */
944
+ destroy(): void
1093
945
  }
1094
946
  ```
1095
947
 
@@ -1150,64 +1002,49 @@ interface StorageProvider {
1150
1002
  }
1151
1003
  ```
1152
1004
 
1153
- #### `StorageValueState`
1154
-
1155
- State for a storage value.
1156
-
1157
- ```typescript
1158
- interface StorageValueState<T> {
1159
- value: T | undefined
1160
- loading: boolean
1161
- error: Error | null
1162
- }
1163
- ```
1164
-
1165
- #### `Store`
1166
-
1167
- Reactive state container with getState, setState, subscribe, and destroy.
1005
+ #### `ThemeProvider`
1168
1006
 
1169
- All state management providers must implement this interface.
1007
+ Manages theme state including the active theme, mode toggling,
1008
+ and change subscriptions.
1170
1009
 
1171
1010
  ```typescript
1172
- interface Store<T> {
1173
- /**
1174
- * Gets the current state.
1175
- */
1176
- getState(): T
1011
+ interface ThemeProvider {
1177
1012
  /**
1178
- * Sets the state (partial or via updater function).
1013
+ * Returns the currently active theme.
1179
1014
  */
1180
- setState(partial: Partial<T> | ((state: T) => Partial<T>)): void
1015
+ getTheme(): Theme
1181
1016
  /**
1182
- * Subscribes to state changes.
1183
- * Returns an unsubscribe function.
1017
+ * Sets the active theme by reference or by name.
1018
+ *
1019
+ * @param theme - A `Theme` object or a theme name string to activate.
1184
1020
  */
1185
- subscribe(listener: StateListener<T>): () => void
1021
+ setTheme(theme: Theme | string): void
1186
1022
  /**
1187
- * Destroys the store and cleans up subscriptions.
1023
+ * Toggles between light and dark mode for the active theme.
1188
1024
  */
1189
- destroy(): void
1190
- }
1191
- ```
1192
-
1193
- #### `StoreConfig`
1194
-
1195
- Configuration for creating a store (initial state, optional name, and middleware chain).
1196
-
1197
- ```typescript
1198
- interface StoreConfig<T> {
1025
+ toggleMode(): void
1199
1026
  /**
1200
- * Initial state value.
1027
+ * Subscribes to theme changes. The callback fires whenever
1028
+ * `setTheme()` or `toggleMode()` is called.
1029
+ *
1030
+ * @param callback - Invoked with the new theme after each change.
1031
+ * @returns An unsubscribe function.
1201
1032
  */
1202
- initialState: T
1033
+ subscribe(callback: (theme: Theme) => void): () => void
1203
1034
  /**
1204
- * Optional name for debugging.
1035
+ * Returns all registered themes. Optional — not all providers
1036
+ * support multiple themes.
1205
1037
  */
1206
- name?: string
1038
+ getThemes?(): Theme[]
1207
1039
  /**
1208
- * Optional middleware functions.
1040
+ * Returns the theme a render WITHOUT a browser produces — a server or
1041
+ * build-time render, which has no storage and no `prefers-color-scheme`
1042
+ * to read. A hydrating client renders this theme first, so its markup
1043
+ * matches what the server sent, and switches to `getTheme()` right
1044
+ * after. Optional — a provider whose theme cannot differ between the two
1045
+ * need not implement it.
1209
1046
  */
1210
- middleware?: StoreMiddleware<T>[]
1047
+ getServerTheme?(): Theme
1211
1048
  }
1212
1049
  ```
1213
1050
 
@@ -1230,157 +1067,1683 @@ interface Theme {
1230
1067
  }
1231
1068
  ```
1232
1069
 
1233
- #### `ThemeProvider`
1070
+ #### `AsyncStateManager`
1234
1071
 
1235
- Manages theme state including the active theme, mode toggling,
1236
- and change subscriptions.
1072
+ Async state manager.
1237
1073
 
1238
1074
  ```typescript
1239
- interface ThemeProvider {
1075
+ interface AsyncStateManager<T> {
1076
+ state$: Observable<T>
1077
+ getState: () => T
1078
+ setState: (value: T | ((prev: T) => T) | Promise<T | ((prev: T) => T)>) => void
1079
+ extendState: (
1080
+ partial:
1081
+ Partial<T> | ((prev: T) => Partial<T>) | Promise<Partial<T> | ((prev: T) => Partial<T>)>,
1082
+ ) => void
1083
+ destroy: () => void
1084
+ }
1085
+ ```
1086
+
1087
+ #### `CapacitorAppManager`
1088
+
1089
+ Capacitor app manager interface.
1090
+
1091
+ ```typescript
1092
+ interface CapacitorAppManager {
1093
+ state$: Observable<CapacitorAppState>
1094
+ ready$: Observable<boolean>
1095
+ initialize: () => Promise<void>
1096
+ destroy: () => void
1097
+ }
1098
+ ```
1099
+
1100
+ #### `ChangePasswordStateManager`
1101
+
1102
+ Change password state manager.
1103
+
1104
+ ```typescript
1105
+ interface ChangePasswordStateManager {
1106
+ state$: Observable<PromiseState<void>>
1107
+ getState: () => PromiseState<void>
1108
+ changePassword: (oldPassword: string, newPassword: string) => Promise<void>
1109
+ reset: () => void
1110
+ destroy: () => void
1111
+ }
1112
+ ```
1113
+
1114
+ #### `DeviceService`
1115
+
1116
+ Device service interface.
1117
+
1118
+ ```typescript
1119
+ interface DeviceService {
1120
+ deviceInfo: DeviceInfo
1121
+ screenInfo: ScreenInfo
1122
+ hardwareInfo: HardwareInfo
1123
+ featureSupport: FeatureSupport
1124
+ supports: (feature: keyof FeatureSupport) => boolean
1125
+ isOnline: () => boolean
1126
+ isStandalone: () => boolean
1127
+ language: string
1128
+ languages: string[]
1129
+ }
1130
+ ```
1131
+
1132
+ #### `HttpState`
1133
+
1134
+ State for HTTP requests.
1135
+
1136
+ ```typescript
1137
+ interface HttpState<T> {
1138
+ data: T | null
1139
+ loading: boolean
1140
+ error: Error | null
1141
+ }
1142
+ ```
1143
+
1144
+ #### `LoginStateManager`
1145
+
1146
+ Login state manager.
1147
+
1148
+ ```typescript
1149
+ interface LoginStateManager<T = unknown> {
1150
+ state$: Observable<PromiseState<AuthResult<T>>>
1151
+ getState: () => PromiseState<AuthResult<T>>
1152
+ login: (credentials: LoginCredentials) => Promise<AuthResult<T>>
1153
+ reset: () => void
1154
+ destroy: () => void
1155
+ }
1156
+ ```
1157
+
1158
+ #### `OAuthOptions`
1159
+
1160
+ OAuth configuration options.
1161
+
1162
+ ```typescript
1163
+ interface OAuthOptions {
1164
+ /** Base URL for the API server (e.g. `https://api.example.com`). */
1165
+ baseURL?: string
1166
+ /** List of supported OAuth provider names (e.g. `['github', 'google']`). */
1167
+ oauthProviders?: string[]
1168
+ /** Path prefix for OAuth initiation routes. Defaults to `/oauth`. */
1169
+ oauthEndpoint?: string
1170
+ /** Path for the OAuth login POST endpoint. Defaults to `/users/log-in/oauth`. */
1171
+ loginEndpoint?: string
1172
+ /** Called after a successful OAuth login (session established). */
1173
+ onSuccess?: () => void
1174
+ /**
1175
+ * Called with a failure message when the OAuth login fails. Failures are
1176
+ * also emitted on {@link OAuthStateManager.error$}.
1177
+ */
1178
+ onError?: (error: string) => void
1179
+ /**
1180
+ * Auth client used to establish the session after the code exchange. This
1181
+ * is THE way to wire session establishment in Angular: inject the
1182
+ * `AUTH_CLIENT` token and pass the client here. When omitted, the helper
1183
+ * falls back to the server-established httpOnly-cookie session (see
1184
+ * {@link OAuthStateManager.handleCallback}).
1185
+ */
1186
+ authClient?: AuthClient<unknown>
1187
+ }
1188
+ ```
1189
+
1190
+ #### `OAuthStateManager`
1191
+
1192
+ OAuth state manager.
1193
+
1194
+ ```typescript
1195
+ interface OAuthStateManager {
1196
+ /** Observable of the configured OAuth provider names. */
1197
+ providers$: Observable<string[]>
1198
+ /**
1199
+ * Observable of OAuth failure messages (from the callback code exchange).
1200
+ * Completed by {@link OAuthStateManager.destroy}.
1201
+ */
1202
+ error$: Observable<string>
1203
+ /** Returns the configured OAuth provider names. */
1204
+ getProviders: () => string[]
1205
+ /** Builds the OAuth initiation URL for a provider. */
1206
+ getOAuthUrl: (provider: string) => string
1207
+ /** Starts the full-page redirect flow for a provider. */
1208
+ redirect: (provider: string) => void
1209
+ /**
1210
+ * Handles the OAuth callback: exchanges the `code` URL parameter for a
1211
+ * session. Invoked automatically at creation (see {@link createOAuthState});
1212
+ * exposed for callers that need to re-run it manually. No-ops unless
1213
+ * running in a browser with a `code` URL parameter and a stashed provider.
1214
+ *
1215
+ * When an auth client was provided, the session is established locally
1216
+ * (`setAccessToken` + `setUser` + `initialize`). When no client is
1217
+ * available, the server has already established the httpOnly-cookie
1218
+ * session during the exchange, so a user-carrying response still counts
1219
+ * as success.
1220
+ */
1221
+ handleCallback: () => Promise<void>
1222
+ /** Completes the `providers$` and `error$` observables. */
1223
+ destroy: () => void
1224
+ }
1225
+ ```
1226
+
1227
+ #### `PasswordResetStateManager`
1228
+
1229
+ Password reset state manager.
1230
+
1231
+ ```typescript
1232
+ interface PasswordResetStateManager {
1233
+ requestState$: Observable<PromiseState<void>>
1234
+ confirmState$: Observable<PromiseState<void>>
1235
+ getRequestState: () => PromiseState<void>
1236
+ getConfirmState: () => PromiseState<void>
1237
+ requestReset: (data: PasswordResetRequest) => Promise<void>
1238
+ confirmReset: (data: PasswordResetConfirm) => Promise<void>
1239
+ reset: () => void
1240
+ destroy: () => void
1241
+ }
1242
+ ```
1243
+
1244
+ #### `PlatformService`
1245
+
1246
+ Platform service interface.
1247
+
1248
+ ```typescript
1249
+ interface PlatformService {
1250
+ platform: Platform
1251
+ isNative: boolean
1252
+ isMobile: boolean
1253
+ isDesktop: boolean
1254
+ isWeb: boolean
1255
+ isDevelopment: boolean
1256
+ isProduction: boolean
1257
+ isPlatform: (...platforms: Platform[]) => boolean
1258
+ }
1259
+ ```
1260
+
1261
+ #### `PromiseStateManager`
1262
+
1263
+ Promise state manager.
1264
+
1265
+ ```typescript
1266
+ interface PromiseStateManager<T> {
1267
+ state$: Observable<PromiseState<T>>
1268
+ getState: () => PromiseState<T>
1269
+ call: (...args: any[]) => Promise<T>
1270
+ cancel: (message?: string) => void
1271
+ reset: () => void
1272
+ destroy: () => void
1273
+ }
1274
+ ```
1275
+
1276
+ **Returns:** The result.
1277
+
1278
+ #### `PushService`
1279
+
1280
+ Push service interface.
1281
+
1282
+ ```typescript
1283
+ interface PushService {
1284
+ permission$: Observable<PermissionStatus | null>
1285
+ token$: Observable<PushToken | null>
1286
+ getPermission: () => PermissionStatus | null
1287
+ getToken: () => PushToken | null
1288
+ checkPermission: () => Promise<PermissionStatus>
1289
+ requestPermission: () => Promise<PermissionStatus>
1290
+ register: (options?: PushRegisterOptions) => Promise<PushToken>
1291
+ unregister: () => Promise<void>
1292
+ onNotificationReceived: (listener: NotificationReceivedListener) => () => void
1293
+ onNotificationAction: (listener: NotificationActionListener) => () => void
1294
+ onTokenChange: (listener: TokenChangeListener) => () => void
1295
+ setBadge: (count: number) => Promise<void>
1296
+ clearBadge: () => Promise<void>
1297
+ destroy: () => void
1298
+ }
1299
+ ```
1300
+
1301
+ #### `SignupStateManager`
1302
+
1303
+ Signup state manager.
1304
+
1305
+ ```typescript
1306
+ interface SignupStateManager<T = unknown> {
1307
+ state$: Observable<PromiseState<AuthResult<T>>>
1308
+ getState: () => PromiseState<AuthResult<T>>
1309
+ signup: (data: RegisterData) => Promise<AuthResult<T>>
1310
+ reset: () => void
1311
+ destroy: () => void
1312
+ }
1313
+ ```
1314
+
1315
+ #### `StorageValueState`
1316
+
1317
+ State for a storage value.
1318
+
1319
+ ```typescript
1320
+ interface StorageValueState<T> {
1321
+ value: T | undefined
1322
+ loading: boolean
1323
+ error: Error | null
1324
+ }
1325
+ ```
1326
+
1327
+ #### `VersionService`
1328
+
1329
+ Version service interface.
1330
+
1331
+ ```typescript
1332
+ interface VersionService {
1333
+ state$: Observable<VersionState>
1334
+ isUpdateAvailable$: Observable<boolean>
1335
+ isChecking$: Observable<boolean>
1336
+ isServiceWorkerWaiting$: Observable<boolean>
1337
+ newVersion$: Observable<string | undefined>
1338
+ getState: () => VersionState
1339
+ checkForUpdates: () => Promise<boolean>
1340
+ applyUpdate: (options?: { force?: boolean }) => void
1341
+ dismissUpdate: () => void
1342
+ startPeriodicChecks: (options?: UpdateCheckOptions) => void
1343
+ stopPeriodicChecks: () => void
1344
+ destroy: () => void
1345
+ }
1346
+ ```
1347
+
1348
+ #### `MoleculeModuleConfig`
1349
+
1350
+ Configuration for molecule Angular module.
1351
+
1352
+ ```typescript
1353
+ interface MoleculeModuleConfig {
1354
+ state?: StateProvider
1355
+ auth?: AuthClient<unknown>
1356
+ theme?: ThemeProvider
1357
+ router?: Router
1358
+ i18n?: I18nProvider
1359
+ http?: HttpClient
1360
+ storage?: StorageProvider
1361
+ logger?: LoggerProvider
1362
+ }
1363
+ ```
1364
+
1365
+ #### `MoleculeTokens`
1366
+
1367
+ Injection tokens for molecule services.
1368
+
1369
+ ```typescript
1370
+ interface MoleculeTokens {
1371
+ STATE_PROVIDER: symbol
1372
+ AUTH_CLIENT: symbol
1373
+ THEME_PROVIDER: symbol
1374
+ ROUTER: symbol
1375
+ I18N_PROVIDER: symbol
1376
+ HTTP_CLIENT: symbol
1377
+ STORAGE_PROVIDER: symbol
1378
+ LOGGER_PROVIDER: symbol
1379
+ }
1380
+ ```
1381
+
1382
+ ### Types
1383
+
1384
+ #### `RouteParams`
1385
+
1386
+ URL path parameter key-value map extracted from dynamic route segments (e.g. `{ id: '123' }`).
1387
+
1388
+ ```typescript
1389
+ type RouteParams = Record<string, string>
1390
+ ```
1391
+
1392
+ #### `QueryParams`
1393
+
1394
+ URL query string parameter map (single values or arrays for repeated keys).
1395
+
1396
+ ```typescript
1397
+ type QueryParams = Record<string, string | string[] | undefined>
1398
+ ```
1399
+
1400
+ ### Classes
1401
+
1402
+ #### `MoleculeAuthService`
1403
+
1404
+ Angular service for authentication.
1405
+
1406
+ Wraps molecule auth client and exposes state as RxJS observables.
1407
+
1408
+ ```typescript
1409
+ @Injectable({
1410
+ providedIn: 'root',
1411
+ })
1412
+ export class MoleculeAuthService<
1413
+ T = unknown,
1414
+ TRegister extends RegisterExtraShape<TRegister> = Record<never, never>,
1415
+ > implements OnDestroy {
1416
+ private stateSubject: BehaviorSubject<AuthState<T>>
1417
+ private unsubscribe: (() => void) | null = null
1418
+
1419
+ /**
1420
+ * Observable of the full auth state.
1421
+ */
1422
+ state$: Observable<AuthState<T>>
1423
+
1424
+ /**
1425
+ * Observable of the authenticated user.
1426
+ */
1427
+ user$: Observable<T | null>
1428
+
1429
+ /**
1430
+ * Observable of authentication status.
1431
+ */
1432
+ isAuthenticated$: Observable<boolean>
1433
+
1434
+ /**
1435
+ * Observable of loading status.
1436
+ */
1437
+ isLoading$: Observable<boolean>
1438
+
1439
+ constructor(@Inject(AUTH_CLIENT) private client: AuthClient<T, TRegister>) {
1440
+ this.stateSubject = new BehaviorSubject<AuthState<T>>(client.getState())
1441
+
1442
+ this.unsubscribe = client.onAuthChange(() => {
1443
+ this.stateSubject.next(client.getState())
1444
+ })
1445
+
1446
+ this.state$ = this.stateSubject.asObservable()
1447
+
1448
+ this.user$ = this.state$.pipe(
1449
+ map((state) => state.user),
1450
+ distinctUntilChanged(),
1451
+ )
1452
+
1453
+ this.isAuthenticated$ = this.state$.pipe(
1454
+ map((state) => state.authenticated),
1455
+ distinctUntilChanged(),
1456
+ )
1457
+
1458
+ this.isLoading$ = this.state$.pipe(
1459
+ map((state) => state.loading),
1460
+ distinctUntilChanged(),
1461
+ )
1462
+ }
1463
+
1464
+ /**
1465
+ * Get the current auth state snapshot.
1466
+ * @returns The current authentication state including user, loading, and authenticated flags.
1467
+ */
1468
+ get state(): AuthState<T> {
1469
+ return this.client.getState()
1470
+ }
1471
+
1472
+ /**
1473
+ * Get the current user snapshot.
1474
+ * @returns The authenticated user object, or `null` if not authenticated.
1475
+ */
1476
+ get user(): T | null {
1477
+ return this.state.user
1478
+ }
1479
+
1480
+ /**
1481
+ * Check if user is currently authenticated.
1482
+ * @returns `true` if the user is authenticated, `false` otherwise.
1483
+ */
1484
+ get isAuthenticated(): boolean {
1485
+ return this.state.authenticated
1486
+ }
1487
+
1488
+ /**
1489
+ * Login with credentials.
1490
+ *
1491
+ * @param credentials - The email/password or provider-specific login credentials.
1492
+ * @returns A promise that resolves to the authentication result with user data and tokens.
1493
+ */
1494
+ login(credentials: LoginCredentials): Promise<AuthResult<T>> {
1495
+ return this.client.login(credentials)
1496
+ }
1497
+
1498
+ /**
1499
+ * Logout the current user.
1500
+ * @returns A promise that resolves when the user has been logged out.
1501
+ */
1502
+ logout(): Promise<void> {
1503
+ return this.client.logout()
1504
+ }
1505
+
1506
+ /**
1507
+ * Register a new user.
1508
+ *
1509
+ * @param data - The registration payload including email, password, and profile fields.
1510
+ * @returns A promise that resolves to the authentication result for the newly registered user.
1511
+ */
1512
+ register(data: RegisterData<TRegister>): Promise<AuthResult<T>> {
1513
+ return this.client.register(data)
1514
+ }
1515
+
1516
+ /**
1517
+ * Refresh the authentication token.
1518
+ * @returns A promise that resolves to the authentication result with refreshed tokens.
1519
+ */
1520
+ refresh(): Promise<AuthResult<T>> {
1521
+ return this.client.refresh()
1522
+ }
1523
+
1524
+ /**
1525
+ * Clean up auth state subscription on service destruction.
1526
+ */
1527
+ ngOnDestroy(): void {
1528
+ this.unsubscribe?.()
1529
+ this.stateSubject.complete()
1530
+ }
1531
+ }
1532
+ ```
1533
+
1534
+ #### `MoleculeFormInstance`
1535
+
1536
+ Wrapper around a FormController that provides RxJS observables and
1537
+ synchronous accessors for use in Angular components.
1538
+
1539
+ ```typescript
1540
+ class MoleculeFormInstance<T extends Record<string, unknown>> {
1541
+ private stateSubject: BehaviorSubject<FormState<T>>
1542
+ private unsubscribe: (() => void) | null = null
1543
+
1544
+ /**
1545
+ * Observable of the full form state.
1546
+ */
1547
+ state$: Observable<FormState<T>>
1548
+
1549
+ /**
1550
+ * Observable of form validity.
1551
+ */
1552
+ isValid$: Observable<boolean>
1553
+
1554
+ /**
1555
+ * Observable of form dirty state.
1556
+ */
1557
+ isDirty$: Observable<boolean>
1558
+
1559
+ /**
1560
+ * Observable of form submitting state.
1561
+ */
1562
+ isSubmitting$: Observable<boolean>
1563
+
1564
+ /**
1565
+ * Observable of field errors.
1566
+ */
1567
+ errors$: Observable<Partial<Record<keyof T, string>>>
1568
+
1569
+ constructor(private controller: FormController<T>) {
1570
+ this.stateSubject = new BehaviorSubject<FormState<T>>(controller.getState())
1571
+
1572
+ this.unsubscribe = controller.subscribe((state: FormState<T>) => {
1573
+ this.stateSubject.next(state)
1574
+ })
1575
+
1576
+ this.state$ = this.stateSubject.asObservable()
1577
+
1578
+ this.isValid$ = this.state$.pipe(
1579
+ map((state) => state.isValid),
1580
+ distinctUntilChanged(),
1581
+ )
1582
+
1583
+ this.isDirty$ = this.state$.pipe(
1584
+ map((state) => state.isDirty),
1585
+ distinctUntilChanged(),
1586
+ )
1587
+
1588
+ this.isSubmitting$ = this.state$.pipe(
1589
+ map((state) => state.isSubmitting),
1590
+ distinctUntilChanged(),
1591
+ )
1592
+
1593
+ this.errors$ = this.state$.pipe(
1594
+ map((state) => state.errors),
1595
+ distinctUntilChanged(),
1596
+ )
1597
+ }
1598
+
1599
+ /**
1600
+ * Get the current form state snapshot.
1601
+ * @returns The current form state including values, errors, validity, and submission status.
1602
+ */
1603
+ get state(): FormState<T> {
1604
+ return this.controller.getState()
1605
+ }
1606
+
1607
+ /**
1608
+ * Get the current form validity snapshot.
1609
+ * @returns `true` if all fields pass validation, `false` otherwise.
1610
+ */
1611
+ get isValid(): boolean {
1612
+ return this.controller.getState().isValid
1613
+ }
1614
+
1615
+ /**
1616
+ * Get the current form dirty state snapshot.
1617
+ * @returns `true` if any field has been modified from its default value, `false` otherwise.
1618
+ */
1619
+ get isDirty(): boolean {
1620
+ return this.controller.getState().isDirty
1621
+ }
1622
+
1623
+ /**
1624
+ * Get the current form submitting state snapshot.
1625
+ * @returns `true` if the form is currently being submitted, `false` otherwise.
1626
+ */
1627
+ get isSubmitting(): boolean {
1628
+ return this.controller.getState().isSubmitting
1629
+ }
1630
+
1631
+ /**
1632
+ * Register a field for form management.
1633
+ *
1634
+ * @param name - The field name corresponding to a key in the form values type.
1635
+ * @param options - Registration options including validation rules and default value.
1636
+ * @returns The field registration with value, onChange, onBlur, and ref bindings.
1637
+ */
1638
+ register(name: keyof T, options?: Omit<RegisterOptions, 'name'>): FieldRegistration {
1639
+ return this.controller.register({ ...options, name: name as string } as RegisterOptions)
1640
+ }
1641
+
1642
+ /**
1643
+ * Get the value of a specific field.
1644
+ *
1645
+ * @param name - The field name corresponding to a key in the form values type.
1646
+ * @returns The current value of the specified field.
1647
+ */
1648
+ getValue<K extends keyof T>(name: K): T[K] {
1649
+ return this.controller.getValue(name)
1650
+ }
1651
+
1652
+ /**
1653
+ * Set the value of a specific field.
1654
+ *
1655
+ * @param name - The field name corresponding to a key in the form values type.
1656
+ * @param value - The new value to set for the field.
1657
+ */
1658
+ setValue<K extends keyof T>(name: K, value: T[K]): void {
1659
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1660
+ this.controller.setValue(name as any, value as any)
1661
+ }
1662
+
1663
+ /**
1664
+ * Get the error for a specific field.
1665
+ *
1666
+ * @param name - The field name to retrieve the error for.
1667
+ * @returns The error message for the field, or `undefined` if the field has no error.
1668
+ */
1669
+ getError(name: keyof T): string | undefined {
1670
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1671
+ return this.controller.getError(name as any)
1672
+ }
1673
+
1674
+ /**
1675
+ * Set the error for a specific field.
1676
+ *
1677
+ * @param name - The field name to set or clear the error for.
1678
+ * @param error - The error message to set, or `undefined` to clear the error.
1679
+ */
1680
+ setError(name: keyof T, error: string | undefined): void {
1681
+ if (error !== undefined) {
1682
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1683
+ this.controller.setError(name as any, error)
1684
+ } else {
1685
+ this.controller.clearError(name)
1686
+ }
1687
+ }
1688
+
1689
+ /**
1690
+ * Clear all field errors.
1691
+ */
1692
+ clearErrors(): void {
1693
+ this.controller.clearErrors()
1694
+ }
1695
+
1696
+ /**
1697
+ * Create a submit handler function.
1698
+ *
1699
+ * @param onSubmit - Callback invoked with validated form values on successful submission.
1700
+ * @param onError - Optional callback invoked with field errors when validation fails.
1701
+ * @returns A submit handler function that can be called from form submit events.
1702
+ */
1703
+ handleSubmit(
1704
+ onSubmit: (values: T) => void | Promise<void>,
1705
+ onError?: (errors: Partial<Record<keyof T, string>>) => void,
1706
+ ): (event?: { preventDefault?: () => void }) => Promise<void> {
1707
+ return this.controller.handleSubmit(onSubmit, onError)
1708
+ }
1709
+
1710
+ /**
1711
+ * Reset the form to initial values.
1712
+ *
1713
+ * @param values - Optional partial values to use as the new initial state.
1714
+ */
1715
+ reset(values?: Partial<T>): void {
1716
+ this.controller.reset(values)
1717
+ }
1718
+
1719
+ /**
1720
+ * Validate all fields.
1721
+ *
1722
+ * @returns A promise that resolves to `true` if all fields are valid, `false` otherwise.
1723
+ */
1724
+ validate(): Promise<boolean> {
1725
+ return this.controller.validate()
1726
+ }
1727
+
1728
+ /**
1729
+ * Destroy the form instance and clean up subscriptions.
1730
+ */
1731
+ destroy(): void {
1732
+ this.unsubscribe?.()
1733
+ this.unsubscribe = null
1734
+ this.stateSubject.complete()
1735
+ this.controller.destroy()
1736
+ }
1737
+ }
1738
+ ```
1739
+
1740
+ #### `MoleculeFormsService`
1741
+
1742
+ Angular service for form handling.
1743
+
1744
+ Wraps molecule form providers and creates form instances that expose
1745
+ state as RxJS observables.
1746
+
1747
+ ```typescript
1748
+ @Injectable({
1749
+ providedIn: 'root',
1750
+ })
1751
+ export class MoleculeFormsService implements OnDestroy {
1752
+ private instances: MoleculeFormInstance<Record<string, unknown>>[] = []
1753
+
1754
+ /**
1755
+ * Create a form instance from a provider.
1756
+ *
1757
+ * @param provider - The form provider that creates form controllers.
1758
+ * @param options - Form configuration including default values and validation mode.
1759
+ * @returns A new form instance with RxJS observables for reactive state tracking.
1760
+ */
1761
+ createForm<T extends Record<string, unknown>>(
1762
+ provider: FormProvider,
1763
+ options: FormOptions<T>,
1764
+ ): MoleculeFormInstance<T> {
1765
+ const controller = provider.createForm(options)
1766
+ const instance = new MoleculeFormInstance<T>(controller)
1767
+ this.instances.push(instance as MoleculeFormInstance<Record<string, unknown>>)
1768
+ return instance
1769
+ }
1770
+
1771
+ /**
1772
+ * Destroy all managed form instances on service destruction.
1773
+ */
1774
+ ngOnDestroy(): void {
1775
+ for (const instance of this.instances) {
1776
+ instance.destroy()
1777
+ }
1778
+ this.instances = []
1779
+ }
1780
+ }
1781
+ ```
1782
+
1783
+ #### `MoleculeHttpService`
1784
+
1785
+ Angular service for HTTP requests.
1786
+
1787
+ Wraps molecule HTTP client and returns RxJS observables.
1788
+
1789
+ ```typescript
1790
+ @Injectable({
1791
+ providedIn: 'root',
1792
+ })
1793
+ /**
1794
+ * Molecule Http Service class.
1795
+ */
1796
+ export class MoleculeHttpService {
1797
+ constructor(@Inject(HTTP_CLIENT) private client: HttpClient) {}
1798
+
1799
+ /**
1800
+ * Make a GET request.
1801
+ *
1802
+ * @param url - Request URL
1803
+ * @param config - Request configuration
1804
+ * @returns Observable of response data
1805
+ */
1806
+ get<T>(url: string, config?: RequestConfig): Observable<T> {
1807
+ return from(this.client.get<T>(url, config)).pipe(map((res) => res.data))
1808
+ }
1809
+
1810
+ /**
1811
+ * Make a POST request.
1812
+ *
1813
+ * @param url - Request URL
1814
+ * @param data - Request body
1815
+ * @param config - Request configuration
1816
+ * @returns Observable of response data
1817
+ */
1818
+ post<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
1819
+ return from(this.client.post<T>(url, data, config)).pipe(map((res) => res.data))
1820
+ }
1821
+
1822
+ /**
1823
+ * Make a PUT request.
1824
+ *
1825
+ * @param url - Request URL
1826
+ * @param data - Request body
1827
+ * @param config - Request configuration
1828
+ * @returns Observable of response data
1829
+ */
1830
+ put<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
1831
+ return from(this.client.put<T>(url, data, config)).pipe(map((res) => res.data))
1832
+ }
1833
+
1834
+ /**
1835
+ * Make a PATCH request.
1836
+ *
1837
+ * @param url - Request URL
1838
+ * @param data - Request body
1839
+ * @param config - Request configuration
1840
+ * @returns Observable of response data
1841
+ */
1842
+ patch<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
1843
+ return from(this.client.patch<T>(url, data, config)).pipe(map((res) => res.data))
1844
+ }
1845
+
1846
+ /**
1847
+ * Make a DELETE request.
1848
+ *
1849
+ * @param url - Request URL
1850
+ * @param config - Request configuration
1851
+ * @returns Observable of response data
1852
+ */
1853
+ delete<T>(url: string, config?: RequestConfig): Observable<T> {
1854
+ return from(this.client.delete<T>(url, config)).pipe(map((res) => res.data))
1855
+ }
1856
+
1857
+ /**
1858
+ * Get the full response (including headers, status, etc.).
1859
+ *
1860
+ * @param method - HTTP method
1861
+ * @param url - Request URL
1862
+ * @param config - Request configuration
1863
+ * @returns Observable of full response
1864
+ */
1865
+ request<T>(
1866
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
1867
+ url: string,
1868
+ config?: RequestConfig,
1869
+ ): Observable<HttpResponse<T>> {
1870
+ const promise = (async () => {
1871
+ switch (method) {
1872
+ case 'GET':
1873
+ return this.client.get<T>(url, config)
1874
+ case 'POST':
1875
+ return this.client.post<T>(
1876
+ url,
1877
+ (config as RequestConfig & { data?: unknown })?.data,
1878
+ config,
1879
+ )
1880
+ case 'PUT':
1881
+ return this.client.put<T>(
1882
+ url,
1883
+ (config as RequestConfig & { data?: unknown })?.data,
1884
+ config,
1885
+ )
1886
+ case 'PATCH':
1887
+ return this.client.patch<T>(
1888
+ url,
1889
+ (config as RequestConfig & { data?: unknown })?.data,
1890
+ config,
1891
+ )
1892
+ case 'DELETE':
1893
+ return this.client.delete<T>(url, config)
1894
+ }
1895
+ })()
1896
+
1897
+ return from(promise)
1898
+ }
1899
+
1900
+ /**
1901
+ * Create a state subject for managing request state.
1902
+ *
1903
+ * @returns The created instance.
1904
+ */
1905
+ createState<T>(): BehaviorSubject<HttpState<T>> {
1906
+ return new BehaviorSubject<HttpState<T>>({
1907
+ data: null,
1908
+ loading: false,
1909
+ error: null,
1910
+ })
1911
+ }
1912
+
1913
+ /**
1914
+ * Make a GET request with state management.
1915
+ *
1916
+ * @param url - Request URL
1917
+ * @param state - State subject to update
1918
+ * @param config - Request configuration
1919
+ */
1920
+ getWithState<T>(url: string, state: BehaviorSubject<HttpState<T>>, config?: RequestConfig): void {
1921
+ state.next({ ...state.value, loading: true, error: null })
1922
+
1923
+ this.get<T>(url, config)
1924
+ .pipe(
1925
+ tap((data) => state.next({ data, loading: false, error: null })),
1926
+ catchError((error) => {
1927
+ state.next({ data: null, loading: false, error })
1928
+ throw error
1929
+ }),
1930
+ )
1931
+ .subscribe()
1932
+ }
1933
+ }
1934
+ ```
1935
+
1936
+ #### `MoleculeI18nService`
1937
+
1938
+ Angular service for internationalization.
1939
+
1940
+ Wraps molecule i18n provider and exposes state as RxJS observables.
1941
+
1942
+ ```typescript
1943
+ @Injectable({
1944
+ providedIn: 'root',
1945
+ })
1946
+ export class MoleculeI18nService implements OnDestroy {
1947
+ private localeSubject: BehaviorSubject<string>
1948
+ private directionSubject: BehaviorSubject<'ltr' | 'rtl'>
1949
+ private localesSubject: BehaviorSubject<LocaleConfig[]>
1950
+ private unsubscribe: (() => void) | null = null
1951
+
1952
+ /**
1953
+ * Observable of the current locale code.
1954
+ */
1955
+ locale$: Observable<string>
1956
+
1957
+ /**
1958
+ * Observable of the text direction.
1959
+ */
1960
+ direction$: Observable<'ltr' | 'rtl'>
1961
+
1962
+ /**
1963
+ * Observable of available locales.
1964
+ */
1965
+ locales$: Observable<LocaleConfig[]>
1966
+
1967
+ constructor(@Inject(I18N_PROVIDER) private provider: I18nProvider) {
1968
+ this.localeSubject = new BehaviorSubject<string>(provider.getLocale())
1969
+ this.directionSubject = new BehaviorSubject<'ltr' | 'rtl'>(provider.getDirection())
1970
+ this.localesSubject = new BehaviorSubject<LocaleConfig[]>(provider.getLocales())
1971
+
1972
+ this.unsubscribe = provider.onLocaleChange(() => {
1973
+ this.localeSubject.next(provider.getLocale())
1974
+ this.directionSubject.next(provider.getDirection())
1975
+ this.localesSubject.next(provider.getLocales())
1976
+ })
1977
+
1978
+ this.locale$ = this.localeSubject.asObservable()
1979
+ this.direction$ = this.directionSubject.asObservable()
1980
+ this.locales$ = this.localesSubject.asObservable()
1981
+ }
1982
+
1983
+ /**
1984
+ * Get the current locale code snapshot.
1985
+ * @returns The active locale code (e.g. `'en'`, `'fr'`).
1986
+ */
1987
+ get locale(): string {
1988
+ return this.provider.getLocale()
1989
+ }
1990
+
1991
+ /**
1992
+ * Get the current text direction snapshot.
1993
+ * @returns The text direction for the active locale, either `'ltr'` or `'rtl'`.
1994
+ */
1995
+ get direction(): 'ltr' | 'rtl' {
1996
+ return this.provider.getDirection()
1997
+ }
1998
+
1999
+ /**
2000
+ * Translate a key.
2001
+ *
2002
+ * @param key - The dot-notation translation key to look up.
2003
+ * @param values - Optional interpolation values to substitute into the translated string.
2004
+ * @returns The translated and interpolated string for the current locale.
2005
+ */
2006
+ t(key: string, values?: InterpolationValues): string {
2007
+ return this.provider.t(key, values)
2008
+ }
2009
+
2010
+ /**
2011
+ * Set the current locale.
2012
+ *
2013
+ * @param code - The locale code to switch to (e.g. `'en'`, `'fr'`).
2014
+ */
2015
+ setLocale(code: string): void {
2016
+ this.provider.setLocale(code)
2017
+ }
2018
+
2019
+ /**
2020
+ * Format a number.
2021
+ *
2022
+ * @param value - The number to format.
2023
+ * @param options - Locale-aware formatting options such as style, currency, and precision.
2024
+ * @returns The locale-formatted number string.
2025
+ */
2026
+ formatNumber(value: number, options?: NumberFormatOptions): string {
2027
+ return this.provider.formatNumber(value, options)
2028
+ }
2029
+
2030
+ /**
2031
+ * Format a date.
2032
+ *
2033
+ * @param value - The date to format, as a Date object, timestamp, or ISO string.
2034
+ * @param options - Locale-aware formatting options such as date style and time zone.
2035
+ * @returns The locale-formatted date string.
2036
+ */
2037
+ formatDate(value: Date | number | string, options?: DateFormatOptions): string {
2038
+ return this.provider.formatDate(value, options)
2039
+ }
2040
+
2041
+ /**
2042
+ * Clean up locale change subscription and complete all subjects on service destruction.
2043
+ */
2044
+ ngOnDestroy(): void {
2045
+ this.unsubscribe?.()
2046
+ this.localeSubject.complete()
2047
+ this.directionSubject.complete()
2048
+ this.localesSubject.complete()
2049
+ }
2050
+ }
2051
+ ```
2052
+
2053
+ #### `MoleculeLoggerService`
2054
+
2055
+ Angular service for logging.
2056
+
2057
+ Wraps molecule logger provider.
2058
+
2059
+ ```typescript
2060
+ @Injectable({
2061
+ providedIn: 'root',
2062
+ })
2063
+ export class MoleculeLoggerService {
2064
+ constructor(@Inject(LOGGER_PROVIDER) private provider: LoggerProvider) {}
2065
+
2066
+ /**
2067
+ * Get a logger instance.
2068
+ *
2069
+ * @param name - Logger name (usually component/service name)
2070
+ * @returns The result.
2071
+ */
2072
+ getLogger(name?: string): Logger {
2073
+ return this.provider.getLogger(name)
2074
+ }
2075
+
2076
+ /**
2077
+ * Create a logger with custom configuration.
2078
+ *
2079
+ * @param config - Logger configuration
2080
+ * @returns The created instance.
2081
+ */
2082
+ createLogger(config: LoggerConfig): Logger {
2083
+ return this.provider.createLogger(config)
2084
+ }
2085
+
2086
+ /**
2087
+ * Set the global log level.
2088
+ *
2089
+ * @param level - Log level
2090
+ */
2091
+ setLevel(level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'): void {
2092
+ this.provider.setLevel(level)
2093
+ }
2094
+
2095
+ /**
2096
+ * Get the current global log level.
2097
+ *
2098
+ * @returns The result.
2099
+ */
2100
+ getLevel(): string {
2101
+ return this.provider.getLevel()
2102
+ }
2103
+
2104
+ /**
2105
+ * Enable logging.
2106
+ */
2107
+ enable(): void {
2108
+ this.provider.enable()
2109
+ }
2110
+
2111
+ /**
2112
+ * Disable logging.
2113
+ */
2114
+ disable(): void {
2115
+ this.provider.disable()
2116
+ }
2117
+
2118
+ /**
2119
+ * Check if logging is enabled.
2120
+ *
2121
+ * @returns Whether logging is enabled
2122
+ */
2123
+ isEnabled(): boolean {
2124
+ return this.provider.isEnabled()
2125
+ }
2126
+ }
2127
+ ```
2128
+
2129
+ #### `MoleculeRouterService`
2130
+
2131
+ Angular service for routing.
2132
+
2133
+ Wraps molecule router and exposes state as RxJS observables.
2134
+
2135
+ ```typescript
2136
+ @Injectable({
2137
+ providedIn: 'root',
2138
+ })
2139
+ /**
2140
+ * Molecule Router Service class.
2141
+ */
2142
+ export class MoleculeRouterService implements OnDestroy {
2143
+ private locationSubject: BehaviorSubject<RouteLocation>
2144
+ private paramsSubject: BehaviorSubject<RouteParams>
2145
+ private querySubject: BehaviorSubject<QueryParams>
2146
+ private unsubscribe: (() => void) | null = null
2147
+
2148
+ /**
2149
+ * Observable of the current location.
2150
+ */
2151
+ location$: Observable<RouteLocation>
2152
+
2153
+ /**
2154
+ * Observable of route parameters.
2155
+ */
2156
+ params$: Observable<RouteParams>
2157
+
2158
+ /**
2159
+ * Observable of query parameters.
2160
+ */
2161
+ query$: Observable<QueryParams>
2162
+
1240
2163
  /**
1241
- * Returns the currently active theme.
2164
+ * Observable of the current pathname.
1242
2165
  */
1243
- getTheme(): Theme
2166
+ pathname$: Observable<string>
2167
+
2168
+ constructor(@Inject(ROUTER) private router: Router) {
2169
+ this.locationSubject = new BehaviorSubject<RouteLocation>(router.getLocation())
2170
+ this.paramsSubject = new BehaviorSubject<RouteParams>(router.getParams())
2171
+ this.querySubject = new BehaviorSubject<QueryParams>(router.getQuery())
2172
+
2173
+ this.unsubscribe = router.subscribe(() => {
2174
+ this.locationSubject.next(router.getLocation())
2175
+ this.paramsSubject.next(router.getParams())
2176
+ this.querySubject.next(router.getQuery())
2177
+ })
2178
+
2179
+ this.location$ = this.locationSubject.asObservable()
2180
+ this.params$ = this.paramsSubject.asObservable()
2181
+ this.query$ = this.querySubject.asObservable()
2182
+
2183
+ this.pathname$ = this.location$.pipe(
2184
+ map((loc) => loc.pathname),
2185
+ distinctUntilChanged(),
2186
+ )
2187
+ }
2188
+
1244
2189
  /**
1245
- * Sets the active theme by reference or by name.
2190
+ * Get the current location snapshot.
2191
+ * @returns The result.
2192
+ */
2193
+ get location(): RouteLocation {
2194
+ return this.router.getLocation()
2195
+ }
2196
+
2197
+ /**
2198
+ * Get the current params snapshot.
2199
+ * @returns The result.
2200
+ */
2201
+ get params(): RouteParams {
2202
+ return this.router.getParams()
2203
+ }
2204
+
2205
+ /**
2206
+ * Get the current query snapshot.
2207
+ * @returns The result.
2208
+ */
2209
+ get query(): QueryParams {
2210
+ return this.router.getQuery()
2211
+ }
2212
+
2213
+ /**
2214
+ * Navigate to a path.
1246
2215
  *
1247
- * @param theme - A `Theme` object or a theme name string to activate.
2216
+ * @param path - Target path
2217
+ * @param options - Navigation options
1248
2218
  */
1249
- setTheme(theme: Theme | string): void
2219
+ navigate(path: string, options?: NavigateOptions): void {
2220
+ this.router.navigate(path, options)
2221
+ }
2222
+
1250
2223
  /**
1251
- * Toggles between light and dark mode for the active theme.
2224
+ * Navigate to a named route.
2225
+ *
2226
+ * @param name - Route name
2227
+ * @param params - Route parameters
2228
+ * @param query - Query parameters
2229
+ * @param options - Navigation options
1252
2230
  */
1253
- toggleMode(): void
2231
+ navigateTo(
2232
+ name: string,
2233
+ params?: RouteParams,
2234
+ query?: QueryParams,
2235
+ options?: NavigateOptions,
2236
+ ): void {
2237
+ this.router.navigateTo(name, params, query, options)
2238
+ }
2239
+
1254
2240
  /**
1255
- * Subscribes to theme changes. The callback fires whenever
1256
- * `setTheme()` or `toggleMode()` is called.
2241
+ * Go back in history.
2242
+ */
2243
+ back(): void {
2244
+ this.router.back()
2245
+ }
2246
+
2247
+ /**
2248
+ * Go forward in history.
2249
+ */
2250
+ forward(): void {
2251
+ this.router.forward()
2252
+ }
2253
+
2254
+ /**
2255
+ * Check if a path is currently active.
1257
2256
  *
1258
- * @param callback - Invoked with the new theme after each change.
1259
- * @returns An unsubscribe function.
2257
+ * @param path - Path to check
2258
+ * @param exact - Whether to match exactly
2259
+ * @returns The result.
1260
2260
  */
1261
- subscribe(callback: (theme: Theme) => void): () => void
2261
+ isActive(path: string, exact = false): boolean {
2262
+ return this.router.isActive(path, exact)
2263
+ }
2264
+
1262
2265
  /**
1263
- * Returns all registered themes. Optional — not all providers
1264
- * support multiple themes.
2266
+ * Cleans up subscriptions and completes subjects on service destruction.
1265
2267
  */
1266
- getThemes?(): Theme[]
2268
+ ngOnDestroy(): void {
2269
+ this.unsubscribe?.()
2270
+ this.locationSubject.complete()
2271
+ this.paramsSubject.complete()
2272
+ this.querySubject.complete()
2273
+ }
1267
2274
  }
1268
2275
  ```
1269
2276
 
1270
- #### `VersionService`
2277
+ #### `MoleculeStateService`
1271
2278
 
1272
- Version service interface.
2279
+ Angular service for state management.
2280
+
2281
+ Wraps molecule state stores and exposes them as RxJS observables.
1273
2282
 
1274
2283
  ```typescript
1275
- interface VersionService {
1276
- state$: Observable<VersionState>
1277
- isUpdateAvailable$: Observable<boolean>
1278
- isChecking$: Observable<boolean>
1279
- isServiceWorkerWaiting$: Observable<boolean>
1280
- newVersion$: Observable<string | undefined>
1281
- getState: () => VersionState
1282
- checkForUpdates: () => Promise<boolean>
1283
- applyUpdate: (options?: { force?: boolean }) => void
1284
- dismissUpdate: () => void
1285
- startPeriodicChecks: (options?: UpdateCheckOptions) => void
1286
- stopPeriodicChecks: () => void
1287
- destroy: () => void
2284
+ @Injectable({
2285
+ providedIn: 'root',
2286
+ })
2287
+ /**
2288
+ * Molecule State Service class.
2289
+ */
2290
+ export class MoleculeStateService implements OnDestroy {
2291
+ private subscriptions = new Map<Store<unknown>, () => void>()
2292
+ private subjects = new Map<Store<unknown>, BehaviorSubject<unknown>>()
2293
+
2294
+ constructor(@Inject(STATE_PROVIDER) private provider: StateProvider) {}
2295
+
2296
+ /**
2297
+ * Get an observable of the full store state.
2298
+ *
2299
+ * @param store - The store to observe
2300
+ * @returns Observable of the store state
2301
+ */
2302
+ observe<T>(store: Store<T>): Observable<T> {
2303
+ return this.getOrCreateSubject(store).asObservable() as Observable<T>
2304
+ }
2305
+
2306
+ /**
2307
+ * Select a piece of state from a store.
2308
+ *
2309
+ * @param store - The store to select from
2310
+ * @param selector - Selector function
2311
+ * @returns Observable of the selected state
2312
+ */
2313
+ select<T, S>(store: Store<T>, selector: (state: T) => S): Observable<S> {
2314
+ return this.observe(store).pipe(map(selector), distinctUntilChanged())
2315
+ }
2316
+
2317
+ /**
2318
+ * Get the current snapshot of a store's state.
2319
+ *
2320
+ * @param store - The store
2321
+ * @returns The result.
2322
+ */
2323
+ snapshot<T>(store: Store<T>): T {
2324
+ return store.getState()
2325
+ }
2326
+
2327
+ /**
2328
+ * Update a store's state.
2329
+ *
2330
+ * @param store - The store to update
2331
+ * @param partial - Partial state or updater function
2332
+ */
2333
+ update<T>(store: Store<T>, partial: Partial<T> | ((state: T) => Partial<T>)): void {
2334
+ store.setState(partial)
2335
+ }
2336
+
2337
+ /**
2338
+ * Gets or creates a BehaviorSubject for the given store.
2339
+ * @param store - The data store to observe.
2340
+ * @returns The BehaviorSubject tracking the store state.
2341
+ */
2342
+ private getOrCreateSubject<T>(store: Store<T>): BehaviorSubject<T> {
2343
+ let subject = this.subjects.get(store as Store<unknown>) as BehaviorSubject<T> | undefined
2344
+
2345
+ if (!subject) {
2346
+ subject = new BehaviorSubject<T>(store.getState())
2347
+ this.subjects.set(store as Store<unknown>, subject as BehaviorSubject<unknown>)
2348
+
2349
+ const unsubscribe = store.subscribe(() => {
2350
+ subject!.next(store.getState())
2351
+ })
2352
+ this.subscriptions.set(store as Store<unknown>, unsubscribe)
2353
+ }
2354
+
2355
+ return subject
2356
+ }
2357
+
2358
+ /**
2359
+ * Cleans up all store subscriptions and completes subjects on service destruction.
2360
+ */
2361
+ ngOnDestroy(): void {
2362
+ // Clean up all subscriptions
2363
+ this.subscriptions.forEach((unsubscribe) => unsubscribe())
2364
+ this.subscriptions.clear()
2365
+
2366
+ // Complete all subjects
2367
+ this.subjects.forEach((subject) => subject.complete())
2368
+ this.subjects.clear()
2369
+ }
1288
2370
  }
1289
2371
  ```
1290
2372
 
1291
- ### Types
2373
+ #### `MoleculeStorageService`
1292
2374
 
1293
- #### `QueryParams`
2375
+ Angular service for storage.
1294
2376
 
1295
- URL query string parameter map (single values or arrays for repeated keys).
2377
+ Wraps molecule storage provider and returns RxJS observables.
1296
2378
 
1297
2379
  ```typescript
1298
- type QueryParams = Record<string, string | string[] | undefined>
2380
+ @Injectable({
2381
+ providedIn: 'root',
2382
+ })
2383
+ /**
2384
+ * Molecule Storage Service class.
2385
+ */
2386
+ export class MoleculeStorageService {
2387
+ constructor(@Inject(STORAGE_PROVIDER) private provider: StorageProvider) {}
2388
+
2389
+ /**
2390
+ * Get a value from storage.
2391
+ *
2392
+ * @param key - Storage key
2393
+ * @returns Observable of the value
2394
+ */
2395
+ get<T>(key: string): Observable<T | null> {
2396
+ return from(this.provider.get<T>(key))
2397
+ }
2398
+
2399
+ /**
2400
+ * Set a value in storage.
2401
+ *
2402
+ * @param key - Storage key
2403
+ * @param value - Value to store
2404
+ * @returns Observable that completes when done
2405
+ */
2406
+ set<T>(key: string, value: T): Observable<void> {
2407
+ return from(this.provider.set(key, value))
2408
+ }
2409
+
2410
+ /**
2411
+ * Remove a value from storage.
2412
+ *
2413
+ * @param key - Storage key
2414
+ * @returns Observable that completes when done
2415
+ */
2416
+ remove(key: string): Observable<void> {
2417
+ return from(this.provider.remove(key))
2418
+ }
2419
+
2420
+ /**
2421
+ * Clear all values from storage.
2422
+ *
2423
+ * @returns Observable that completes when done
2424
+ */
2425
+ clear(): Observable<void> {
2426
+ return from(this.provider.clear())
2427
+ }
2428
+
2429
+ /**
2430
+ * Get all keys in storage.
2431
+ *
2432
+ * @returns Observable of key array
2433
+ */
2434
+ keys(): Observable<string[]> {
2435
+ return from(this.provider.keys())
2436
+ }
2437
+
2438
+ /**
2439
+ * Create a reactive storage value with auto-loading.
2440
+ *
2441
+ * @param key - Storage key
2442
+ * @param defaultValue - Default value if not found
2443
+ * @returns BehaviorSubject that syncs with storage
2444
+ */
2445
+ createValue<T>(key: string, defaultValue: T): BehaviorSubject<StorageValueState<T>> {
2446
+ const state = new BehaviorSubject<StorageValueState<T>>({
2447
+ value: defaultValue,
2448
+ loading: true,
2449
+ error: null,
2450
+ })
2451
+
2452
+ // Load initial value
2453
+ this.get<T>(key).subscribe({
2454
+ next: (value) => {
2455
+ state.next({
2456
+ value: value ?? defaultValue,
2457
+ loading: false,
2458
+ error: null,
2459
+ })
2460
+ },
2461
+ error: (error) => {
2462
+ state.next({
2463
+ value: defaultValue,
2464
+ loading: false,
2465
+ error,
2466
+ })
2467
+ },
2468
+ })
2469
+
2470
+ return state
2471
+ }
2472
+
2473
+ /**
2474
+ * Update a reactive storage value.
2475
+ *
2476
+ * @param key - Storage key
2477
+ * @param value - New value
2478
+ * @param state - State subject to update
2479
+ */
2480
+ updateValue<T>(key: string, value: T, state: BehaviorSubject<StorageValueState<T>>): void {
2481
+ state.next({ ...state.value, loading: true })
2482
+
2483
+ this.set(key, value).subscribe({
2484
+ next: () => {
2485
+ state.next({ value, loading: false, error: null })
2486
+ },
2487
+ error: (error) => {
2488
+ state.next({ ...state.value, loading: false, error })
2489
+ },
2490
+ })
2491
+ }
2492
+ }
1299
2493
  ```
1300
2494
 
1301
- #### `RouteParams`
2495
+ #### `MoleculeThemeService`
1302
2496
 
1303
- URL path parameter key-value map extracted from dynamic route segments (e.g. `{ id: '123' }`).
2497
+ Angular service for theming.
2498
+
2499
+ Wraps molecule theme provider and exposes state as RxJS observables.
1304
2500
 
1305
2501
  ```typescript
1306
- type RouteParams = Record<string, string>
2502
+ @Injectable({
2503
+ providedIn: 'root',
2504
+ })
2505
+ /**
2506
+ * Molecule Theme Service class.
2507
+ */
2508
+ export class MoleculeThemeService implements OnDestroy {
2509
+ private themeSubject: BehaviorSubject<Theme>
2510
+ private themeNameSubject: BehaviorSubject<string>
2511
+ private unsubscribe: (() => void) | null = null
2512
+
2513
+ /**
2514
+ * Observable of the current theme.
2515
+ */
2516
+ theme$: Observable<Theme>
2517
+
2518
+ /**
2519
+ * Observable of the current theme name.
2520
+ */
2521
+ themeName$: Observable<string>
2522
+
2523
+ /**
2524
+ * Observable of the theme mode (light/dark).
2525
+ */
2526
+ mode$: Observable<'light' | 'dark'>
2527
+
2528
+ constructor(@Inject(THEME_PROVIDER) private provider: ThemeProvider) {
2529
+ this.themeSubject = new BehaviorSubject<Theme>(provider.getTheme())
2530
+ this.themeNameSubject = new BehaviorSubject<string>(provider.getTheme().name)
2531
+
2532
+ this.unsubscribe = provider.subscribe(() => {
2533
+ this.themeSubject.next(provider.getTheme())
2534
+ this.themeNameSubject.next(provider.getTheme().name)
2535
+ })
2536
+
2537
+ this.theme$ = this.themeSubject.asObservable()
2538
+ this.themeName$ = this.themeNameSubject.asObservable()
2539
+
2540
+ this.mode$ = this.theme$.pipe(
2541
+ map((theme: Theme) => theme.mode),
2542
+ distinctUntilChanged(),
2543
+ )
2544
+ }
2545
+
2546
+ /**
2547
+ * Get the current theme snapshot.
2548
+ * @returns The result.
2549
+ */
2550
+ get theme(): Theme {
2551
+ return this.provider.getTheme()
2552
+ }
2553
+
2554
+ /**
2555
+ * Get the current theme name snapshot.
2556
+ * @returns The result.
2557
+ */
2558
+ get themeName(): string {
2559
+ return this.provider.getTheme().name
2560
+ }
2561
+
2562
+ /**
2563
+ * Get available themes.
2564
+ * @returns The result.
2565
+ */
2566
+ get themes(): Theme[] {
2567
+ return this.provider.getThemes?.() ?? []
2568
+ }
2569
+
2570
+ /**
2571
+ * Set the current theme.
2572
+ *
2573
+ * @param name - Theme name
2574
+ */
2575
+ setTheme(name: string): void {
2576
+ this.provider.setTheme(name)
2577
+ }
2578
+
2579
+ /**
2580
+ * Toggle to the next theme.
2581
+ */
2582
+ toggleTheme(): void {
2583
+ const themes = this.themes
2584
+ const currentName = this.themeName
2585
+ const currentIndex = themes.findIndex((t: Theme) => t.name === currentName)
2586
+ const nextIndex = (currentIndex + 1) % themes.length
2587
+ this.provider.setTheme(themes[nextIndex])
2588
+ }
2589
+
2590
+ /**
2591
+ * Cleans up subscriptions and completes subjects on service destruction.
2592
+ */
2593
+ ngOnDestroy(): void {
2594
+ this.unsubscribe?.()
2595
+ this.themeSubject.complete()
2596
+ this.themeNameSubject.complete()
2597
+ }
2598
+ }
1307
2599
  ```
1308
2600
 
1309
- ### Classes
2601
+ ### Functions
2602
+
2603
+ #### `bumpLocaleVersion()`
2604
+
2605
+ Bump the locale version signal, causing all template bindings that use
2606
+ the reactive `t()` to re-evaluate on the next change detection cycle.
2607
+
2608
+ Called automatically by the `ENVIRONMENT_INITIALIZER` in `provideMolecule`.
2609
+
2610
+ ```typescript
2611
+ function bumpLocaleVersion(): void
2612
+ ```
2613
+
2614
+ #### `t(key, values, options)`
2615
+
2616
+ Translate a key using the current locale.
2617
+
2618
+ This is a signal-aware wrapper around `@molecule/app-i18n`'s `t()`.
2619
+ Reading the internal locale signal establishes an Angular reactivity
2620
+ dependency, so template bindings that call this function will be
2621
+ re-evaluated when the locale changes.
2622
+
2623
+ ```typescript
2624
+ function t(
2625
+ key: string,
2626
+ values?: InterpolationValues,
2627
+ options?: { defaultValue?: string; count?: number },
2628
+ ): string
2629
+ ```
2630
+
2631
+ - `key` — Translation key
2632
+ - `values` — Interpolation values
2633
+ - `options` — Options (defaultValue, count).
2634
+ - `options.defaultValue` — Fallback string when no translation is found.
2635
+ - `options.count` — Pluralization count.
2636
+
2637
+ **Returns:** Translated string
2638
+
2639
+ #### `provideState(provider)`
2640
+
2641
+ Provide state management.
2642
+
2643
+ ```typescript
2644
+ function provideState(provider: StateProvider): EnvironmentProviders
2645
+ ```
2646
+
2647
+ - `provider` — State provider
2648
+
2649
+ **Returns:** Environment providers
2650
+
2651
+ #### `provideAuth(client)`
2652
+
2653
+ Registers an AuthClient as an Angular environment provider for dependency injection.
2654
+
2655
+ ```typescript
2656
+ function provideAuth(client: AuthClient<T, Record<never, never>>): EnvironmentProviders
2657
+ ```
2658
+
2659
+ - `client` — Auth client
1310
2660
 
1311
- #### `MoleculeAuthService`
2661
+ **Returns:** Environment providers
1312
2662
 
1313
- Angular service for authentication.
2663
+ #### `provideTheme(provider)`
1314
2664
 
1315
- Wraps molecule auth client and exposes state as RxJS observables.
2665
+ Registers a ThemeProvider as an Angular environment provider for dependency injection.
1316
2666
 
1317
- #### `MoleculeFormInstance`
2667
+ ```typescript
2668
+ function provideTheme(provider: ThemeProvider): EnvironmentProviders
2669
+ ```
1318
2670
 
1319
- Wrapper around a FormController that provides RxJS observables and
1320
- synchronous accessors for use in Angular components.
2671
+ - `provider` — Theme provider
1321
2672
 
1322
- #### `MoleculeFormsService`
2673
+ **Returns:** Environment providers
1323
2674
 
1324
- Angular service for form handling.
2675
+ #### `provideRouter(router)`
1325
2676
 
1326
- Wraps molecule form providers and creates form instances that expose
1327
- state as RxJS observables.
2677
+ Registers a Router as an Angular environment provider for dependency injection.
1328
2678
 
1329
- #### `MoleculeHttpService`
2679
+ ```typescript
2680
+ function provideRouter(router: Router): EnvironmentProviders
2681
+ ```
1330
2682
 
1331
- Angular service for HTTP requests.
2683
+ - `router` — Router instance
1332
2684
 
1333
- Wraps molecule HTTP client and returns RxJS observables.
2685
+ **Returns:** Environment providers
1334
2686
 
1335
- #### `MoleculeI18nService`
2687
+ #### `provideI18n(provider)`
1336
2688
 
1337
- Angular service for internationalization.
2689
+ Registers an I18nProvider as an Angular environment provider for dependency injection.
1338
2690
 
1339
- Wraps molecule i18n provider and exposes state as RxJS observables.
2691
+ ```typescript
2692
+ function provideI18n(provider: I18nProvider): EnvironmentProviders
2693
+ ```
1340
2694
 
1341
- #### `MoleculeLoggerService`
2695
+ - `provider` — I18n provider
1342
2696
 
1343
- Angular service for logging.
2697
+ **Returns:** Environment providers
1344
2698
 
1345
- Wraps molecule logger provider.
2699
+ #### `provideHttp(client)`
1346
2700
 
1347
- #### `MoleculeRouterService`
2701
+ Provide HTTP client.
1348
2702
 
1349
- Angular service for routing.
2703
+ ```typescript
2704
+ function provideHttp(client: HttpClient): EnvironmentProviders
2705
+ ```
1350
2706
 
1351
- Wraps molecule router and exposes state as RxJS observables.
2707
+ - `client` — HTTP client
1352
2708
 
1353
- #### `MoleculeStateService`
2709
+ **Returns:** Environment providers
1354
2710
 
1355
- Angular service for state management.
2711
+ #### `provideStorage(provider)`
1356
2712
 
1357
- Wraps molecule state stores and exposes them as RxJS observables.
2713
+ Registers a StorageProvider as an Angular environment provider for dependency injection.
1358
2714
 
1359
- #### `MoleculeStorageService`
2715
+ ```typescript
2716
+ function provideStorage(provider: StorageProvider): EnvironmentProviders
2717
+ ```
1360
2718
 
1361
- Angular service for storage.
2719
+ - `provider` — Storage provider
1362
2720
 
1363
- Wraps molecule storage provider and returns RxJS observables.
2721
+ **Returns:** Environment providers
1364
2722
 
1365
- #### `MoleculeThemeService`
2723
+ #### `provideLogger(provider)`
1366
2724
 
1367
- Angular service for theming.
2725
+ Registers a LoggerProvider as an Angular environment provider for dependency injection.
1368
2726
 
1369
- Wraps molecule theme provider and exposes state as RxJS observables.
2727
+ ```typescript
2728
+ function provideLogger(provider: LoggerProvider): EnvironmentProviders
2729
+ ```
1370
2730
 
1371
- ### Functions
2731
+ - `provider` — Logger provider
1372
2732
 
1373
- #### `bumpLocaleVersion()`
2733
+ **Returns:** Environment providers
1374
2734
 
1375
- Bump the locale version signal, causing all template bindings that use
1376
- the reactive `t()` to re-evaluate on the next change detection cycle.
2735
+ #### `provideMolecule(config)`
1377
2736
 
1378
- Called automatically by the `ENVIRONMENT_INITIALIZER` in `provideMolecule`.
2737
+ Provide all molecule services at once.
1379
2738
 
1380
2739
  ```typescript
1381
- function bumpLocaleVersion(): void
2740
+ function provideMolecule(config: MoleculeModuleConfig): EnvironmentProviders
1382
2741
  ```
1383
2742
 
2743
+ - `config` — Configuration with all providers
2744
+
2745
+ **Returns:** Environment providers
2746
+
1384
2747
  #### `createAsyncState(initialState)`
1385
2748
 
1386
2749
  Creates an async-capable state manager.
@@ -1413,7 +2776,9 @@ function createCapacitorAppState(options?: CapacitorAppOptions): CapacitorAppMan
1413
2776
  Creates a change password state manager with async state tracking.
1414
2777
 
1415
2778
  ```typescript
1416
- function createChangePasswordState(client: AuthClient<UserProfile>): ChangePasswordStateManager
2779
+ function createChangePasswordState(
2780
+ client: AuthClient<UserProfile, Record<never, never>>,
2781
+ ): ChangePasswordStateManager
1417
2782
  ```
1418
2783
 
1419
2784
  - `client` — Auth client
@@ -1435,7 +2800,7 @@ function createDeviceService(): DeviceService
1435
2800
  Creates a login state manager with async state tracking.
1436
2801
 
1437
2802
  ```typescript
1438
- function createLoginState(client: AuthClient<T>): LoginStateManager<T>
2803
+ function createLoginState(client: AuthClient<T, Record<never, never>>): LoginStateManager<T>
1439
2804
  ```
1440
2805
 
1441
2806
  - `client` — Auth client
@@ -1463,7 +2828,9 @@ function createOAuthState(options?: OAuthOptions): OAuthStateManager
1463
2828
  Creates a password reset state manager with async state tracking.
1464
2829
 
1465
2830
  ```typescript
1466
- function createPasswordResetState(client: AuthClient<UserProfile>): PasswordResetStateManager
2831
+ function createPasswordResetState(
2832
+ client: AuthClient<UserProfile, Record<never, never>>,
2833
+ ): PasswordResetStateManager
1467
2834
  ```
1468
2835
 
1469
2836
  - `client` — Auth client
@@ -1507,7 +2874,7 @@ function createPushService(): PushService
1507
2874
  Creates a signup state manager with async state tracking.
1508
2875
 
1509
2876
  ```typescript
1510
- function createSignupState(client: AuthClient<T>): SignupStateManager<T>
2877
+ function createSignupState(client: AuthClient<T, Record<never, never>>): SignupStateManager<T>
1511
2878
  ```
1512
2879
 
1513
2880
  - `client` — Auth client
@@ -1524,155 +2891,38 @@ function createVersionService(): VersionService
1524
2891
 
1525
2892
  **Returns:** The created instance.
1526
2893
 
1527
- #### `provideAuth(client)`
1528
-
1529
- Registers an AuthClient as an Angular environment provider for dependency injection.
1530
-
1531
- ```typescript
1532
- function provideAuth(client: AuthClient<T>): EnvironmentProviders
1533
- ```
1534
-
1535
- - `client` — Auth client
1536
-
1537
- **Returns:** Environment providers
1538
-
1539
- #### `provideHttp(client)`
1540
-
1541
- Provide HTTP client.
1542
-
1543
- ```typescript
1544
- function provideHttp(client: HttpClient): EnvironmentProviders
1545
- ```
1546
-
1547
- - `client` — HTTP client
1548
-
1549
- **Returns:** Environment providers
1550
-
1551
- #### `provideI18n(provider)`
1552
-
1553
- Registers an I18nProvider as an Angular environment provider for dependency injection.
1554
-
1555
- ```typescript
1556
- function provideI18n(provider: I18nProvider): EnvironmentProviders
1557
- ```
1558
-
1559
- - `provider` — I18n provider
1560
-
1561
- **Returns:** Environment providers
1562
-
1563
- #### `provideLogger(provider)`
1564
-
1565
- Registers a LoggerProvider as an Angular environment provider for dependency injection.
1566
-
1567
- ```typescript
1568
- function provideLogger(provider: LoggerProvider): EnvironmentProviders
1569
- ```
1570
-
1571
- - `provider` — Logger provider
1572
-
1573
- **Returns:** Environment providers
1574
-
1575
- #### `provideMolecule(config)`
1576
-
1577
- Provide all molecule services at once.
1578
-
1579
- ```typescript
1580
- function provideMolecule(config: MoleculeModuleConfig): EnvironmentProviders
1581
- ```
1582
-
1583
- - `config` — Configuration with all providers
1584
-
1585
- **Returns:** Environment providers
1586
-
1587
- #### `provideRouter(router)`
1588
-
1589
- Registers a Router as an Angular environment provider for dependency injection.
1590
-
1591
- ```typescript
1592
- function provideRouter(router: Router): EnvironmentProviders
1593
- ```
1594
-
1595
- - `router` — Router instance
1596
-
1597
- **Returns:** Environment providers
1598
-
1599
- #### `provideState(provider)`
1600
-
1601
- Provide state management.
1602
-
1603
- ```typescript
1604
- function provideState(provider: StateProvider): EnvironmentProviders
1605
- ```
1606
-
1607
- - `provider` — State provider
1608
-
1609
- **Returns:** Environment providers
1610
-
1611
- #### `provideStorage(provider)`
1612
-
1613
- Registers a StorageProvider as an Angular environment provider for dependency injection.
1614
-
1615
- ```typescript
1616
- function provideStorage(provider: StorageProvider): EnvironmentProviders
1617
- ```
1618
-
1619
- - `provider` — Storage provider
1620
-
1621
- **Returns:** Environment providers
2894
+ ### Constants
1622
2895
 
1623
- #### `provideTheme(provider)`
2896
+ #### `STATE_PROVIDER`
1624
2897
 
1625
- Registers a ThemeProvider as an Angular environment provider for dependency injection.
2898
+ Injection token for state provider.
1626
2899
 
1627
2900
  ```typescript
1628
- function provideTheme(provider: ThemeProvider): EnvironmentProviders
2901
+ const STATE_PROVIDER: InjectionToken<StateProvider>
1629
2902
  ```
1630
2903
 
1631
- - `provider` — Theme provider
1632
-
1633
- **Returns:** Environment providers
1634
-
1635
- #### `t(key, values, options)`
1636
-
1637
- Translate a key using the current locale.
2904
+ #### `AUTH_CLIENT`
1638
2905
 
1639
- This is a signal-aware wrapper around `@molecule/app-i18n`'s `t()`.
1640
- Reading the internal locale signal establishes an Angular reactivity
1641
- dependency, so template bindings that call this function will be
1642
- re-evaluated when the locale changes.
2906
+ Injection token for auth client.
1643
2907
 
1644
2908
  ```typescript
1645
- function t(
1646
- key: string,
1647
- values?: InterpolationValues,
1648
- options?: { defaultValue?: string; count?: number },
1649
- ): string
2909
+ const AUTH_CLIENT: InjectionToken<AuthClient<unknown, Record<never, never>>>
1650
2910
  ```
1651
2911
 
1652
- - `key` — Translation key
1653
- - `values` — Interpolation values
1654
- - `options` — Options (defaultValue, count).
1655
- - `options.defaultValue` — Fallback string when no translation is found.
1656
- - `options.count` — Pluralization count.
1657
-
1658
- **Returns:** Translated string
1659
-
1660
- ### Constants
1661
-
1662
- #### `AUTH_CLIENT`
2912
+ #### `THEME_PROVIDER`
1663
2913
 
1664
- Injection token for auth client.
2914
+ Injection token for theme provider.
1665
2915
 
1666
2916
  ```typescript
1667
- const AUTH_CLIENT: InjectionToken<AuthClient<unknown>>
2917
+ const THEME_PROVIDER: InjectionToken<ThemeProvider>
1668
2918
  ```
1669
2919
 
1670
- #### `HTTP_CLIENT`
2920
+ #### `ROUTER`
1671
2921
 
1672
- Injection token for HTTP client.
2922
+ Injection token for router.
1673
2923
 
1674
2924
  ```typescript
1675
- const HTTP_CLIENT: InjectionToken<HttpClient>
2925
+ const ROUTER: InjectionToken<Router>
1676
2926
  ```
1677
2927
 
1678
2928
  #### `I18N_PROVIDER`
@@ -1683,28 +2933,12 @@ Injection token for i18n provider.
1683
2933
  const I18N_PROVIDER: InjectionToken<I18nProvider>
1684
2934
  ```
1685
2935
 
1686
- #### `LOGGER_PROVIDER`
1687
-
1688
- Injection token for logger provider.
1689
-
1690
- ```typescript
1691
- const LOGGER_PROVIDER: InjectionToken<LoggerProvider>
1692
- ```
1693
-
1694
- #### `ROUTER`
1695
-
1696
- Injection token for router.
1697
-
1698
- ```typescript
1699
- const ROUTER: InjectionToken<Router>
1700
- ```
1701
-
1702
- #### `STATE_PROVIDER`
2936
+ #### `HTTP_CLIENT`
1703
2937
 
1704
- Injection token for state provider.
2938
+ Injection token for HTTP client.
1705
2939
 
1706
2940
  ```typescript
1707
- const STATE_PROVIDER: InjectionToken<StateProvider>
2941
+ const HTTP_CLIENT: InjectionToken<HttpClient>
1708
2942
  ```
1709
2943
 
1710
2944
  #### `STORAGE_PROVIDER`
@@ -1715,12 +2949,12 @@ Injection token for storage provider.
1715
2949
  const STORAGE_PROVIDER: InjectionToken<StorageProvider>
1716
2950
  ```
1717
2951
 
1718
- #### `THEME_PROVIDER`
2952
+ #### `LOGGER_PROVIDER`
1719
2953
 
1720
- Injection token for theme provider.
2954
+ Injection token for logger provider.
1721
2955
 
1722
2956
  ```typescript
1723
- const THEME_PROVIDER: InjectionToken<ThemeProvider>
2957
+ const LOGGER_PROVIDER: InjectionToken<LoggerProvider>
1724
2958
  ```
1725
2959
 
1726
2960
  ## Injection Notes