@c.a.f/infrastructure-react 1.0.2 → 1.0.4

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.
@@ -1,15 +1,20 @@
1
1
  import type { Ploc } from "@c.a.f/core";
2
+ /** Infers the state type S from a Ploc subclass P (P extends Ploc<S>) */
3
+ type PlocState<P> = P extends Ploc<infer S> ? S : never;
2
4
  /**
3
5
  * React hook that subscribes to a Ploc and returns the current state and the Ploc instance.
4
6
  * Handles subscription on mount, syncs when the ploc reference changes, and unsubscribes on unmount.
7
+ * The return type preserves the concrete ploc type (e.g. UserPloc) so methods like loadUsers() are typed.
5
8
  *
6
9
  * @param ploc - A Ploc instance (from @c.a.f/core)
7
- * @returns A tuple of [currentState, ploc]
10
+ * @returns A tuple of [currentState, ploc] with the same ploc type as passed in
8
11
  *
9
12
  * @example
10
13
  * ```tsx
11
14
  * const [state, userPloc] = usePloc(userPloc);
15
+ * await userPloc.loadUsers(); // typed correctly
12
16
  * return <span>{state.name}</span>;
13
17
  * ```
14
18
  */
15
- export declare function usePloc<T>(ploc: Ploc<T>): [T, Ploc<T>];
19
+ export declare function usePloc<P extends Ploc<PlocState<P>>>(ploc: P): [PlocState<P>, P];
20
+ export {};
@@ -2,13 +2,15 @@ import { useEffect, useState } from "react";
2
2
  /**
3
3
  * React hook that subscribes to a Ploc and returns the current state and the Ploc instance.
4
4
  * Handles subscription on mount, syncs when the ploc reference changes, and unsubscribes on unmount.
5
+ * The return type preserves the concrete ploc type (e.g. UserPloc) so methods like loadUsers() are typed.
5
6
  *
6
7
  * @param ploc - A Ploc instance (from @c.a.f/core)
7
- * @returns A tuple of [currentState, ploc]
8
+ * @returns A tuple of [currentState, ploc] with the same ploc type as passed in
8
9
  *
9
10
  * @example
10
11
  * ```tsx
11
12
  * const [state, userPloc] = usePloc(userPloc);
13
+ * await userPloc.loadUsers(); // typed correctly
12
14
  * return <span>{state.name}</span>;
13
15
  * ```
14
16
  */
@@ -0,0 +1,25 @@
1
+ /// <reference types="react" />
2
+ import type { Ploc } from "@c.a.f/core";
3
+ import type { UseCase } from "@c.a.f/core";
4
+ /**
5
+ * Value provided by CAFProvider. Allows descendants to access Plocs and UseCases by key.
6
+ * Use useCAFContext() to read, or usePlocFromContext / useUseCaseFromContext for typed access by key.
7
+ */
8
+ export interface CAFContextValue {
9
+ /** Plocs registered at the root, keyed by string (e.g. "user", "auth"). */
10
+ plocs: Record<string, Ploc<unknown>>;
11
+ /** UseCases registered at the root, keyed by string (e.g. "createUser", "login"). */
12
+ useCases: Record<string, UseCase<any[], any>>;
13
+ }
14
+ export declare const CAFContext: import("react").Context<CAFContextValue>;
15
+ /**
16
+ * Hook to access the CAF context (Plocs and UseCases registered by CAFProvider).
17
+ * Returns the context value; use .plocs[key] or .useCases[key] to get instances.
18
+ * When used outside CAFProvider, returns the default empty registry (no throw).
19
+ *
20
+ * @example
21
+ * const { plocs, useCases } = useCAFContext();
22
+ * const userPloc = plocs["user"] as UserPloc | undefined;
23
+ * const createUser = useCases["createUser"];
24
+ */
25
+ export declare function useCAFContext(): CAFContextValue;
@@ -0,0 +1,19 @@
1
+ import { createContext, useContext } from "react";
2
+ const defaultValue = {
3
+ plocs: {},
4
+ useCases: {},
5
+ };
6
+ export const CAFContext = createContext(defaultValue);
7
+ /**
8
+ * Hook to access the CAF context (Plocs and UseCases registered by CAFProvider).
9
+ * Returns the context value; use .plocs[key] or .useCases[key] to get instances.
10
+ * When used outside CAFProvider, returns the default empty registry (no throw).
11
+ *
12
+ * @example
13
+ * const { plocs, useCases } = useCAFContext();
14
+ * const userPloc = plocs["user"] as UserPloc | undefined;
15
+ * const createUser = useCases["createUser"];
16
+ */
17
+ export function useCAFContext() {
18
+ return useContext(CAFContext);
19
+ }
@@ -0,0 +1,42 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { Ploc } from "@c.a.f/core";
3
+ import type { UseCase } from "@c.a.f/core";
4
+ export interface CAFProviderProps {
5
+ /**
6
+ * Plocs to provide to the tree, keyed by string.
7
+ * Descendants can access via useCAFContext().plocs[key] or usePlocFromContext(key).
8
+ */
9
+ plocs?: Record<string, Ploc<unknown>>;
10
+ /**
11
+ * UseCases to provide to the tree, keyed by string.
12
+ * Descendants can access via useCAFContext().useCases[key] or useUseCaseFromContext(key).
13
+ */
14
+ useCases?: Record<string, UseCase<any[], any>>;
15
+ children: ReactNode;
16
+ }
17
+ /**
18
+ * Root-level provider for Plocs and UseCases. Register instances by key so any descendant
19
+ * can access them without prop drilling.
20
+ *
21
+ * @example Single provider at app root
22
+ * ```tsx
23
+ * const userPloc = useMemo(() => new UserPloc(userRepo), [userRepo]);
24
+ * const createUser = useMemo(() => new CreateUser(repo), [repo]);
25
+ *
26
+ * <CAFProvider plocs={{ user: userPloc }} useCases={{ createUser }}>
27
+ * <App />
28
+ * </CAFProvider>
29
+ * ```
30
+ *
31
+ * @example Nested providers (e.g. feature-specific Plocs)
32
+ * ```tsx
33
+ * <CAFProvider plocs={{ user: userPloc }}>
34
+ * <CAFProvider plocs={{ dashboard: dashboardPloc }}>
35
+ * <Dashboard />
36
+ * </CAFProvider>
37
+ * </CAFProvider>
38
+ * ```
39
+ * Inner provider does not merge with outer; use a single provider at root with all keys, or
40
+ * ensure children only rely on the nearest provider's keys.
41
+ */
42
+ export declare function CAFProvider({ plocs, useCases, children }: CAFProviderProps): React.JSX.Element;
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { CAFContext } from "./CAFContext";
3
+ /**
4
+ * Root-level provider for Plocs and UseCases. Register instances by key so any descendant
5
+ * can access them without prop drilling.
6
+ *
7
+ * @example Single provider at app root
8
+ * ```tsx
9
+ * const userPloc = useMemo(() => new UserPloc(userRepo), [userRepo]);
10
+ * const createUser = useMemo(() => new CreateUser(repo), [repo]);
11
+ *
12
+ * <CAFProvider plocs={{ user: userPloc }} useCases={{ createUser }}>
13
+ * <App />
14
+ * </CAFProvider>
15
+ * ```
16
+ *
17
+ * @example Nested providers (e.g. feature-specific Plocs)
18
+ * ```tsx
19
+ * <CAFProvider plocs={{ user: userPloc }}>
20
+ * <CAFProvider plocs={{ dashboard: dashboardPloc }}>
21
+ * <Dashboard />
22
+ * </CAFProvider>
23
+ * </CAFProvider>
24
+ * ```
25
+ * Inner provider does not merge with outer; use a single provider at root with all keys, or
26
+ * ensure children only rely on the nearest provider's keys.
27
+ */
28
+ export function CAFProvider({ plocs = {}, useCases = {}, children }) {
29
+ const value = {
30
+ plocs: { ...plocs },
31
+ useCases: { ...useCases },
32
+ };
33
+ return React.createElement(CAFContext.Provider, { value: value }, children);
34
+ }
@@ -0,0 +1,3 @@
1
+ export { CAFContext, useCAFContext, type CAFContextValue } from "./CAFContext";
2
+ export { CAFProvider, type CAFProviderProps } from "./CAFProvider";
3
+ export { usePlocFromContext, useUseCaseFromContext } from "./useContextHooks";
@@ -0,0 +1,3 @@
1
+ export { CAFContext, useCAFContext } from "./CAFContext";
2
+ export { CAFProvider } from "./CAFProvider";
3
+ export { usePlocFromContext, useUseCaseFromContext } from "./useContextHooks";
@@ -0,0 +1,34 @@
1
+ import type { Ploc, UseCase } from "@c.a.f/core";
2
+ /**
3
+ * Hook to get a Ploc from the nearest CAFProvider by key.
4
+ * Returns `undefined` when the key is not registered or when used outside a provider
5
+ * (default context has empty plocs). Use the generic for type-safe access.
6
+ *
7
+ * @param key - Key used when registering the Ploc in CAFProvider (e.g. "user", "auth")
8
+ * @returns The Ploc instance or undefined if not found
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const userPloc = usePlocFromContext<UserPloc>("user");
13
+ * if (!userPloc) return <NotFound />;
14
+ * const [state, ploc] = usePloc(userPloc);
15
+ * return <span>{state.name}</span>;
16
+ * ```
17
+ */
18
+ export declare function usePlocFromContext<P extends Ploc<unknown>>(key: string): P | undefined;
19
+ /**
20
+ * Hook to get a UseCase from the nearest CAFProvider by key.
21
+ * Returns `undefined` when the key is not registered or when used outside a provider
22
+ * (default context has empty useCases). Use generics for type-safe args and result.
23
+ *
24
+ * @param key - Key used when registering the UseCase in CAFProvider (e.g. "createUser", "login")
25
+ * @returns The UseCase instance or undefined if not found
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const createUser = useUseCaseFromContext<[CreateUserInput], User>("createUser");
30
+ * if (!createUser) return null;
31
+ * const { execute, loading } = useUseCase(createUser);
32
+ * ```
33
+ */
34
+ export declare function useUseCaseFromContext<TArgs extends any[], TResult>(key: string): UseCase<TArgs, TResult> | undefined;
@@ -0,0 +1,40 @@
1
+ import { useCAFContext } from "./CAFContext";
2
+ /**
3
+ * Hook to get a Ploc from the nearest CAFProvider by key.
4
+ * Returns `undefined` when the key is not registered or when used outside a provider
5
+ * (default context has empty plocs). Use the generic for type-safe access.
6
+ *
7
+ * @param key - Key used when registering the Ploc in CAFProvider (e.g. "user", "auth")
8
+ * @returns The Ploc instance or undefined if not found
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const userPloc = usePlocFromContext<UserPloc>("user");
13
+ * if (!userPloc) return <NotFound />;
14
+ * const [state, ploc] = usePloc(userPloc);
15
+ * return <span>{state.name}</span>;
16
+ * ```
17
+ */
18
+ export function usePlocFromContext(key) {
19
+ const { plocs } = useCAFContext();
20
+ return plocs[key];
21
+ }
22
+ /**
23
+ * Hook to get a UseCase from the nearest CAFProvider by key.
24
+ * Returns `undefined` when the key is not registered or when used outside a provider
25
+ * (default context has empty useCases). Use generics for type-safe args and result.
26
+ *
27
+ * @param key - Key used when registering the UseCase in CAFProvider (e.g. "createUser", "login")
28
+ * @returns The UseCase instance or undefined if not found
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const createUser = useUseCaseFromContext<[CreateUserInput], User>("createUser");
33
+ * if (!createUser) return null;
34
+ * const { execute, loading } = useUseCase(createUser);
35
+ * ```
36
+ */
37
+ export function useUseCaseFromContext(key) {
38
+ const { useCases } = useCAFContext();
39
+ return useCases[key];
40
+ }
package/.build/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export * from './Ploc';
5
5
  export * from './UseCase';
6
6
  export * from './ErrorBoundary';
7
7
  export * from './DevTools';
8
+ export * from './Provider';
package/.build/index.js CHANGED
@@ -5,3 +5,4 @@ export * from './Ploc';
5
5
  export * from './UseCase';
6
6
  export * from './ErrorBoundary';
7
7
  export * from './DevTools';
8
+ export * from './Provider';
package/README.md CHANGED
@@ -249,6 +249,73 @@ function UserComponent({ userPloc }: { userPloc: UserPloc }) {
249
249
 
250
250
  The DevTools data is also exposed to `window.__CAF_DEVTOOLS__` for React DevTools extension integration.
251
251
 
252
+ ### CAFProvider (Ploc/UseCase provisioning)
253
+
254
+ Register Plocs and UseCases at the app root so any descendant can access them without prop drilling. Use a single provider with all keys, or nest providers for feature-specific instances.
255
+
256
+ **Wiring at app root:** Create your Plocs and UseCases once (e.g. in the root component or a bootstrap module), pass them into `CAFProvider` by key, and wrap your app. Any descendant can then read them via `usePlocFromContext(key)` or `useUseCaseFromContext(key)` without prop drilling.
257
+
258
+ **Minimal example (wrap app, inject Ploc, consume in child):**
259
+
260
+ ```tsx
261
+ import { CAFProvider, usePlocFromContext, usePloc } from '@c.a.f/infrastructure-react';
262
+
263
+ // Root: wrap app and inject Plocs by key
264
+ function main() {
265
+ const counterPloc = new CounterPloc(0);
266
+ root.render(
267
+ <CAFProvider plocs={{ counter: counterPloc }}>
268
+ <App />
269
+ </CAFProvider>
270
+ );
271
+ }
272
+
273
+ // Child: consume from context (no props)
274
+ function Counter() {
275
+ const ploc = usePlocFromContext<CounterPloc>('counter');
276
+ if (!ploc) return null;
277
+ const [state, p] = usePloc(ploc);
278
+ return <button onClick={() => p.increment()}>{state}</button>;
279
+ }
280
+ ```
281
+
282
+ **Recommended: single provider at root**
283
+
284
+ ```typescript
285
+ import { CAFProvider, usePlocFromContext, useUseCaseFromContext, usePloc, useUseCase } from '@c.a.f/infrastructure-react';
286
+
287
+ // At app root: create Plocs/UseCases (e.g. with useMemo) and pass by key
288
+ function AppRoot() {
289
+ const userPloc = useMemo(() => new UserPloc(userRepo), [userRepo]);
290
+ const createUser = useMemo(() => new CreateUser(repo), [repo]);
291
+
292
+ return (
293
+ <CAFProvider plocs={{ user: userPloc }} useCases={{ createUser }}>
294
+ <App />
295
+ </CAFProvider>
296
+ );
297
+ }
298
+
299
+ // In any descendant: typed hooks (return undefined if key not registered)
300
+ function UserProfile() {
301
+ const userPloc = usePlocFromContext<UserPloc>('user');
302
+ if (!userPloc) return null;
303
+ const [state, ploc] = usePloc(userPloc);
304
+ return <span>{state.name}</span>;
305
+ }
306
+
307
+ function CreateUserForm() {
308
+ const createUser = useUseCaseFromContext<[CreateUserInput], User>('createUser');
309
+ if (!createUser) return null;
310
+ const { execute, loading, error } = useUseCase(createUser);
311
+ // ...
312
+ }
313
+ ```
314
+
315
+ You can also use `useCAFContext()` and read `.plocs[key]` / `.useCases[key]` when you need the raw registry. When the key is missing or outside a provider, `usePlocFromContext` and `useUseCaseFromContext` return `undefined` (no throw).
316
+
317
+ **Nested providers:** Inner provider does not merge with outer; children see only the nearest provider’s `plocs` / `useCases`. Prefer one root provider with all keys.
318
+
252
319
  ### useRouteManager
253
320
 
254
321
  Hook that provides a `RouteManager` from `@c.a.f/core`:
@@ -297,6 +364,11 @@ function MyComponent() {
297
364
  - `useUseCase` — Hook that wraps UseCase execution with loading/error/data state management; handles RequestResult subscriptions automatically
298
365
  - `CAFErrorBoundary` — Error Boundary component that catches errors from Ploc/UseCase execution; provides error context via React Context
299
366
  - `useCAFError` — Hook to access error context from CAFErrorBoundary
367
+ - `CAFProvider` — Root-level provider for Plocs and UseCases (by key); descendants access via `useCAFContext()` or typed hooks
368
+ - `useCAFContext` — Hook to read the CAF context (`plocs` and `useCases` registries from the nearest `CAFProvider`)
369
+ - `usePlocFromContext` — Hook to get a Ploc by key from context; returns `undefined` if key not registered (generic for type safety)
370
+ - `useUseCaseFromContext` — Hook to get a UseCase by key from context; returns `undefined` if key not registered (generics for args/result)
371
+ - `CAFContext` — React context used by `CAFProvider` (for advanced use)
300
372
  - `usePlocDevTools` — Hook that provides DevTools for a Ploc instance; enables state tracking and time-travel debugging
301
373
  - `useUseCaseDevTools` — Hook that provides DevTools for UseCase execution tracking
302
374
  - `useCAFDevTools` — Main hook that provides centralized DevTools access; tracks all Plocs and UseCases
package/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@c.a.f/infrastructure-react",
3
- "type": "module",
4
- "version": "1.0.2",
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
- "node": "./.build/index.js",
30
- "default": "./.build/index.js"
31
- }
32
- },
33
- "files": [
34
- ".build"
35
- ],
36
- "scripts": {
37
- "build": "tsc --build",
38
- "start": "tsc --watch",
39
- "prepublishOnly": "npm run build",
40
- "test": "node_modules/.bin/vitest run",
41
- "test:watch": "node_modules/.bin/vitest"
42
- },
43
- "dependencies": {
44
- "@c.a.f/core": "^1.0.3",
45
- "@c.a.f/devtools": "^1.0.2",
46
- "react-router-dom": "^6.23.1"
47
- },
48
- "peerDependencies": {
49
- "react": ">=16.8.0"
50
- },
51
- "devDependencies": {
52
- "@testing-library/dom": "^10.4.0",
53
- "@testing-library/jest-dom": "^6.1.5",
54
- "@testing-library/react": "^16.0.0",
55
- "happy-dom": "^15.11.7",
56
- "vitest": "^2.1.0"
57
- }
58
- }
1
+ {
2
+ "name": "@c.a.f/infrastructure-react",
3
+ "type": "module",
4
+ "version": "1.0.4",
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
+ "node": "./.build/index.js",
30
+ "default": "./.build/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ ".build"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc --build",
38
+ "start": "tsc --watch",
39
+ "prepublishOnly": "npm run build",
40
+ "test": "node_modules/.bin/vitest run",
41
+ "test:watch": "node_modules/.bin/vitest"
42
+ },
43
+ "dependencies": {
44
+ "@c.a.f/core": "^1.0.3",
45
+ "@c.a.f/devtools": "^1.0.2",
46
+ "react-router-dom": "^6.23.1"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=16.8.0"
50
+ },
51
+ "devDependencies": {
52
+ "@testing-library/dom": "^10.4.0",
53
+ "@testing-library/jest-dom": "^6.1.5",
54
+ "@testing-library/react": "^16.0.0",
55
+ "happy-dom": "^15.11.7",
56
+ "vitest": "^2.1.0"
57
+ }
58
+ }