@defjs/react 0.0.1-beta.0 → 0.0.1
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 +52 -40
- package/index.d.ts +22 -15
- package/index.js +19 -14
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @defjs/react
|
|
2
2
|
|
|
3
|
-
Thin React
|
|
3
|
+
Thin React plugin for `@defjs/core`. It provides an existing Defjs client through React Context.
|
|
4
|
+
|
|
5
|
+
Keep reusable SDK definitions in their own package or module. A React application configures the client, provides it to components, and executes those definitions through `useClient()`. The SDK does not need a React dependency or a component-owned client.
|
|
6
|
+
|
|
7
|
+
This plugin is optional: import a fixed application-owned client, pass a `Client` explicitly, use Context for tree-level defaults and overrides, or choose your own organization. SDK definitions do not require this plugin. Share an instance only when its captured configuration and state are safe to share; request-specific identity or credentials belong to the appropriate request boundary.
|
|
4
8
|
|
|
5
9
|
Supports React 18+.
|
|
6
10
|
|
|
@@ -16,76 +20,78 @@ The package is ESM and requires `@defjs/core` as a peer in the `^0.4.0` range. I
|
|
|
16
20
|
|
|
17
21
|
`ClientProvider` exposes the exact `Client` instance supplied by the application. `useClient()` reads the nearest provided instance.
|
|
18
22
|
|
|
19
|
-
Client creation and configuration stay in `@defjs/core`. The
|
|
23
|
+
Client creation and configuration stay in `@defjs/core`. The plugin does not create, cache, replace, dispose, abort, or close anything on behalf of the application, and it does not add a query layer, Suspense integration, GraphQL client, or application state management.
|
|
20
24
|
|
|
21
25
|
## Quick Start
|
|
22
26
|
|
|
23
|
-
Define requests in a shared module:
|
|
27
|
+
Define the SDK's requests in a shared module, or import them from your published SDK:
|
|
24
28
|
|
|
25
29
|
```tsx
|
|
26
30
|
// api.ts
|
|
27
|
-
import { defineRequest, struct } from
|
|
31
|
+
import { defineRequest, struct } from "@defjs/core";
|
|
28
32
|
|
|
29
33
|
export const getUser = defineRequest({
|
|
30
|
-
method:
|
|
31
|
-
path:
|
|
32
|
-
input: struct.
|
|
33
|
-
|
|
34
|
-
}),
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
status: 200,
|
|
38
|
-
body: struct.object({ id: struct.number(), name: struct.string() }),
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
})
|
|
34
|
+
method: "GET",
|
|
35
|
+
path: "/users/:id",
|
|
36
|
+
input: struct.object({ id: struct.number() }),
|
|
37
|
+
builder: (input) => ({ path: input }),
|
|
38
|
+
output: struct.object({ id: struct.number(), name: struct.string() }),
|
|
39
|
+
});
|
|
42
40
|
```
|
|
43
41
|
|
|
44
42
|
Create and own the client at the application boundary, then provide it:
|
|
45
43
|
|
|
46
44
|
```tsx
|
|
47
45
|
// app.tsx
|
|
48
|
-
import { createClient, withEndpoint } from
|
|
49
|
-
import { ClientProvider } from
|
|
50
|
-
import { UserProfile } from
|
|
46
|
+
import { createClient, withEndpoint } from "@defjs/core";
|
|
47
|
+
import { ClientProvider } from "@defjs/react";
|
|
48
|
+
import { UserProfile } from "./user-profile";
|
|
51
49
|
|
|
52
|
-
const client = createClient(withEndpoint(
|
|
50
|
+
const client = createClient(withEndpoint("https://api.example.com"));
|
|
53
51
|
|
|
54
52
|
export function App() {
|
|
55
53
|
return (
|
|
56
54
|
<ClientProvider client={client}>
|
|
57
55
|
<UserProfile id={1} />
|
|
58
56
|
</ClientProvider>
|
|
59
|
-
)
|
|
57
|
+
);
|
|
60
58
|
}
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
Read the client inside descendants and own request cleanup where the work starts:
|
|
61
|
+
Read the client inside descendants and own request cleanup where the work starts. Best practice: name the command, pass that name to `execute`, and return before reading the user when `error` is set. Abort still belongs to the effect:
|
|
64
62
|
|
|
65
63
|
```tsx
|
|
66
|
-
import { useEffect, useState } from
|
|
67
|
-
import { useClient } from
|
|
68
|
-
import { getUser } from
|
|
64
|
+
import { useEffect, useState } from "react";
|
|
65
|
+
import { useClient } from "@defjs/react";
|
|
66
|
+
import { getUser } from "./api";
|
|
69
67
|
|
|
70
68
|
export function UserProfile({ id }: { id: number }) {
|
|
71
|
-
const client = useClient()
|
|
72
|
-
const [name, setName] = useState(
|
|
69
|
+
const client = useClient();
|
|
70
|
+
const [name, setName] = useState("loading...");
|
|
73
71
|
|
|
74
72
|
useEffect(() => {
|
|
75
|
-
const abort = new AbortController()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
const abort = new AbortController();
|
|
74
|
+
|
|
75
|
+
const command = getUser({ id });
|
|
76
|
+
void client
|
|
77
|
+
.execute(command, { signal: abort.signal })
|
|
78
|
+
.then(([error, user]) => {
|
|
79
|
+
if (abort.signal.aborted) return;
|
|
80
|
+
if (error) {
|
|
81
|
+
setName(error.message);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setName(user.name);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return () => abort.abort();
|
|
88
|
+
}, [client, id]);
|
|
89
|
+
|
|
90
|
+
return <div>{name}</div>;
|
|
85
91
|
}
|
|
86
92
|
```
|
|
87
93
|
|
|
88
|
-
Nested providers follow normal React Context rules: descendants receive the nearest provider's exact client. Supplying a different `client` prop changes the provided value; the
|
|
94
|
+
Nested providers follow normal React Context rules: descendants receive the nearest provider's exact client. Supplying a different `client` prop changes the provided value; the plugin does not invent a remount or caching policy.
|
|
89
95
|
|
|
90
96
|
## API
|
|
91
97
|
|
|
@@ -95,11 +101,17 @@ Provides the supplied client to descendant components. `children` is optional.
|
|
|
95
101
|
|
|
96
102
|
### `useClient(): Client`
|
|
97
103
|
|
|
98
|
-
|
|
104
|
+
```ts
|
|
105
|
+
function useClient(overrideClient?: Client): Client;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Returns the exact `overrideClient` when supplied; omitted or `undefined` reads the nearest `ClientProvider`. Throws an `Error` only when neither source supplies a client. Zero-argument calls remain valid.
|
|
109
|
+
|
|
110
|
+
Always call `useClient(explicitClient)` at the top level of a component or custom Hook, even with an explicit client; not in an ordinary function or event callback. Do not write `explicitClient ?? useClient()`, which conditionally calls a Hook. The override does not write Context or create a provider, and the Hook still subscribes to Context.
|
|
99
111
|
|
|
100
112
|
## Notes
|
|
101
113
|
|
|
102
|
-
- The published entry is marked with `'use client'`, so React Server Component consumers treat this
|
|
114
|
+
- The published entry is marked with `'use client'`, so React Server Component consumers treat this plugin as a client boundary.
|
|
103
115
|
- Create request-specific clients at the application boundary for SSR data that contains credentials, cookies, or tenant state.
|
|
104
116
|
- The owner that creates a client also owns all requests, SSE streams, and WebSocket sessions started through it. Provider unmount does not clean them up automatically.
|
|
105
117
|
- Import `createClient`, `withEndpoint`, `withInterceptors`, and all other client options from `@defjs/core`.
|
package/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import { Client } from "@defjs/core";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link ClientProvider}: the required `client` and optional `children`.
|
|
6
|
+
*
|
|
7
|
+
* @group react
|
|
8
|
+
*/
|
|
6
9
|
interface ClientProviderProps {
|
|
7
10
|
/** Defjs client owned by the application. */
|
|
8
11
|
client: Client;
|
|
@@ -12,30 +15,34 @@ interface ClientProviderProps {
|
|
|
12
15
|
/**
|
|
13
16
|
* Provide a Defjs client to React descendants via context.
|
|
14
17
|
*
|
|
15
|
-
*
|
|
18
|
+
* Pass an existing client from `createClient` as `client`; descendants read it with `useClient`. The component does not create, cache, or dispose the client. A nested `ClientProvider` overrides the client for its own subtree, and a different `client` prop changes the context value for descendants.
|
|
16
19
|
*
|
|
17
20
|
* @param props - `client` and optional `children`.
|
|
18
21
|
* @returns A context provider element.
|
|
19
22
|
*
|
|
20
|
-
* @
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* </ClientProvider>
|
|
25
|
-
* ```
|
|
23
|
+
* @group react
|
|
24
|
+
* @see {@link Client}
|
|
25
|
+
* @see {@link ClientProviderProps}
|
|
26
|
+
* @exampleFile examples/react/client-provider.tsx#example
|
|
26
27
|
*/
|
|
27
|
-
export declare function ClientProvider({ client, children }: ClientProviderProps):
|
|
28
|
+
export declare function ClientProvider({ client, children }: ClientProviderProps): React.FunctionComponentElement<React.ProviderProps<Client | null>>;
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
+
* React Hook that returns an explicit Defjs client or the nearest {@link ClientProvider}.
|
|
31
|
+
*
|
|
32
|
+
* `useClient(overrideClient)` prefers the exact explicit client; omitted or `undefined` reads the nearest `ClientProvider`. An `Error` is thrown only when neither source supplies a client. Zero-argument `useClient()` calls remain valid.
|
|
33
|
+
*
|
|
34
|
+
* Always call `useClient(explicitClient)` at the top level of a component or custom Hook, even with an explicit client; not in an ordinary function or event callback. Do not write `explicitClient ?? useClient()`, which conditionally calls a Hook. The override does not write Context or create a provider, and the Hook still subscribes to Context.
|
|
30
35
|
*
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
36
|
+
* @param overrideClient - Client to use before context; `undefined` uses the provider.
|
|
37
|
+
* @returns The selected `Client` instance.
|
|
38
|
+
* @throws If neither an explicit client nor a `ClientProvider` is available.
|
|
33
39
|
*
|
|
34
40
|
* @example
|
|
35
41
|
* ```tsx
|
|
36
42
|
* const client = useClient()
|
|
37
43
|
* ```
|
|
44
|
+
* @group react
|
|
45
|
+
* @see {@link Client}
|
|
38
46
|
*/
|
|
39
|
-
export declare function useClient(): Client;
|
|
40
|
-
//#endregion
|
|
47
|
+
export declare function useClient(overrideClient?: Client): Client;
|
|
41
48
|
export type { ClientProviderProps };
|
package/index.js
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { createContext, createElement, useContext } from "react";
|
|
3
|
-
|
|
3
|
+
// src/core.ts
|
|
4
4
|
const HttpClientContext = createContext(null);
|
|
5
5
|
/**
|
|
6
6
|
* Provide a Defjs client to React descendants via context.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Pass an existing client from `createClient` as `client`; descendants read it with `useClient`. The component does not create, cache, or dispose the client. A nested `ClientProvider` overrides the client for its own subtree, and a different `client` prop changes the context value for descendants.
|
|
9
9
|
*
|
|
10
10
|
* @param props - `client` and optional `children`.
|
|
11
11
|
* @returns A context provider element.
|
|
12
12
|
*
|
|
13
|
-
* @
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* </ClientProvider>
|
|
18
|
-
* ```
|
|
13
|
+
* @group react
|
|
14
|
+
* @see {@link Client}
|
|
15
|
+
* @see {@link ClientProviderProps}
|
|
16
|
+
* @exampleFile examples/react/client-provider.tsx#example
|
|
19
17
|
*/
|
|
20
18
|
function ClientProvider({ client, children }) {
|
|
21
19
|
return createElement(HttpClientContext.Provider, { value: client }, children);
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
22
|
+
* React Hook that returns an explicit Defjs client or the nearest {@link ClientProvider}.
|
|
23
|
+
*
|
|
24
|
+
* `useClient(overrideClient)` prefers the exact explicit client; omitted or `undefined` reads the nearest `ClientProvider`. An `Error` is thrown only when neither source supplies a client. Zero-argument `useClient()` calls remain valid.
|
|
25
|
+
*
|
|
26
|
+
* Always call `useClient(explicitClient)` at the top level of a component or custom Hook, even with an explicit client; not in an ordinary function or event callback. Do not write `explicitClient ?? useClient()`, which conditionally calls a Hook. The override does not write Context or create a provider, and the Hook still subscribes to Context.
|
|
25
27
|
*
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
+
* @param overrideClient - Client to use before context; `undefined` uses the provider.
|
|
29
|
+
* @returns The selected `Client` instance.
|
|
30
|
+
* @throws If neither an explicit client nor a `ClientProvider` is available.
|
|
28
31
|
*
|
|
29
32
|
* @example
|
|
30
33
|
* ```tsx
|
|
31
34
|
* const client = useClient()
|
|
32
35
|
* ```
|
|
36
|
+
* @group react
|
|
37
|
+
* @see {@link Client}
|
|
33
38
|
*/
|
|
34
|
-
function useClient() {
|
|
35
|
-
const
|
|
39
|
+
function useClient(overrideClient) {
|
|
40
|
+
const providedClient = useContext(HttpClientContext);
|
|
41
|
+
const client = overrideClient ?? providedClient;
|
|
36
42
|
if (!client) throw new Error("No Defjs client provided. Did you forget to wrap your app in <ClientProvider>?");
|
|
37
43
|
return client;
|
|
38
44
|
}
|
|
39
|
-
//#endregion
|
|
40
45
|
export { ClientProvider, useClient };
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defjs/react",
|
|
3
|
-
"version": "0.0.1
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Provide an existing Defjs client to a React tree.",
|
|
4
5
|
"bugs": {
|
|
5
6
|
"url": "https://github.com/defjs/defjs/issues"
|
|
6
7
|
},
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
"main": "./index.js",
|
|
14
15
|
"types": "./index.d.ts",
|
|
15
16
|
"peerDependencies": {
|
|
16
|
-
"@defjs/core": "0.4.0
|
|
17
|
+
"@defjs/core": "^0.4.0",
|
|
17
18
|
"react": ">=18.0.0"
|
|
18
19
|
},
|
|
19
20
|
"module": "./index.js",
|