@c.a.f/infrastructure-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.build/DevTools/index.d.ts +3 -0
  2. package/.build/DevTools/index.js +3 -0
  3. package/.build/DevTools/useCAFDevTools.d.ts +43 -0
  4. package/.build/DevTools/useCAFDevTools.js +74 -0
  5. package/.build/DevTools/usePlocDevTools.d.ts +23 -0
  6. package/.build/DevTools/usePlocDevTools.js +58 -0
  7. package/.build/DevTools/useUseCaseDevTools.d.ts +18 -0
  8. package/.build/DevTools/useUseCaseDevTools.js +25 -0
  9. package/.build/ErrorBoundary/CAFErrorBoundary.d.ts +36 -0
  10. package/.build/ErrorBoundary/CAFErrorBoundary.js +99 -0
  11. package/.build/ErrorBoundary/ErrorContext.d.ts +18 -0
  12. package/.build/ErrorBoundary/ErrorContext.js +11 -0
  13. package/.build/ErrorBoundary/index.d.ts +2 -0
  14. package/.build/ErrorBoundary/index.js +2 -0
  15. package/.build/Ploc/index.d.ts +1 -0
  16. package/.build/Ploc/index.js +1 -0
  17. package/.build/Ploc/usePloc.d.ts +15 -0
  18. package/.build/Ploc/usePloc.js +26 -0
  19. package/.build/RouteManager/RouteHandler.d.ts +12 -0
  20. package/.build/RouteManager/RouteHandler.js +19 -0
  21. package/.build/RouteManager/RouteManger.d.ts +10 -0
  22. package/.build/RouteManager/RouteManger.js +16 -0
  23. package/.build/RouteManager/index.d.ts +1 -0
  24. package/.build/RouteManager/index.js +1 -0
  25. package/.build/RouteManager/useRouteManager.d.ts +8 -0
  26. package/.build/RouteManager/useRouteManager.js +15 -0
  27. package/.build/RouteManager/useRouteRepository.d.ts +6 -0
  28. package/.build/RouteManager/useRouteRepository.js +20 -0
  29. package/.build/UseCase/index.d.ts +1 -0
  30. package/.build/UseCase/index.js +1 -0
  31. package/.build/UseCase/useUseCase.d.ts +26 -0
  32. package/.build/UseCase/useUseCase.js +83 -0
  33. package/.build/index.d.ts +7 -0
  34. package/.build/index.js +7 -0
  35. package/.build/vitest.config.d.ts +2 -0
  36. package/.build/vitest.config.js +22 -0
  37. package/.build/vitest.setup.d.ts +1 -0
  38. package/.build/vitest.setup.js +1 -0
  39. package/README.md +319 -0
  40. package/package.json +57 -0
@@ -0,0 +1,3 @@
1
+ export { usePlocDevTools } from "./usePlocDevTools";
2
+ export { useUseCaseDevTools } from "./useUseCaseDevTools";
3
+ export { useCAFDevTools, useTrackPloc, type CAFDevToolsContext } from "./useCAFDevTools";
@@ -0,0 +1,3 @@
1
+ export { usePlocDevTools } from "./usePlocDevTools";
2
+ export { useUseCaseDevTools } from "./useUseCaseDevTools";
3
+ export { useCAFDevTools, useTrackPloc } from "./useCAFDevTools";
@@ -0,0 +1,43 @@
1
+ import type { Ploc } from "@c.a.f/core";
2
+ import type { PlocDevTools } from "@c.a.f/devtools";
3
+ import { useUseCaseDevTools } from "./useUseCaseDevTools";
4
+ export interface CAFDevToolsContext {
5
+ plocs: Map<Ploc<any>, PlocDevTools<any>>;
6
+ useCases: ReturnType<typeof useUseCaseDevTools>;
7
+ enabled: boolean;
8
+ enable: () => void;
9
+ disable: () => void;
10
+ }
11
+ /**
12
+ * React hook that provides centralized DevTools access for CAF applications.
13
+ * Tracks all Plocs and UseCases in your application and exposes them for debugging.
14
+ *
15
+ * @param options - Global DevTools options
16
+ * @returns DevTools context with access to all tracked Plocs and UseCases
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const devTools = useCAFDevTools({ enabled: process.env.NODE_ENV === 'development' });
21
+ *
22
+ * // Enable/disable globally
23
+ * devTools.enable();
24
+ * devTools.disable();
25
+ *
26
+ * // Access tracked Plocs
27
+ * const plocTools = devTools.plocs.get(myPloc);
28
+ * if (plocTools) {
29
+ * const history = plocTools.getStateHistory();
30
+ * }
31
+ * ```
32
+ */
33
+ export declare function useCAFDevTools(options?: {
34
+ enabled?: boolean;
35
+ }): CAFDevToolsContext;
36
+ /**
37
+ * Helper hook to register a Ploc with DevTools.
38
+ * Use this inside components that use Plocs to automatically track them.
39
+ *
40
+ * @param ploc - Ploc instance to track
41
+ * @param name - Optional name for the Ploc
42
+ */
43
+ export declare function useTrackPloc<T>(ploc: Ploc<T>, name?: string): void;
@@ -0,0 +1,74 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { usePlocDevTools } from "./usePlocDevTools";
3
+ import { useUseCaseDevTools } from "./useUseCaseDevTools";
4
+ /**
5
+ * React hook that provides centralized DevTools access for CAF applications.
6
+ * Tracks all Plocs and UseCases in your application and exposes them for debugging.
7
+ *
8
+ * @param options - Global DevTools options
9
+ * @returns DevTools context with access to all tracked Plocs and UseCases
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const devTools = useCAFDevTools({ enabled: process.env.NODE_ENV === 'development' });
14
+ *
15
+ * // Enable/disable globally
16
+ * devTools.enable();
17
+ * devTools.disable();
18
+ *
19
+ * // Access tracked Plocs
20
+ * const plocTools = devTools.plocs.get(myPloc);
21
+ * if (plocTools) {
22
+ * const history = plocTools.getStateHistory();
23
+ * }
24
+ * ```
25
+ */
26
+ export function useCAFDevTools(options) {
27
+ const plocsRef = useRef(new Map());
28
+ const [enabled, setEnabled] = useState(options?.enabled ?? false);
29
+ const useCaseDevTools = useUseCaseDevTools();
30
+ // Expose devtools to React DevTools if available
31
+ useEffect(() => {
32
+ if (typeof window !== "undefined") {
33
+ // Expose CAF DevTools to global scope for React DevTools extension
34
+ window.__CAF_DEVTOOLS__ = {
35
+ plocs: plocsRef.current,
36
+ useCases: useCaseDevTools,
37
+ enabled,
38
+ enable: () => setEnabled(true),
39
+ disable: () => setEnabled(false),
40
+ };
41
+ }
42
+ return () => {
43
+ if (typeof window !== "undefined") {
44
+ delete window.__CAF_DEVTOOLS__;
45
+ }
46
+ };
47
+ }, [enabled, useCaseDevTools]);
48
+ return {
49
+ plocs: plocsRef.current,
50
+ useCases: useCaseDevTools,
51
+ enabled,
52
+ enable: () => setEnabled(true),
53
+ disable: () => setEnabled(false),
54
+ };
55
+ }
56
+ /**
57
+ * Helper hook to register a Ploc with DevTools.
58
+ * Use this inside components that use Plocs to automatically track them.
59
+ *
60
+ * @param ploc - Ploc instance to track
61
+ * @param name - Optional name for the Ploc
62
+ */
63
+ export function useTrackPloc(ploc, name) {
64
+ const devTools = useCAFDevTools();
65
+ const plocDevTools = usePlocDevTools(ploc, { name, enabled: devTools.enabled });
66
+ useEffect(() => {
67
+ if (!devTools.plocs.has(ploc)) {
68
+ devTools.plocs.set(ploc, plocDevTools);
69
+ }
70
+ return () => {
71
+ devTools.plocs.delete(ploc);
72
+ };
73
+ }, [ploc, plocDevTools, devTools]);
74
+ }
@@ -0,0 +1,23 @@
1
+ import type { Ploc } from "@c.a.f/core";
2
+ import { type PlocDevTools, type PlocDevToolsOptions } from "@c.a.f/devtools";
3
+ /**
4
+ * React hook that provides DevTools for a Ploc instance.
5
+ * Automatically cleans up on unmount.
6
+ *
7
+ * @param ploc - A Ploc instance
8
+ * @param options - DevTools options
9
+ * @returns DevTools instance and current state
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const [state, ploc] = usePloc(userPloc);
14
+ * const devTools = usePlocDevTools(ploc, { name: 'UserPloc', enabled: true });
15
+ *
16
+ * // Access state history
17
+ * const history = devTools.getStateHistory();
18
+ *
19
+ * // Time-travel debugging
20
+ * devTools.jumpToState(2);
21
+ * ```
22
+ */
23
+ export declare function usePlocDevTools<T>(ploc: Ploc<T>, options?: PlocDevToolsOptions): PlocDevTools<T>;
@@ -0,0 +1,58 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { createPlocDevTools, } from "@c.a.f/devtools";
3
+ /**
4
+ * React hook that provides DevTools for a Ploc instance.
5
+ * Automatically cleans up on unmount.
6
+ *
7
+ * @param ploc - A Ploc instance
8
+ * @param options - DevTools options
9
+ * @returns DevTools instance and current state
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const [state, ploc] = usePloc(userPloc);
14
+ * const devTools = usePlocDevTools(ploc, { name: 'UserPloc', enabled: true });
15
+ *
16
+ * // Access state history
17
+ * const history = devTools.getStateHistory();
18
+ *
19
+ * // Time-travel debugging
20
+ * devTools.jumpToState(2);
21
+ * ```
22
+ */
23
+ export function usePlocDevTools(ploc, options) {
24
+ const devToolsRef = useRef(null);
25
+ const plocRef = useRef(null);
26
+ // Create adapter wrapper that matches PlocInstance interface
27
+ // PlocDevTools expects subscribe to return void (not unsubscribe function)
28
+ const createPlocAdapter = (p) => {
29
+ return {
30
+ state: p.state,
31
+ changeState: (state) => p.changeState(state),
32
+ subscribe: (listener) => {
33
+ p.subscribe(listener);
34
+ // Note: PlocDevTools now handles cleanup internally via MemoryLeakDetector
35
+ },
36
+ unsubscribe: (listener) => p.unsubscribe(listener),
37
+ };
38
+ };
39
+ // Recreate devtools if ploc changed
40
+ if (plocRef.current !== ploc || !devToolsRef.current) {
41
+ plocRef.current = ploc;
42
+ if (devToolsRef.current) {
43
+ devToolsRef.current.cleanup();
44
+ }
45
+ const adapter = createPlocAdapter(ploc);
46
+ devToolsRef.current = createPlocDevTools(adapter, options);
47
+ }
48
+ // Cleanup on unmount
49
+ useEffect(() => {
50
+ return () => {
51
+ if (devToolsRef.current) {
52
+ devToolsRef.current.cleanup();
53
+ devToolsRef.current = null;
54
+ }
55
+ };
56
+ }, []);
57
+ return devToolsRef.current;
58
+ }
@@ -0,0 +1,18 @@
1
+ import { type UseCaseDevTools, type UseCaseDevToolsOptions } from "@c.a.f/devtools";
2
+ /**
3
+ * React hook that provides DevTools for UseCase execution tracking.
4
+ * Creates a singleton DevTools instance that persists across renders.
5
+ *
6
+ * @param options - DevTools options
7
+ * @returns DevTools instance
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const useCaseDevTools = useUseCaseDevTools({ name: 'CreateUser', enabled: true });
12
+ * const wrappedUseCase = useCaseDevTools.wrap(createUserUseCase);
13
+ *
14
+ * // Get execution history
15
+ * const history = useCaseDevTools.getExecutionHistory();
16
+ * ```
17
+ */
18
+ export declare function useUseCaseDevTools(options?: UseCaseDevToolsOptions): UseCaseDevTools;
@@ -0,0 +1,25 @@
1
+ import { useRef } from "react";
2
+ import { createUseCaseDevTools, } from "@c.a.f/devtools";
3
+ /**
4
+ * React hook that provides DevTools for UseCase execution tracking.
5
+ * Creates a singleton DevTools instance that persists across renders.
6
+ *
7
+ * @param options - DevTools options
8
+ * @returns DevTools instance
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const useCaseDevTools = useUseCaseDevTools({ name: 'CreateUser', enabled: true });
13
+ * const wrappedUseCase = useCaseDevTools.wrap(createUserUseCase);
14
+ *
15
+ * // Get execution history
16
+ * const history = useCaseDevTools.getExecutionHistory();
17
+ * ```
18
+ */
19
+ export function useUseCaseDevTools(options) {
20
+ const devToolsRef = useRef(null);
21
+ if (!devToolsRef.current) {
22
+ devToolsRef.current = createUseCaseDevTools(options);
23
+ }
24
+ return devToolsRef.current;
25
+ }
@@ -0,0 +1,36 @@
1
+ import React, { Component, type ReactNode } from "react";
2
+ export interface CAFErrorBoundaryProps {
3
+ children: ReactNode;
4
+ fallback?: (error: Error, errorInfo: React.ErrorInfo, resetError: () => void) => ReactNode;
5
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
6
+ }
7
+ interface CAFErrorBoundaryState {
8
+ error: Error | null;
9
+ errorInfo: React.ErrorInfo | null;
10
+ }
11
+ /**
12
+ * Error Boundary component for CAF applications.
13
+ * Catches errors from Ploc/UseCase execution and component rendering.
14
+ * Provides error context via React Context and supports custom error UI and recovery.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <CAFErrorBoundary fallback={(error, errorInfo, reset) => (
19
+ * <div>
20
+ * <h2>Something went wrong</h2>
21
+ * <p>{error.message}</p>
22
+ * <button onClick={reset}>Try again</button>
23
+ * </div>
24
+ * )}>
25
+ * <App />
26
+ * </CAFErrorBoundary>
27
+ * ```
28
+ */
29
+ export declare class CAFErrorBoundary extends Component<CAFErrorBoundaryProps, CAFErrorBoundaryState> {
30
+ constructor(props: CAFErrorBoundaryProps);
31
+ static getDerivedStateFromError(error: Error): Partial<CAFErrorBoundaryState>;
32
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
33
+ resetError: () => void;
34
+ render(): ReactNode;
35
+ }
36
+ export {};
@@ -0,0 +1,99 @@
1
+ import React, { Component } from "react";
2
+ import { CAFErrorContext } from "./ErrorContext";
3
+ /**
4
+ * Error Boundary component for CAF applications.
5
+ * Catches errors from Ploc/UseCase execution and component rendering.
6
+ * Provides error context via React Context and supports custom error UI and recovery.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <CAFErrorBoundary fallback={(error, errorInfo, reset) => (
11
+ * <div>
12
+ * <h2>Something went wrong</h2>
13
+ * <p>{error.message}</p>
14
+ * <button onClick={reset}>Try again</button>
15
+ * </div>
16
+ * )}>
17
+ * <App />
18
+ * </CAFErrorBoundary>
19
+ * ```
20
+ */
21
+ export class CAFErrorBoundary extends Component {
22
+ constructor(props) {
23
+ super(props);
24
+ this.state = {
25
+ error: null,
26
+ errorInfo: null,
27
+ };
28
+ }
29
+ static getDerivedStateFromError(error) {
30
+ return {
31
+ error,
32
+ };
33
+ }
34
+ componentDidCatch(error, errorInfo) {
35
+ this.setState({
36
+ error,
37
+ errorInfo,
38
+ });
39
+ // Call optional error handler
40
+ if (this.props.onError) {
41
+ this.props.onError(error, errorInfo);
42
+ }
43
+ }
44
+ resetError = () => {
45
+ this.setState({
46
+ error: null,
47
+ errorInfo: null,
48
+ });
49
+ };
50
+ render() {
51
+ if (this.state.error) {
52
+ const contextValue = {
53
+ error: this.state.error,
54
+ errorInfo: this.state.errorInfo,
55
+ resetError: this.resetError,
56
+ };
57
+ // Use custom fallback if provided
58
+ if (this.props.fallback) {
59
+ return (React.createElement(CAFErrorContext.Provider, { value: contextValue }, this.props.fallback(this.state.error, this.state.errorInfo, this.resetError)));
60
+ }
61
+ // Default error UI
62
+ return (React.createElement(CAFErrorContext.Provider, { value: contextValue },
63
+ React.createElement("div", { style: {
64
+ padding: "2rem",
65
+ margin: "1rem",
66
+ border: "1px solid #ff6b6b",
67
+ borderRadius: "8px",
68
+ backgroundColor: "#fff5f5",
69
+ } },
70
+ React.createElement("h2", { style: { color: "#c92a2a", marginTop: 0 } }, "Something went wrong"),
71
+ React.createElement("details", { style: { marginBottom: "1rem" } },
72
+ React.createElement("summary", { style: { cursor: "pointer", fontWeight: "bold" } }, "Error details"),
73
+ React.createElement("pre", { style: {
74
+ marginTop: "0.5rem",
75
+ padding: "1rem",
76
+ backgroundColor: "#fff",
77
+ borderRadius: "4px",
78
+ overflow: "auto",
79
+ fontSize: "0.875rem",
80
+ } },
81
+ this.state.error.toString(),
82
+ this.state.errorInfo?.componentStack)),
83
+ React.createElement("button", { onClick: this.resetError, style: {
84
+ padding: "0.5rem 1rem",
85
+ backgroundColor: "#667eea",
86
+ color: "white",
87
+ border: "none",
88
+ borderRadius: "4px",
89
+ cursor: "pointer",
90
+ fontSize: "1rem",
91
+ } }, "Try again"))));
92
+ }
93
+ return (React.createElement(CAFErrorContext.Provider, { value: {
94
+ error: null,
95
+ errorInfo: null,
96
+ resetError: this.resetError,
97
+ } }, this.props.children));
98
+ }
99
+ }
@@ -0,0 +1,18 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * Context for CAF error boundary state.
4
+ * Provides access to the current error and recovery function.
5
+ */
6
+ export interface CAFErrorContextValue {
7
+ error: Error | null;
8
+ errorInfo: React.ErrorInfo | null;
9
+ resetError: () => void;
10
+ }
11
+ export declare const CAFErrorContext: import("react").Context<CAFErrorContextValue | null>;
12
+ /**
13
+ * Hook to access CAF error boundary context.
14
+ * Returns null if used outside of CAFErrorBoundary.
15
+ *
16
+ * @returns Error context value or null
17
+ */
18
+ export declare function useCAFError(): CAFErrorContextValue | null;
@@ -0,0 +1,11 @@
1
+ import { createContext, useContext } from "react";
2
+ export const CAFErrorContext = createContext(null);
3
+ /**
4
+ * Hook to access CAF error boundary context.
5
+ * Returns null if used outside of CAFErrorBoundary.
6
+ *
7
+ * @returns Error context value or null
8
+ */
9
+ export function useCAFError() {
10
+ return useContext(CAFErrorContext);
11
+ }
@@ -0,0 +1,2 @@
1
+ export { CAFErrorBoundary, type CAFErrorBoundaryProps } from "./CAFErrorBoundary";
2
+ export { CAFErrorContext, useCAFError, type CAFErrorContextValue } from "./ErrorContext";
@@ -0,0 +1,2 @@
1
+ export { CAFErrorBoundary } from "./CAFErrorBoundary";
2
+ export { CAFErrorContext, useCAFError } from "./ErrorContext";
@@ -0,0 +1 @@
1
+ export { usePloc } from "./usePloc";
@@ -0,0 +1 @@
1
+ export { usePloc } from "./usePloc";
@@ -0,0 +1,15 @@
1
+ import type { Ploc } from "@c.a.f/core";
2
+ /**
3
+ * React hook that subscribes to a Ploc and returns the current state and the Ploc instance.
4
+ * Handles subscription on mount, syncs when the ploc reference changes, and unsubscribes on unmount.
5
+ *
6
+ * @param ploc - A Ploc instance (from @c.a.f/core)
7
+ * @returns A tuple of [currentState, ploc]
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const [state, userPloc] = usePloc(userPloc);
12
+ * return <span>{state.name}</span>;
13
+ * ```
14
+ */
15
+ export declare function usePloc<T>(ploc: Ploc<T>): [T, Ploc<T>];
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from "react";
2
+ /**
3
+ * React hook that subscribes to a Ploc and returns the current state and the Ploc instance.
4
+ * Handles subscription on mount, syncs when the ploc reference changes, and unsubscribes on unmount.
5
+ *
6
+ * @param ploc - A Ploc instance (from @c.a.f/core)
7
+ * @returns A tuple of [currentState, ploc]
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const [state, userPloc] = usePloc(userPloc);
12
+ * return <span>{state.name}</span>;
13
+ * ```
14
+ */
15
+ export function usePloc(ploc) {
16
+ const [state, setState] = useState(() => ploc.state);
17
+ useEffect(() => {
18
+ setState(ploc.state);
19
+ const listener = (newState) => setState(newState);
20
+ ploc.subscribe(listener);
21
+ return () => {
22
+ ploc.unsubscribe(listener);
23
+ };
24
+ }, [ploc]);
25
+ return [state, ploc];
26
+ }
@@ -0,0 +1,12 @@
1
+ import { RouteRepository } from "@c.a.f/core";
2
+ /**
3
+ * @deprecated This class violates React's rules of hooks by calling hooks in a constructor.
4
+ * Use `useRouteRepository()` hook instead, which properly calls hooks at the hook level.
5
+ */
6
+ export declare class RouteHandler implements RouteRepository {
7
+ private navigate;
8
+ constructor();
9
+ currentRoute: string;
10
+ change(route: string): void;
11
+ watchCurrentRoute(): void;
12
+ }
@@ -0,0 +1,19 @@
1
+ import { useNavigate, useLocation } from "react-router-dom";
2
+ /**
3
+ * @deprecated This class violates React's rules of hooks by calling hooks in a constructor.
4
+ * Use `useRouteRepository()` hook instead, which properly calls hooks at the hook level.
5
+ */
6
+ export class RouteHandler {
7
+ navigate = useNavigate();
8
+ constructor() {
9
+ this.watchCurrentRoute();
10
+ }
11
+ currentRoute = '';
12
+ change(route) {
13
+ this.navigate(route);
14
+ }
15
+ watchCurrentRoute() {
16
+ const { pathname } = useLocation();
17
+ this.currentRoute = pathname;
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import { RouteManager, RouteManagerAuthOptions } from "@c.a.f/core";
2
+ /**
3
+ * @deprecated This class creates RouteHandler in constructor, which violates React's rules of hooks.
4
+ * Use `useRouteManager()` hook instead, which properly calls React hooks at the hook level.
5
+ */
6
+ export declare class RouterService {
7
+ private routeManager;
8
+ constructor(authOptions?: RouteManagerAuthOptions);
9
+ getRouteManager(): RouteManager;
10
+ }
@@ -0,0 +1,16 @@
1
+ import { RouteManager } from "@c.a.f/core";
2
+ import { RouteHandler } from "./RouteHandler";
3
+ /**
4
+ * @deprecated This class creates RouteHandler in constructor, which violates React's rules of hooks.
5
+ * Use `useRouteManager()` hook instead, which properly calls React hooks at the hook level.
6
+ */
7
+ export class RouterService {
8
+ routeManager;
9
+ constructor(authOptions) {
10
+ const routeHandler = new RouteHandler();
11
+ this.routeManager = new RouteManager(routeHandler, authOptions);
12
+ }
13
+ getRouteManager() {
14
+ return this.routeManager;
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export * from './RouteManger';
@@ -0,0 +1 @@
1
+ export * from './RouteManger';
@@ -0,0 +1,8 @@
1
+ import { RouteManager, RouteManagerAuthOptions } from "@c.a.f/core";
2
+ /**
3
+ * React hook that provides a RouteManager from @c.a.f/core.
4
+ * Uses useRouteRepository to get the RouteRepository implementation.
5
+ *
6
+ * @param authOptions - Optional authentication configuration. If not provided, RouteManager will work without auth checks.
7
+ */
8
+ export declare const useRouteManager: (authOptions?: RouteManagerAuthOptions) => RouteManager;
@@ -0,0 +1,15 @@
1
+ import { RouteManager } from "@c.a.f/core";
2
+ import { useMemo } from "react";
3
+ import { useRouteRepository } from "./useRouteRepository";
4
+ /**
5
+ * React hook that provides a RouteManager from @c.a.f/core.
6
+ * Uses useRouteRepository to get the RouteRepository implementation.
7
+ *
8
+ * @param authOptions - Optional authentication configuration. If not provided, RouteManager will work without auth checks.
9
+ */
10
+ export const useRouteManager = (authOptions) => {
11
+ const routeRepository = useRouteRepository();
12
+ return useMemo(() => {
13
+ return new RouteManager(routeRepository, authOptions);
14
+ }, [routeRepository, authOptions]);
15
+ };
@@ -0,0 +1,6 @@
1
+ import { RouteRepository } from "@c.a.f/core";
2
+ /**
3
+ * React hook that provides a RouteRepository implementation.
4
+ * Calls React Router hooks at the hook level (not in a constructor).
5
+ */
6
+ export declare const useRouteRepository: () => RouteRepository;
@@ -0,0 +1,20 @@
1
+ import { useNavigate, useLocation } from "react-router-dom";
2
+ import { useMemo } from "react";
3
+ /**
4
+ * React hook that provides a RouteRepository implementation.
5
+ * Calls React Router hooks at the hook level (not in a constructor).
6
+ */
7
+ export const useRouteRepository = () => {
8
+ const navigate = useNavigate();
9
+ const location = useLocation();
10
+ return useMemo(() => {
11
+ return {
12
+ get currentRoute() {
13
+ return location.pathname;
14
+ },
15
+ change(route) {
16
+ navigate(route);
17
+ },
18
+ };
19
+ }, [navigate, location.pathname]);
20
+ };
@@ -0,0 +1 @@
1
+ export { useUseCase } from "./useUseCase";
@@ -0,0 +1 @@
1
+ export { useUseCase } from "./useUseCase";
@@ -0,0 +1,26 @@
1
+ import type { UseCase } from "@c.a.f/core";
2
+ /**
3
+ * React hook that wraps a UseCase execution with loading/error/data state management.
4
+ * Handles RequestResult subscriptions automatically and provides a clean API for executing use cases.
5
+ *
6
+ * @param useCase - A UseCase instance (from @c.a.f/core)
7
+ * @returns An object with execute function, loading state, error state, and data
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { execute, loading, error, data } = useUseCase(createUserUseCase);
12
+ *
13
+ * const handleCreate = async () => {
14
+ * const result = await execute({ name: 'John', email: 'john@example.com' });
15
+ * if (result) {
16
+ * console.log('User created:', result);
17
+ * }
18
+ * };
19
+ * ```
20
+ */
21
+ export declare function useUseCase<TArgs extends any[], TResult>(useCase: UseCase<TArgs, TResult>): {
22
+ execute: (...args: TArgs) => Promise<TResult | null>;
23
+ loading: boolean;
24
+ error: Error | null;
25
+ data: TResult | null;
26
+ };
@@ -0,0 +1,83 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ /**
3
+ * React hook that wraps a UseCase execution with loading/error/data state management.
4
+ * Handles RequestResult subscriptions automatically and provides a clean API for executing use cases.
5
+ *
6
+ * @param useCase - A UseCase instance (from @c.a.f/core)
7
+ * @returns An object with execute function, loading state, error state, and data
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { execute, loading, error, data } = useUseCase(createUserUseCase);
12
+ *
13
+ * const handleCreate = async () => {
14
+ * const result = await execute({ name: 'John', email: 'john@example.com' });
15
+ * if (result) {
16
+ * console.log('User created:', result);
17
+ * }
18
+ * };
19
+ * ```
20
+ */
21
+ export function useUseCase(useCase) {
22
+ const [loading, setLoading] = useState(false);
23
+ const [error, setError] = useState(null);
24
+ const [data, setData] = useState(null);
25
+ const requestResultRef = useRef(null);
26
+ const listenersRef = useRef(null);
27
+ // Cleanup subscriptions on unmount
28
+ useEffect(() => {
29
+ return () => {
30
+ const currentResult = requestResultRef.current;
31
+ const listeners = listenersRef.current;
32
+ if (currentResult && listeners) {
33
+ currentResult.loading.unsubscribe(listeners.loading);
34
+ currentResult.data.unsubscribe(listeners.data);
35
+ currentResult.error.unsubscribe(listeners.error);
36
+ }
37
+ };
38
+ }, []);
39
+ const execute = useCallback(async (...args) => {
40
+ // Unsubscribe from previous result if exists
41
+ const previousResult = requestResultRef.current;
42
+ const previousListeners = listenersRef.current;
43
+ if (previousResult && previousListeners) {
44
+ previousResult.loading.unsubscribe(previousListeners.loading);
45
+ previousResult.data.unsubscribe(previousListeners.data);
46
+ previousResult.error.unsubscribe(previousListeners.error);
47
+ }
48
+ try {
49
+ const result = await useCase.execute(...args);
50
+ requestResultRef.current = result;
51
+ // Create listeners for the new result
52
+ const loadingListener = (value) => setLoading(value);
53
+ const dataListener = (value) => setData(value);
54
+ const errorListener = (value) => setError(value);
55
+ listenersRef.current = {
56
+ loading: loadingListener,
57
+ data: dataListener,
58
+ error: errorListener,
59
+ };
60
+ // Subscribe to the new result
61
+ result.loading.subscribe(loadingListener);
62
+ result.data.subscribe(dataListener);
63
+ result.error.subscribe(errorListener);
64
+ // Initialize state from result
65
+ setLoading(result.loading.value);
66
+ setData(result.data.value);
67
+ setError(result.error.value);
68
+ return result.data.value;
69
+ }
70
+ catch (err) {
71
+ const error = err instanceof Error ? err : new Error(String(err));
72
+ setError(error);
73
+ setLoading(false);
74
+ return null;
75
+ }
76
+ }, [useCase]);
77
+ return {
78
+ execute,
79
+ loading,
80
+ error,
81
+ data,
82
+ };
83
+ }
@@ -0,0 +1,7 @@
1
+ export * from './RouteManager';
2
+ export * from './RouteManager/useRouteRepository';
3
+ export * from './RouteManager/useRouteManager';
4
+ export * from './Ploc';
5
+ export * from './UseCase';
6
+ export * from './ErrorBoundary';
7
+ export * from './DevTools';
@@ -0,0 +1,7 @@
1
+ export * from './RouteManager';
2
+ export * from './RouteManager/useRouteRepository';
3
+ export * from './RouteManager/useRouteManager';
4
+ export * from './Ploc';
5
+ export * from './UseCase';
6
+ export * from './ErrorBoundary';
7
+ export * from './DevTools';
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname, resolve } from "path";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ export default defineConfig({
7
+ test: {
8
+ environment: "happy-dom",
9
+ include: ["**/*.spec.ts", "**/*.spec.tsx"],
10
+ setupFiles: [resolve(__dirname, "./vitest.setup.ts")],
11
+ globals: true,
12
+ },
13
+ resolve: {
14
+ alias: {
15
+ // Force resolution from workspace root node_modules
16
+ "@testing-library/dom": resolve(__dirname, "../../../node_modules/@testing-library/dom"),
17
+ // Fix ES module resolution for @c.a.f/core - resolve to the actual built file
18
+ "@c.a.f/core": resolve(__dirname, "../../../node_modules/@c.a.f/core/.build/src/index.js"),
19
+ },
20
+ conditions: ["import", "module", "default"],
21
+ },
22
+ });
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/vitest";
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/vitest";
package/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # @c.a.f/infrastructure-react
2
+
3
+ React-specific infrastructure adapters for CAF.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @c.a.f/infrastructure-react react-router-dom
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### usePloc
14
+
15
+ Hook that subscribes to a Ploc and returns the current state and the Ploc instance. Subscribes on mount, syncs when the ploc reference changes, and unsubscribes on unmount.
16
+
17
+ ```typescript
18
+ import { usePloc } from '@c.a.f/infrastructure-react';
19
+
20
+ function UserProfile({ userPloc }: { userPloc: UserPloc }) {
21
+ const [state, ploc] = usePloc(userPloc);
22
+
23
+ return (
24
+ <div>
25
+ <span>{state.name}</span>
26
+ <button onClick={() => ploc.loadUser()}>Refresh</button>
27
+ </div>
28
+ );
29
+ }
30
+ ```
31
+
32
+ The hook returns a tuple `[state, ploc]`: the current state (re-renders when the Ploc updates) and the same Ploc instance so you can call methods on it. The Ploc is typically provided via props, context, or created with `useMemo` for the component tree.
33
+
34
+ ### useUseCase
35
+
36
+ Hook that wraps a UseCase execution with loading/error/data state management. Handles `RequestResult` subscriptions automatically and provides a clean API for executing use cases.
37
+
38
+ ```typescript
39
+ import { useUseCase } from '@c.a.f/infrastructure-react';
40
+ import { CreateUser } from './application/User/Commands/CreateUser';
41
+
42
+ function CreateUserForm({ createUserUseCase }: { createUserUseCase: CreateUser }) {
43
+ const { execute, loading, error, data } = useUseCase(createUserUseCase);
44
+
45
+ const handleSubmit = async (e: React.FormEvent) => {
46
+ e.preventDefault();
47
+ const formData = new FormData(e.target as HTMLFormElement);
48
+ const result = await execute({
49
+ name: formData.get('name') as string,
50
+ email: formData.get('email') as string,
51
+ });
52
+
53
+ if (result) {
54
+ console.log('User created:', result);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <form onSubmit={handleSubmit}>
60
+ {loading && <p>Creating user...</p>}
61
+ {error && <p>Error: {error.message}</p>}
62
+ {data && <p>User created: {data.name}</p>}
63
+ {/* form fields */}
64
+ </form>
65
+ );
66
+ }
67
+ ```
68
+
69
+ The hook automatically subscribes to the `RequestResult`'s `loading`, `data`, and `error` pulses, so your component re-renders when these values change. The `execute` function returns the data value directly (or `null` on error), making it easy to handle results.
70
+
71
+ ### CAFErrorBoundary
72
+
73
+ Error Boundary component that catches errors from Ploc/UseCase execution and component rendering. Provides error context via React Context and supports custom error UI and recovery.
74
+
75
+ ```typescript
76
+ import { CAFErrorBoundary, useCAFError } from '@c.a.f/infrastructure-react';
77
+
78
+ function App() {
79
+ return (
80
+ <CAFErrorBoundary
81
+ fallback={(error, errorInfo, resetError) => (
82
+ <div>
83
+ <h2>Oops! Something went wrong</h2>
84
+ <p>{error.message}</p>
85
+ <button onClick={resetError}>Try again</button>
86
+ </div>
87
+ )}
88
+ onError={(error, errorInfo) => {
89
+ // Log to error reporting service
90
+ console.error('Error caught:', error, errorInfo);
91
+ }}
92
+ >
93
+ <YourApp />
94
+ </CAFErrorBoundary>
95
+ );
96
+ }
97
+ ```
98
+
99
+ Access error context from anywhere within the boundary:
100
+
101
+ ```typescript
102
+ import { useCAFError } from '@c.a.f/infrastructure-react';
103
+
104
+ function ErrorDisplay() {
105
+ const errorContext = useCAFError();
106
+
107
+ if (errorContext?.error) {
108
+ return (
109
+ <div>
110
+ <p>Error: {errorContext.error.message}</p>
111
+ <button onClick={errorContext.resetError}>Reset</button>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ return null;
117
+ }
118
+ ```
119
+
120
+ The error boundary catches:
121
+ - Errors during component rendering
122
+ - Errors in lifecycle methods
123
+ - Errors in constructors
124
+ - Errors from Ploc/UseCase execution (when not caught locally)
125
+
126
+ ### DevTools Integration
127
+
128
+ React hooks for debugging and inspecting CAF applications. Integrates with `@c.a.f/devtools` and exposes data for React DevTools.
129
+
130
+ #### usePlocDevTools
131
+
132
+ Hook that provides DevTools for a Ploc instance. Enables state tracking, time-travel debugging, state history, and memory leak detection.
133
+
134
+ ```typescript
135
+ import { usePloc, usePlocDevTools } from '@c.a.f/infrastructure-react';
136
+ import { createMemoryLeakDetector } from '@c.a.f/devtools';
137
+
138
+ function UserProfile({ userPloc }: { userPloc: UserPloc }) {
139
+ const [state, ploc] = usePloc(userPloc);
140
+
141
+ // Optional: Create memory leak detector
142
+ const leakDetector = createMemoryLeakDetector({
143
+ enabled: process.env.NODE_ENV === 'development',
144
+ warnThreshold: 10000, // Warn after 10 seconds
145
+ });
146
+
147
+ const devTools = usePlocDevTools(ploc, {
148
+ name: 'UserPloc',
149
+ enabled: process.env.NODE_ENV === 'development',
150
+ enableLeakDetection: true,
151
+ leakDetector, // Optional: provide custom detector
152
+ });
153
+
154
+ // Access state history
155
+ const history = devTools.getStateHistory();
156
+
157
+ // Time-travel debugging
158
+ const handleUndo = () => {
159
+ devTools.previousState();
160
+ };
161
+
162
+ return (
163
+ <div>
164
+ <span>{state.name}</span>
165
+ {process.env.NODE_ENV === 'development' && (
166
+ <button onClick={handleUndo}>Undo</button>
167
+ )}
168
+ </div>
169
+ );
170
+ }
171
+ ```
172
+
173
+ #### useUseCaseDevTools
174
+
175
+ Hook that provides DevTools for UseCase execution tracking. Tracks execution history, timing, errors, and performance profiling.
176
+
177
+ ```typescript
178
+ import { useUseCaseDevTools } from '@c.a.f/infrastructure-react';
179
+ import { createPerformanceProfiler } from '@c.a.f/devtools';
180
+ import { CreateUser } from './application/User/Commands/CreateUser';
181
+
182
+ function CreateUserForm({ createUserUseCase }: { createUserUseCase: CreateUser }) {
183
+ // Optional: Create performance profiler
184
+ const profiler = createPerformanceProfiler({
185
+ enabled: process.env.NODE_ENV === 'development',
186
+ trackSlowOperations: true,
187
+ slowThreshold: 100, // ms
188
+ });
189
+
190
+ const useCaseDevTools = useUseCaseDevTools({
191
+ name: 'CreateUser',
192
+ enabled: true,
193
+ logExecutionTime: true,
194
+ profiler, // Optional: provide custom profiler
195
+ });
196
+
197
+ // Wrap use case with DevTools tracking
198
+ const trackedUseCase = useCaseDevTools.wrap(createUserUseCase);
199
+
200
+ // Get execution statistics
201
+ const stats = useCaseDevTools.getStatistics();
202
+ console.log('Total executions:', stats.totalExecutions);
203
+ console.log('Average duration:', stats.averageDuration, 'ms');
204
+
205
+ // Use the tracked use case...
206
+ }
207
+ ```
208
+
209
+ #### useCAFDevTools
210
+
211
+ Main hook that provides centralized DevTools access for your entire application. Tracks all Plocs and UseCases.
212
+
213
+ ```typescript
214
+ import { useCAFDevTools, useTrackPloc } from '@c.a.f/infrastructure-react';
215
+
216
+ function App() {
217
+ const devTools = useCAFDevTools({
218
+ enabled: process.env.NODE_ENV === 'development'
219
+ });
220
+
221
+ // Enable/disable globally
222
+ const handleToggleDevTools = () => {
223
+ if (devTools.enabled) {
224
+ devTools.disable();
225
+ } else {
226
+ devTools.enable();
227
+ }
228
+ };
229
+
230
+ return (
231
+ <div>
232
+ {/* Your app */}
233
+ {process.env.NODE_ENV === 'development' && (
234
+ <button onClick={handleToggleDevTools}>
235
+ {devTools.enabled ? 'Disable' : 'Enable'} DevTools
236
+ </button>
237
+ )}
238
+ </div>
239
+ );
240
+ }
241
+
242
+ // In components using Plocs, automatically track them:
243
+ function UserComponent({ userPloc }: { userPloc: UserPloc }) {
244
+ useTrackPloc(userPloc, 'UserPloc'); // Automatically registered with DevTools
245
+ const [state] = usePloc(userPloc);
246
+ // ...
247
+ }
248
+ ```
249
+
250
+ The DevTools data is also exposed to `window.__CAF_DEVTOOLS__` for React DevTools extension integration.
251
+
252
+ ### useRouteManager
253
+
254
+ Hook that provides a `RouteManager` from `@c.a.f/core`:
255
+
256
+ ```typescript
257
+ import { useRouteManager } from '@c.a.f/infrastructure-react';
258
+ import { RouteManagerAuthOptions } from '@c.a.f/core';
259
+
260
+ function MyComponent() {
261
+ // Optional: provide auth configuration
262
+ const authOptions: RouteManagerAuthOptions = {
263
+ loginPath: '/login',
264
+ isLoggedIn: () => !!localStorage.getItem('token'),
265
+ };
266
+
267
+ const routeManager = useRouteManager(authOptions);
268
+
269
+ const handleLogin = async () => {
270
+ // ... login logic
271
+ routeManager.changeRoute('/dashboard');
272
+ };
273
+
274
+ return <button onClick={handleLogin}>Login</button>;
275
+ }
276
+ ```
277
+
278
+ ### useRouteRepository
279
+
280
+ Hook that provides a `RouteRepository` implementation:
281
+
282
+ ```typescript
283
+ import { useRouteRepository } from '@c.a.f/infrastructure-react';
284
+ import { RouteManager } from '@c.a.f/core';
285
+
286
+ function MyComponent() {
287
+ const routeRepository = useRouteRepository();
288
+ const routeManager = new RouteManager(routeRepository);
289
+
290
+ // Use routeManager...
291
+ }
292
+ ```
293
+
294
+ ## Exports
295
+
296
+ - `usePloc` — Hook that subscribes to a Ploc and returns `[state, ploc]`; handles subscribe/unsubscribe and cleanup
297
+ - `useUseCase` — Hook that wraps UseCase execution with loading/error/data state management; handles RequestResult subscriptions automatically
298
+ - `CAFErrorBoundary` — Error Boundary component that catches errors from Ploc/UseCase execution; provides error context via React Context
299
+ - `useCAFError` — Hook to access error context from CAFErrorBoundary
300
+ - `usePlocDevTools` — Hook that provides DevTools for a Ploc instance; enables state tracking and time-travel debugging
301
+ - `useUseCaseDevTools` — Hook that provides DevTools for UseCase execution tracking
302
+ - `useCAFDevTools` — Main hook that provides centralized DevTools access; tracks all Plocs and UseCases
303
+ - `useTrackPloc` — Helper hook to automatically register a Ploc with DevTools
304
+ - `useRouteManager` — Hook returning core `RouteManager` with React Router integration
305
+ - `useRouteRepository` — Hook returning `RouteRepository` implementation
306
+
307
+ ## Dependencies
308
+
309
+ - `@c.a.f/core` — Core primitives
310
+ - `@c.a.f/devtools` — DevTools utilities (for debugging)
311
+ - `react-router-dom` — React Router
312
+
313
+ ## Peer Dependencies
314
+
315
+ - `react` >= 16.8.0
316
+
317
+ ## License
318
+
319
+ MIT
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@c.a.f/infrastructure-react",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Clean Architecture Frontend (CAF) — React adapters: usePloc, useUseCase, CAFErrorBoundary, DevTools, useRouteManager, useRouteRepository.",
6
+ "keywords": [
7
+ "clean-architecture-frontend",
8
+ "clean-architecture",
9
+ "caf",
10
+ "infrastructure",
11
+ "react",
12
+ "routing",
13
+ "hooks",
14
+ "ploc",
15
+ "state-management",
16
+ "devtools",
17
+ "debugging"
18
+ ],
19
+ "author": "ali aslani <aliaslani.mm@gmail.com>",
20
+ "license": "MIT",
21
+ "repository": "https://github.com/ialiaslani/caf.git",
22
+ "main": "./.build/index.js",
23
+ "module": "./.build/index.js",
24
+ "types": "./.build/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./.build/index.d.ts",
28
+ "import": "./.build/index.js",
29
+ "default": "./.build/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ ".build"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc --build",
37
+ "start": "tsc --watch",
38
+ "prepublishOnly": "npm run build",
39
+ "test": "node_modules/.bin/vitest run",
40
+ "test:watch": "node_modules/.bin/vitest"
41
+ },
42
+ "dependencies": {
43
+ "@c.a.f/core": "1.0.0",
44
+ "@c.a.f/devtools": "1.0.0",
45
+ "react-router-dom": "^6.23.1"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=16.8.0"
49
+ },
50
+ "devDependencies": {
51
+ "@testing-library/dom": "^10.4.0",
52
+ "@testing-library/jest-dom": "^6.1.5",
53
+ "@testing-library/react": "^16.0.0",
54
+ "happy-dom": "^15.11.7",
55
+ "vitest": "^2.1.0"
56
+ }
57
+ }