@hexdspace/react 0.0.12 → 0.0.13

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/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
2
  import { QueryClient, QueryKey } from '@tanstack/react-query';
3
- import { ResultOk, ResultError } from '@hexdspace/util';
4
- import React, { CSSProperties } from 'react';
3
+ import { ResultOk, ResultError, AsyncResult, Result } from '@hexdspace/util';
4
+ import * as react from 'react';
5
+ import react__default, { CSSProperties, Dispatch, ReactNode } from 'react';
6
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
7
 
6
8
  type NotificationVariant = 'success' | 'warning' | 'error' | 'info';
7
9
  declare const DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
@@ -118,7 +120,7 @@ type NotificationHostProps = {
118
120
  isDark?: () => boolean;
119
121
  theme?: ToastTheme;
120
122
  };
121
- declare const NotificationHost: React.FC<NotificationHostProps>;
123
+ declare const NotificationHost: react__default.FC<NotificationHostProps>;
122
124
 
123
125
  type InstructionContext = {
124
126
  queryClient: QueryClient;
@@ -163,4 +165,214 @@ type ResponsiveMutation<Args, Res> = {
163
165
 
164
166
  declare function useResponsiveMutation<Args, Res>(responsiveMutation: ResponsiveMutation<Args, Res>, queryClient: QueryClient): _tanstack_react_query.UseMutationResult<UIOk<Res>, UIFail, Args, OptimisticSnapshot | undefined>;
165
167
 
166
- export { type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type Instruction, type InstructionContext, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OptimisticSnapshot, type ResolvedToastTheme, type ResponsiveMutation, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, notifierController, resolveToastTheme, ui, useResponsiveMutation };
168
+ type FactoryBuilder<TDeps, TController> = (deps: TDeps) => TController;
169
+ type FactoryDefaults<TDeps> = (overrides: Partial<TDeps>) => TDeps;
170
+ declare function controllerFactory<TDeps, TController>(builder: FactoryBuilder<TDeps, TController>, defaults: FactoryDefaults<TDeps>): (overrides?: Partial<TDeps>) => TController;
171
+
172
+ interface GenericResponse {
173
+ message: string;
174
+ }
175
+
176
+ interface ErrorResponse {
177
+ error: string;
178
+ details?: unknown;
179
+ }
180
+
181
+ interface HttpResponse<T = unknown> {
182
+ data: T;
183
+ status: number;
184
+ headers: Record<string, string>;
185
+ }
186
+
187
+ declare class HttpError extends Error {
188
+ readonly status: number;
189
+ readonly response: ErrorResponse;
190
+ constructor(message: string, status: number, response: ErrorResponse);
191
+ }
192
+
193
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
194
+ interface HttpClient {
195
+ get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
196
+ post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
197
+ put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
198
+ delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
199
+ }
200
+ interface RequestConfig {
201
+ headers?: Record<string, string>;
202
+ timeout?: number;
203
+ }
204
+
205
+ declare class AutoRefreshDecorator implements HttpClient {
206
+ private readonly httpClient;
207
+ private readonly refresh;
208
+ private static inFlightRefresh;
209
+ constructor(httpClient: HttpClient, refresh: (client: HttpClient) => AsyncResult<HttpError, GenericResponse>);
210
+ get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
211
+ post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
212
+ put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
213
+ delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
214
+ private tryRequest;
215
+ private needsRefresh;
216
+ private performRefresh;
217
+ }
218
+
219
+ declare const httpClient: AutoRefreshDecorator;
220
+
221
+ type MockHandler<T = unknown> = (payload: {
222
+ url: string;
223
+ data?: unknown;
224
+ config?: RequestConfig;
225
+ }) => Promise<HttpResponse<T>> | HttpResponse<T>;
226
+ declare class MockHttpClient implements HttpClient {
227
+ private handlers;
228
+ register<T>(method: HttpMethod, url: string, handler: MockHandler<T>): void;
229
+ respondWith<T>(method: HttpMethod, url: string, response: HttpResponse<T>): void;
230
+ respondWithError(method: HttpMethod, url: string, error: Error): void;
231
+ reset(): void;
232
+ get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
233
+ post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
234
+ put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
235
+ delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
236
+ private invoke;
237
+ }
238
+
239
+ interface User {
240
+ id: string;
241
+ email: string;
242
+ }
243
+
244
+ interface LoginUser {
245
+ execute(email: string, password: string): AsyncResult<Error, User>;
246
+ }
247
+
248
+ interface LogoutUser {
249
+ execute(): AsyncResult<Error, null>;
250
+ }
251
+
252
+ interface RegisterUser {
253
+ execute(email: string, password: string): AsyncResult<Error, GenericResponse>;
254
+ }
255
+
256
+ interface GetAuthenticatedUser {
257
+ execute(): AsyncResult<Error, User | null>;
258
+ }
259
+
260
+ interface AuthProvider$1 {
261
+ getAuthenticatedUser(): AsyncResult<Error, User | null>;
262
+ login(email: string, password: string): AsyncResult<Error, User>;
263
+ register(email: string, password: string): AsyncResult<Error, GenericResponse>;
264
+ logout(): AsyncResult<Error, null>;
265
+ }
266
+
267
+ declare class AuthController {
268
+ private readonly loginUser;
269
+ private readonly logoutUser;
270
+ private readonly registerUser;
271
+ private readonly getAuthenticatedUser;
272
+ constructor(loginUser: LoginUser, logoutUser: LogoutUser, registerUser: RegisterUser, getAuthenticatedUser: GetAuthenticatedUser);
273
+ login(email: string, password: string): AsyncResult<Error, User>;
274
+ logout(): AsyncResult<Error, null>;
275
+ register(email: string, password: string): AsyncResult<Error, {
276
+ message: string;
277
+ }>;
278
+ getCurrentUser(): AsyncResult<Error, User | null>;
279
+ }
280
+ type AuthControllerDeps = {
281
+ httpClient: HttpClient;
282
+ authProvider: AuthProvider$1;
283
+ loginUser: LoginUser;
284
+ logoutUser: LogoutUser;
285
+ registerUser: RegisterUser;
286
+ getAuthenticatedUser: GetAuthenticatedUser;
287
+ };
288
+ declare const createAuthController: (overrides?: Partial<AuthControllerDeps>) => AuthController;
289
+ declare const authController: AuthController;
290
+
291
+ type AuthState = {
292
+ status: 'loading';
293
+ } | {
294
+ status: 'authenticated';
295
+ user: User;
296
+ } | {
297
+ status: 'unauthenticated';
298
+ message?: string;
299
+ } | {
300
+ status: 'error';
301
+ error: string;
302
+ };
303
+
304
+ declare function useAuth(): AuthState;
305
+
306
+ declare function useAuthActions(): {
307
+ login: (email: string, password: string) => void;
308
+ logout: () => void;
309
+ register: (email: string, password: string) => Promise<Result<Error, {
310
+ message: string;
311
+ }>>;
312
+ };
313
+
314
+ declare function useAuthedUser(): User;
315
+
316
+ declare function useAuthController(): AuthController;
317
+
318
+ type AuthAction = {
319
+ type: 'REQUEST';
320
+ } | {
321
+ type: 'COMPLETE';
322
+ user: User;
323
+ } | {
324
+ type: 'SUCCESS';
325
+ message: string;
326
+ } | {
327
+ type: 'FAILED';
328
+ error: string;
329
+ } | {
330
+ type: 'LOGOUT';
331
+ };
332
+
333
+ declare function useAuthDispatch(): Dispatch<AuthAction>;
334
+
335
+ declare const AuthStateCtx: react.Context<AuthState | null>;
336
+ declare const AuthDispatchCtx: react.Context<Dispatch<AuthAction> | null>;
337
+ declare function AuthProvider({ children }: {
338
+ readonly children: ReactNode;
339
+ }): react_jsx_runtime.JSX.Element;
340
+
341
+ declare const AuthControllerCtx: react.Context<AuthController | null>;
342
+ type AuthControllerProviderProps = {
343
+ readonly children: ReactNode;
344
+ readonly controller: AuthController;
345
+ };
346
+ declare function AuthControllerProvider({ children, controller }: AuthControllerProviderProps): react_jsx_runtime.JSX.Element;
347
+
348
+ interface InputProps {
349
+ id: string;
350
+ label: string;
351
+ type: string;
352
+ value: string;
353
+ onChange: (e: react__default.ChangeEvent<HTMLInputElement>) => void;
354
+ placeholder: string;
355
+ className?: string;
356
+ }
357
+ declare const AuthFormInputField: react__default.FC<InputProps>;
358
+
359
+ type AuthFixtures = {
360
+ meError?: HttpError;
361
+ loginError?: HttpError;
362
+ registerError?: HttpError;
363
+ logoutError?: Error;
364
+ currentUser?: User | null;
365
+ };
366
+ declare class MockAuthHttpClient extends MockHttpClient {
367
+ private fixtures;
368
+ private readonly delayMs;
369
+ constructor(initial?: AuthFixtures, delayMs?: number);
370
+ setCurrentUser(user: User | null): void;
371
+ setLoginError(error: HttpError | undefined): void;
372
+ setRegisterError(error: HttpError | undefined): void;
373
+ setMeError(error: HttpError | undefined): void;
374
+ setLogoutError(error: Error | undefined): void;
375
+ private registerAuthRoutes;
376
+ }
377
+
378
+ export { AuthController, AuthControllerCtx, type AuthControllerDeps, AuthControllerProvider, AuthDispatchCtx, AuthFormInputField, AuthProvider, type AuthState, AuthStateCtx, type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type ErrorResponse, type GenericResponse, HttpError, type HttpResponse, type Instruction, type InstructionContext, MockAuthHttpClient, MockHttpClient, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OptimisticSnapshot, type ResolvedToastTheme, type ResponsiveMutation, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, type User, authController, controllerFactory, createAuthController, httpClient as fetchHttpClient, notifierController, resolveToastTheme, ui, useAuth, useAuthActions, useAuthController, useAuthDispatch, useAuthedUser, useResponsiveMutation };
package/dist/index.js CHANGED
@@ -426,12 +426,597 @@ var executeEffects = (effects, ctx) => {
426
426
  }
427
427
  });
428
428
  };
429
+
430
+ // src/util/controller-factory.ts
431
+ function controllerFactory(builder, defaults) {
432
+ return (overrides = {}) => {
433
+ const resolvedDeps = defaults(overrides);
434
+ return builder({ ...resolvedDeps, ...overrides });
435
+ };
436
+ }
437
+
438
+ // src/feature/http/entity/http-error.ts
439
+ var HttpError = class extends Error {
440
+ constructor(message, status, response) {
441
+ super(message);
442
+ this.status = status;
443
+ this.response = response;
444
+ this.name = "HttpError";
445
+ }
446
+ };
447
+
448
+ // src/feature/http/infra/fetch-http-client.ts
449
+ import { nok, ok } from "@hexdspace/util";
450
+
451
+ // src/feature/http/infra/auto-refresh-decorator.ts
452
+ var AutoRefreshDecorator = class _AutoRefreshDecorator {
453
+ constructor(httpClient2, refresh2) {
454
+ this.httpClient = httpClient2;
455
+ this.refresh = refresh2;
456
+ }
457
+ static inFlightRefresh = null;
458
+ async get(url, config) {
459
+ return this.tryRequest(() => this.httpClient.get(url, config));
460
+ }
461
+ async post(url, data, config) {
462
+ return this.tryRequest(() => this.httpClient.post(url, data, config));
463
+ }
464
+ async put(url, data, config) {
465
+ return this.tryRequest(() => this.httpClient.put(url, data, config));
466
+ }
467
+ async delete(url, config) {
468
+ return this.tryRequest(() => this.httpClient.delete(url, config));
469
+ }
470
+ async tryRequest(fn) {
471
+ try {
472
+ return await fn();
473
+ } catch (e) {
474
+ if (e instanceof HttpError && this.needsRefresh(e.status)) {
475
+ await this.performRefresh();
476
+ return fn();
477
+ }
478
+ throw e;
479
+ }
480
+ }
481
+ needsRefresh(status) {
482
+ return [401, 403, 419, 440].includes(status);
483
+ }
484
+ async performRefresh() {
485
+ if (_AutoRefreshDecorator.inFlightRefresh) {
486
+ return _AutoRefreshDecorator.inFlightRefresh;
487
+ }
488
+ _AutoRefreshDecorator.inFlightRefresh = (async () => {
489
+ const res = await this.refresh(this.httpClient);
490
+ if (!res.ok) {
491
+ throw new HttpError("Token refresh failed", res.error.status, res.error.response);
492
+ }
493
+ })().finally(() => {
494
+ _AutoRefreshDecorator.inFlightRefresh = null;
495
+ });
496
+ return _AutoRefreshDecorator.inFlightRefresh;
497
+ }
498
+ };
499
+
500
+ // src/feature/http/infra/fetch-http-client.ts
501
+ var FetchHttpClient = class {
502
+ baseURL;
503
+ constructor(baseURL = "/api") {
504
+ this.baseURL = baseURL;
505
+ }
506
+ async get(url, config) {
507
+ return this.doRequest("GET", url, void 0, config);
508
+ }
509
+ async post(url, data, config) {
510
+ return this.doRequest("POST", url, data, config);
511
+ }
512
+ async put(url, data, config) {
513
+ return this.doRequest("PUT", url, data, config);
514
+ }
515
+ async delete(url, config) {
516
+ return this.doRequest("DELETE", url, void 0, config);
517
+ }
518
+ async doRequest(method, url, body, config) {
519
+ const isFormData = body instanceof FormData;
520
+ const headers = { ...config?.headers };
521
+ let parsedBody;
522
+ if (body !== void 0) {
523
+ parsedBody = isFormData ? body : JSON.stringify(body);
524
+ }
525
+ if (!isFormData) {
526
+ headers["Content-Type"] ??= "application/json";
527
+ }
528
+ try {
529
+ const res = await fetch(this.resolve(url), {
530
+ method,
531
+ body: parsedBody,
532
+ headers: { ...headers, ...config?.headers },
533
+ credentials: "include",
534
+ signal: config?.signal
535
+ });
536
+ const data = await (res.headers.get("content-type")?.includes("application/json") ? res.json() : res.text());
537
+ if (!res.ok) throw new HttpError(res.statusText, res.status, data);
538
+ return {
539
+ data,
540
+ status: res.status,
541
+ headers: Object.fromEntries(res.headers.entries())
542
+ };
543
+ } catch (err) {
544
+ if (err instanceof HttpError) throw err;
545
+ const message = err instanceof Error ? err.message : "HTTP request failed";
546
+ throw new HttpError(message, 0, { error: JSON.stringify(err) });
547
+ }
548
+ }
549
+ resolve(url) {
550
+ return url.startsWith("http") ? url : `${this.baseURL}${url}`;
551
+ }
552
+ };
553
+ async function refresh(httpClient2) {
554
+ try {
555
+ const res = await httpClient2.get("/auth/refresh");
556
+ return ok(res.data);
557
+ } catch (err) {
558
+ if (err instanceof HttpError) {
559
+ return nok(err);
560
+ } else {
561
+ return nok(new HttpError("Unknown error during refresh", 0, { error: JSON.stringify(err) }));
562
+ }
563
+ }
564
+ }
565
+ var httpClient = new AutoRefreshDecorator(new FetchHttpClient(), refresh);
566
+
567
+ // src/feature/http/infra/mock-http-client.ts
568
+ function getKey(method, url) {
569
+ return `${method}:${url}`;
570
+ }
571
+ var MockHttpClient = class {
572
+ handlers = /* @__PURE__ */ new Map();
573
+ register(method, url, handler) {
574
+ this.handlers.set(getKey(method, url), handler);
575
+ }
576
+ respondWith(method, url, response) {
577
+ this.register(method, url, () => response);
578
+ }
579
+ respondWithError(method, url, error) {
580
+ this.register(method, url, () => Promise.reject(error));
581
+ }
582
+ reset() {
583
+ this.handlers.clear();
584
+ }
585
+ get(url, config) {
586
+ return this.invoke("GET", url, void 0, config);
587
+ }
588
+ post(url, data, config) {
589
+ return this.invoke("POST", url, data, config);
590
+ }
591
+ put(url, data, config) {
592
+ return this.invoke("PUT", url, data, config);
593
+ }
594
+ delete(url, config) {
595
+ return this.invoke("DELETE", url, void 0, config);
596
+ }
597
+ invoke(method, url, data, config) {
598
+ const handler = this.handlers.get(getKey(method, url));
599
+ if (!handler) {
600
+ return Promise.reject(new Error(`No mock handler registered for ${method} ${url}`));
601
+ }
602
+ const result = handler({ url, data, config });
603
+ return Promise.resolve(result);
604
+ }
605
+ };
606
+
607
+ // src/feature/auth/infra/http-cookie-auth-provider.ts
608
+ import { nok as nok2, ok as ok2 } from "@hexdspace/util";
609
+ var HttpCookieAuthProvider = class {
610
+ constructor(httpClient2) {
611
+ this.httpClient = httpClient2;
612
+ }
613
+ async getAuthenticatedUser() {
614
+ try {
615
+ const response = await this.httpClient.get("/auth/me");
616
+ return ok2(response.data.user);
617
+ } catch (err) {
618
+ if (isUnauthorizedError(err)) {
619
+ return ok2(null);
620
+ } else if (err instanceof Error) {
621
+ return nok2(err);
622
+ } else {
623
+ return nok2(new Error("Unknown error during authentication"));
624
+ }
625
+ }
626
+ }
627
+ async login(email, password) {
628
+ try {
629
+ const body = { email, password };
630
+ await this.httpClient.post("/auth/login", body);
631
+ const userResponse = await this.httpClient.get("/auth/me");
632
+ return ok2(userResponse.data.user);
633
+ } catch (err) {
634
+ if (err instanceof Error) {
635
+ return nok2(err);
636
+ } else {
637
+ return nok2(new Error("Unknown error during authentication"));
638
+ }
639
+ }
640
+ }
641
+ async register(email, password) {
642
+ try {
643
+ const res = await this.httpClient.post("/auth/register", { email, password });
644
+ return ok2(res.data);
645
+ } catch (err) {
646
+ if (err instanceof Error) {
647
+ return nok2(err);
648
+ } else {
649
+ return nok2(new Error("Unknown error during authentication"));
650
+ }
651
+ }
652
+ }
653
+ async logout() {
654
+ try {
655
+ await this.httpClient.post("/auth/logout");
656
+ return ok2(null);
657
+ } catch (err) {
658
+ if (err instanceof Error) {
659
+ return nok2(err);
660
+ } else {
661
+ return nok2(new Error("Unknown error during logout"));
662
+ }
663
+ }
664
+ }
665
+ };
666
+ function isUnauthorizedError(error) {
667
+ return error instanceof HttpError && error.status === 401;
668
+ }
669
+
670
+ // src/feature/auth/application/use-case/login-user-use-case.ts
671
+ var LoginUserUseCase = class {
672
+ constructor(authProvider) {
673
+ this.authProvider = authProvider;
674
+ }
675
+ execute(email, password) {
676
+ return this.authProvider.login(email, password);
677
+ }
678
+ };
679
+
680
+ // src/feature/auth/application/use-case/logout-user-use-case.ts
681
+ var LogoutUserUseCase = class {
682
+ constructor(authProvider) {
683
+ this.authProvider = authProvider;
684
+ }
685
+ execute() {
686
+ return this.authProvider.logout();
687
+ }
688
+ };
689
+
690
+ // src/feature/auth/application/use-case/register-user-use-case.ts
691
+ var RegisterUserUseCase = class {
692
+ constructor(authProvider) {
693
+ this.authProvider = authProvider;
694
+ }
695
+ execute(email, password) {
696
+ return this.authProvider.register(email, password);
697
+ }
698
+ };
699
+
700
+ // src/feature/auth/application/use-case/get-authenticated-user-use-case.ts
701
+ var GetAuthenticatedUserUseCase = class {
702
+ constructor(authProvider) {
703
+ this.authProvider = authProvider;
704
+ }
705
+ execute() {
706
+ return this.authProvider.getAuthenticatedUser();
707
+ }
708
+ };
709
+
710
+ // src/feature/auth/interface/controller/auth-controller.ts
711
+ var AuthController = class {
712
+ constructor(loginUser, logoutUser, registerUser, getAuthenticatedUser) {
713
+ this.loginUser = loginUser;
714
+ this.logoutUser = logoutUser;
715
+ this.registerUser = registerUser;
716
+ this.getAuthenticatedUser = getAuthenticatedUser;
717
+ }
718
+ async login(email, password) {
719
+ return this.loginUser.execute(email, password);
720
+ }
721
+ async logout() {
722
+ return this.logoutUser.execute();
723
+ }
724
+ async register(email, password) {
725
+ return this.registerUser.execute(email, password);
726
+ }
727
+ async getCurrentUser() {
728
+ return this.getAuthenticatedUser.execute();
729
+ }
730
+ };
731
+ var createAuthController = controllerFactory(
732
+ (deps) => new AuthController(
733
+ deps.loginUser,
734
+ deps.logoutUser,
735
+ deps.registerUser,
736
+ deps.getAuthenticatedUser
737
+ ),
738
+ (overrides) => {
739
+ const httpClient2 = overrides.httpClient ?? httpClient;
740
+ const authProvider = overrides.authProvider ?? new HttpCookieAuthProvider(httpClient2);
741
+ return {
742
+ httpClient: httpClient2,
743
+ authProvider,
744
+ loginUser: overrides.loginUser ?? new LoginUserUseCase(authProvider),
745
+ logoutUser: overrides.logoutUser ?? new LogoutUserUseCase(authProvider),
746
+ registerUser: overrides.registerUser ?? new RegisterUserUseCase(authProvider),
747
+ getAuthenticatedUser: overrides.getAuthenticatedUser ?? new GetAuthenticatedUserUseCase(authProvider)
748
+ };
749
+ }
750
+ );
751
+ var authController = createAuthController();
752
+
753
+ // src/feature/auth/interface/web/react/hook/useAuth.tsx
754
+ import { useContext as useContext2 } from "react";
755
+
756
+ // src/feature/auth/interface/web/react/AuthProvider.tsx
757
+ import { createContext as createContext2, useEffect as useEffect2, useReducer } from "react";
758
+
759
+ // src/feature/auth/interface/web/react/state/auth-reducer.ts
760
+ function authStateReducer(_state, action) {
761
+ switch (action.type) {
762
+ case "REQUEST":
763
+ return { status: "loading" };
764
+ case "SUCCESS":
765
+ return { status: "unauthenticated", message: action.message };
766
+ case "FAILED":
767
+ return { status: "error", error: action.error };
768
+ case "LOGOUT":
769
+ return { status: "unauthenticated" };
770
+ case "COMPLETE":
771
+ return { status: "authenticated", user: action.user };
772
+ default:
773
+ throw new Error(`Unknown state: ${JSON.stringify(action)}`);
774
+ }
775
+ }
776
+
777
+ // src/feature/auth/interface/web/react/hook/useAuthController.tsx
778
+ import { useContext } from "react";
779
+
780
+ // src/feature/auth/interface/web/react/AuthControllerProvider.tsx
781
+ import { createContext } from "react";
782
+ import { jsx as jsx3 } from "react/jsx-runtime";
783
+ var AuthControllerCtx = createContext(null);
784
+ function AuthControllerProvider({ children, controller }) {
785
+ return /* @__PURE__ */ jsx3(AuthControllerCtx.Provider, { value: controller, children });
786
+ }
787
+
788
+ // src/feature/auth/interface/web/react/hook/useAuthController.tsx
789
+ function useAuthController() {
790
+ const authController2 = useContext(AuthControllerCtx);
791
+ if (!authController2) {
792
+ throw new Error("useAuthController must be used inside <AuthControllerProvider>");
793
+ }
794
+ return authController2;
795
+ }
796
+
797
+ // src/feature/auth/interface/web/react/AuthProvider.tsx
798
+ import { jsx as jsx4 } from "react/jsx-runtime";
799
+ var AuthStateCtx = createContext2(null);
800
+ var AuthDispatchCtx = createContext2(null);
801
+ function AuthProvider({ children }) {
802
+ const [state, dispatch] = useReducer(authStateReducer, {
803
+ status: "loading"
804
+ });
805
+ const authController2 = useAuthController();
806
+ useEffect2(() => {
807
+ const fetchActiveUser = async () => {
808
+ const res = await authController2.getCurrentUser();
809
+ if (res.ok) {
810
+ if (res.value) {
811
+ dispatch({ type: "COMPLETE", user: res.value });
812
+ } else {
813
+ dispatch({ type: "LOGOUT" });
814
+ }
815
+ } else {
816
+ dispatch({ type: "FAILED", error: res.error.message });
817
+ }
818
+ };
819
+ void fetchActiveUser();
820
+ }, [dispatch, authController2]);
821
+ return /* @__PURE__ */ jsx4(AuthDispatchCtx.Provider, { value: dispatch, children: /* @__PURE__ */ jsx4(AuthStateCtx.Provider, { value: state, children }) });
822
+ }
823
+
824
+ // src/feature/auth/interface/web/react/hook/useAuth.tsx
825
+ function useAuth() {
826
+ const authState = useContext2(AuthStateCtx);
827
+ if (!authState) {
828
+ throw new Error("useAuth must be used inside <AuthProvider>");
829
+ }
830
+ return authState;
831
+ }
832
+
833
+ // src/feature/auth/interface/web/react/hook/useAuthActions.tsx
834
+ import { useCallback as useCallback2 } from "react";
835
+
836
+ // src/feature/auth/interface/web/react/hook/useAuthDispatch.tsx
837
+ import { useContext as useContext3 } from "react";
838
+ function useAuthDispatch() {
839
+ const dispatch = useContext3(AuthDispatchCtx);
840
+ if (!dispatch) {
841
+ throw new Error("useAuthDispatch must be used inside <AuthProvider>");
842
+ }
843
+ return dispatch;
844
+ }
845
+
846
+ // src/feature/auth/interface/web/react/hook/useAuthActions.tsx
847
+ function useAuthActions() {
848
+ const dispatch = useAuthDispatch();
849
+ const authController2 = useAuthController();
850
+ const login = useCallback2((email, password) => {
851
+ dispatch({ type: "REQUEST" });
852
+ authController2.login(email, password).then((res) => {
853
+ dispatchLoginResult(res, dispatch);
854
+ });
855
+ }, [dispatch, authController2]);
856
+ const logout = useCallback2(() => {
857
+ authController2.logout().then(() => dispatch({ type: "LOGOUT" }));
858
+ }, [dispatch, authController2]);
859
+ const register = useCallback2(async (email, password) => {
860
+ dispatch({ type: "REQUEST" });
861
+ const res = await authController2.register(email, password);
862
+ dispatchRegisterResult(res, dispatch);
863
+ return res;
864
+ }, [dispatch, authController2]);
865
+ return { login, logout, register };
866
+ }
867
+ function dispatchLoginResult(res, dispatch) {
868
+ if (res.ok) {
869
+ dispatch({ type: "COMPLETE", user: res.value });
870
+ } else {
871
+ dispatch({ type: "FAILED", error: res.error.message });
872
+ }
873
+ }
874
+ function dispatchRegisterResult(res, dispatch) {
875
+ if (res.ok) {
876
+ dispatch({ type: "SUCCESS", message: res.value.message });
877
+ } else {
878
+ dispatch({ type: "FAILED", error: res.error.message });
879
+ }
880
+ }
881
+
882
+ // src/feature/auth/interface/web/react/hook/useAuthedUser.tsx
883
+ function useAuthedUser() {
884
+ const auth = useAuth();
885
+ if (auth.status !== "authenticated") {
886
+ throw new Error("useAuthedUser called outside of an authenticated route");
887
+ }
888
+ return auth.user;
889
+ }
890
+
891
+ // src/feature/auth/interface/web/react/AuthFormInputField.tsx
892
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
893
+ var inputLabelStyles = {
894
+ display: "block",
895
+ fontSize: "small",
896
+ fontWeight: "bolder",
897
+ marginBottom: "4px"
898
+ };
899
+ var inputFieldStyles = {
900
+ display: "block",
901
+ width: "100%",
902
+ padding: "0.5rem 0.75rem"
903
+ };
904
+ var AuthFormInputField = ({ id, label, type, value, onChange, placeholder, className }) => {
905
+ return /* @__PURE__ */ jsxs2("div", { children: [
906
+ /* @__PURE__ */ jsx5("label", { htmlFor: id, style: inputLabelStyles, children: label }),
907
+ /* @__PURE__ */ jsx5(
908
+ "input",
909
+ {
910
+ id,
911
+ name: id,
912
+ type,
913
+ required: true,
914
+ value,
915
+ onChange,
916
+ className,
917
+ placeholder,
918
+ style: inputFieldStyles
919
+ }
920
+ )
921
+ ] });
922
+ };
923
+
924
+ // src/feature/auth/infra/mock-auth-http-client.ts
925
+ function httpResponse(data, status = 200) {
926
+ return { data, status, headers: {} };
927
+ }
928
+ function unauthorized() {
929
+ return new HttpError("Unauthorized", 401, { error: "Unauthorized" });
930
+ }
931
+ var MockAuthHttpClient = class extends MockHttpClient {
932
+ fixtures;
933
+ delayMs;
934
+ constructor(initial, delayMs = 0) {
935
+ super();
936
+ this.fixtures = { currentUser: null, ...initial };
937
+ this.delayMs = delayMs;
938
+ this.registerAuthRoutes();
939
+ }
940
+ setCurrentUser(user) {
941
+ this.fixtures.currentUser = user;
942
+ }
943
+ setLoginError(error) {
944
+ this.fixtures.loginError = error;
945
+ }
946
+ setRegisterError(error) {
947
+ this.fixtures.registerError = error;
948
+ }
949
+ setMeError(error) {
950
+ this.fixtures.meError = error;
951
+ }
952
+ setLogoutError(error) {
953
+ this.fixtures.logoutError = error;
954
+ }
955
+ registerAuthRoutes() {
956
+ this.register("GET", "/auth/me", async () => {
957
+ await new Promise((resolve) => setTimeout(resolve, this.delayMs));
958
+ if (this.fixtures.meError) {
959
+ throw this.fixtures.meError;
960
+ }
961
+ if (this.fixtures.currentUser) {
962
+ return httpResponse({ user: this.fixtures.currentUser });
963
+ }
964
+ throw unauthorized();
965
+ });
966
+ this.register("POST", "/auth/login", async (payload) => {
967
+ await new Promise((resolve) => setTimeout(resolve, this.delayMs));
968
+ if (this.fixtures.loginError) {
969
+ throw this.fixtures.loginError;
970
+ }
971
+ const body = payload.data;
972
+ this.fixtures.currentUser ||= {
973
+ id: "mock-user",
974
+ email: body.email
975
+ };
976
+ return httpResponse({ message: "Logged in" });
977
+ });
978
+ this.register("POST", "/auth/register", async () => {
979
+ await new Promise((resolve) => setTimeout(resolve, this.delayMs));
980
+ if (this.fixtures.registerError) {
981
+ throw this.fixtures.registerError;
982
+ }
983
+ return httpResponse({ message: "Registered" });
984
+ });
985
+ this.register("POST", "/auth/logout", async () => {
986
+ await new Promise((resolve) => setTimeout(resolve, this.delayMs));
987
+ if (this.fixtures.logoutError) {
988
+ throw this.fixtures.logoutError;
989
+ }
990
+ this.fixtures.currentUser = null;
991
+ return httpResponse(null);
992
+ });
993
+ }
994
+ };
429
995
  export {
996
+ AuthController,
997
+ AuthControllerCtx,
998
+ AuthControllerProvider,
999
+ AuthDispatchCtx,
1000
+ AuthFormInputField,
1001
+ AuthProvider,
1002
+ AuthStateCtx,
430
1003
  DEFAULT_NOTIFICATION_CHANNEL,
1004
+ HttpError,
1005
+ MockAuthHttpClient,
1006
+ MockHttpClient,
431
1007
  NotificationHost,
432
1008
  NotifierController,
1009
+ authController,
1010
+ controllerFactory,
1011
+ createAuthController,
1012
+ httpClient as fetchHttpClient,
433
1013
  notifierController,
434
1014
  resolveToastTheme,
435
1015
  ui,
1016
+ useAuth,
1017
+ useAuthActions,
1018
+ useAuthController,
1019
+ useAuthDispatch,
1020
+ useAuthedUser,
436
1021
  useResponsiveMutation
437
1022
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexdspace/react",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "react-router-dom": "^7.10.1",
25
25
  "react-toastify": "^11.0.5",
26
26
  "uuid": "^13.0.0",
27
- "@hexdspace/util": "0.0.24"
27
+ "@hexdspace/util": "0.0.25"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@tanstack/react-query": "^5.90.11",