@plasius/graph-client-react 0.1.1 → 0.1.2
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 +112 -6
- package/dist/index.cjs +111 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +112 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,128 @@
|
|
|
1
1
|
# @plasius/graph-client-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@plasius/graph-client-react)
|
|
4
|
+
[](https://github.com/Plasius-LTD/graph-client-react/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/Plasius-LTD/graph-client-react)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](./CODE_OF_CONDUCT.md)
|
|
8
|
+
[](./SECURITY.md)
|
|
9
|
+
[](./CHANGELOG.md)
|
|
10
|
+
|
|
11
|
+
[](https://github.com/Plasius-LTD/graph-client-react/actions/workflows/ci.yml)
|
|
12
|
+
[](https://github.com/Plasius-LTD/graph-client-react/actions/workflows/cd.yml)
|
|
13
|
+
|
|
14
|
+
React bindings for `@plasius/graph-client-core` query and mutation workflows.
|
|
15
|
+
|
|
16
|
+
Apache-2.0. ESM + CJS builds. TypeScript types included.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js 24+ (matches `.nvmrc` and CI/CD)
|
|
23
|
+
- React 19 (`peerDependencies`)
|
|
24
|
+
- `@plasius/graph-client-core`
|
|
25
|
+
|
|
26
|
+
---
|
|
4
27
|
|
|
5
28
|
## Installation
|
|
6
29
|
|
|
7
|
-
|
|
30
|
+
```bash
|
|
31
|
+
npm install @plasius/graph-client-react @plasius/graph-client-core
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Exports
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
GraphClientProvider,
|
|
41
|
+
useGraphClient,
|
|
42
|
+
useGraphQuery,
|
|
43
|
+
useGraphMutation,
|
|
44
|
+
type UseGraphQueryOptions,
|
|
45
|
+
type UseGraphQueryState,
|
|
46
|
+
type UseGraphMutationOptions,
|
|
47
|
+
type GraphMutationClient,
|
|
48
|
+
} from "@plasius/graph-client-react";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { GraphClient } from "@plasius/graph-client-core";
|
|
57
|
+
import { GraphClientProvider, useGraphQuery } from "@plasius/graph-client-react";
|
|
58
|
+
|
|
59
|
+
const client = new GraphClient({ transport: { async fetch() { return { data: { ok: true }, version: 1 }; } } });
|
|
60
|
+
|
|
61
|
+
function Profile() {
|
|
62
|
+
const { data, loading, revalidating, error, refresh } = useGraphQuery(
|
|
63
|
+
{
|
|
64
|
+
requests: [{ resolver: "user.profile", key: "user:1" }],
|
|
65
|
+
},
|
|
66
|
+
{ retryAttempts: 1, retryDelayMs: 25 },
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (loading) return <p>Loading</p>;
|
|
70
|
+
if (error) return <p>{error.message}</p>;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<button onClick={() => void refresh()}>
|
|
74
|
+
{revalidating ? "Refreshing..." : JSON.stringify(data?.results)}
|
|
75
|
+
</button>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function App() {
|
|
80
|
+
return (
|
|
81
|
+
<GraphClientProvider client={client}>
|
|
82
|
+
<Profile />
|
|
83
|
+
</GraphClientProvider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
8
89
|
|
|
9
90
|
## Development
|
|
10
91
|
|
|
92
|
+
```bash
|
|
93
|
+
npm run clean
|
|
11
94
|
npm install
|
|
95
|
+
npm run lint
|
|
96
|
+
npm run typecheck
|
|
97
|
+
npm run test:coverage
|
|
12
98
|
npm run build
|
|
13
|
-
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Query State Mapping
|
|
104
|
+
|
|
105
|
+
- `loading`: first request with no cached hook data.
|
|
106
|
+
- `revalidating`: refresh/rerender request when prior hook data exists.
|
|
107
|
+
- `status`: `"loading" | "success" | "error"` terminal state for last request.
|
|
108
|
+
- `error`: normalized to `Error`.
|
|
109
|
+
|
|
110
|
+
Suspense is intentionally disabled in current package scope (see ADR-0003). Passing `suspense: true` to `useGraphQuery` throws.
|
|
111
|
+
|
|
112
|
+
Both hooks support optional `telemetry` (from `TelemetrySink`) to emit analytics signals:
|
|
113
|
+
|
|
114
|
+
- Query: `graph.react.query.run`, `graph.react.query.success`, `graph.react.query.retry`, `graph.react.query.error`
|
|
115
|
+
- Mutation: `graph.react.mutation.success`, `graph.react.mutation.error`
|
|
116
|
+
|
|
117
|
+
---
|
|
14
118
|
|
|
15
119
|
## Architecture
|
|
16
120
|
|
|
17
|
-
-
|
|
18
|
-
- Cross-package
|
|
121
|
+
- Package ADRs: [`docs/adrs`](./docs/adrs)
|
|
122
|
+
- Cross-package ADRs: `plasius-ltd-site/docs/adrs/adr-0020` to `adr-0024`
|
|
123
|
+
|
|
124
|
+
---
|
|
19
125
|
|
|
20
126
|
## License
|
|
21
127
|
|
|
22
|
-
Apache-2.0
|
|
128
|
+
Licensed under the [Apache-2.0 License](./LICENSE).
|
package/dist/index.cjs
CHANGED
|
@@ -48,24 +48,98 @@ function GraphClientProvider(props) {
|
|
|
48
48
|
|
|
49
49
|
// src/hooks.ts
|
|
50
50
|
var import_react3 = require("react");
|
|
51
|
-
var
|
|
51
|
+
var wait = async (delayMs) => {
|
|
52
|
+
if (delayMs <= 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
setTimeout(resolve, delayMs);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
var useGraphQuery = (query, options = {}) => {
|
|
52
60
|
const client = useGraphClient();
|
|
61
|
+
const retryAttempts = Math.max(0, options.retryAttempts ?? 0);
|
|
62
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);
|
|
63
|
+
const telemetry = options.telemetry;
|
|
64
|
+
if (options.suspense) {
|
|
65
|
+
throw new Error("Suspense mode is not supported by useGraphQuery. Use loading/error/revalidating state mapping.");
|
|
66
|
+
}
|
|
53
67
|
const [data, setData] = (0, import_react3.useState)(null);
|
|
68
|
+
const dataRef = (0, import_react3.useRef)(null);
|
|
54
69
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
55
70
|
const [loading, setLoading] = (0, import_react3.useState)(true);
|
|
71
|
+
const [revalidating, setRevalidating] = (0, import_react3.useState)(false);
|
|
72
|
+
const [status, setStatus] = (0, import_react3.useState)("loading");
|
|
73
|
+
(0, import_react3.useEffect)(() => {
|
|
74
|
+
dataRef.current = data;
|
|
75
|
+
}, [data]);
|
|
56
76
|
const run = (0, import_react3.useCallback)(async (forceRefresh = false) => {
|
|
57
|
-
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
const hasExistingData = dataRef.current !== null;
|
|
79
|
+
setLoading(!hasExistingData);
|
|
80
|
+
setRevalidating(hasExistingData);
|
|
81
|
+
if (!hasExistingData) {
|
|
82
|
+
setStatus("loading");
|
|
83
|
+
}
|
|
84
|
+
telemetry?.metric({
|
|
85
|
+
name: "graph.react.query.run",
|
|
86
|
+
value: 1,
|
|
87
|
+
unit: "count",
|
|
88
|
+
tags: {
|
|
89
|
+
forceRefresh: forceRefresh ? "true" : "false",
|
|
90
|
+
hasExistingData: hasExistingData ? "true" : "false"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
58
93
|
setError(null);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
for (let attempt = 0; attempt <= retryAttempts; attempt += 1) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await client.query(query, { allowStale: true, forceRefresh });
|
|
97
|
+
dataRef.current = result;
|
|
98
|
+
setData(result);
|
|
99
|
+
setStatus("success");
|
|
100
|
+
setLoading(false);
|
|
101
|
+
setRevalidating(false);
|
|
102
|
+
telemetry?.metric({
|
|
103
|
+
name: "graph.react.query.success",
|
|
104
|
+
value: Date.now() - startedAt,
|
|
105
|
+
unit: "ms",
|
|
106
|
+
tags: {
|
|
107
|
+
attempt: String(attempt + 1)
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
} catch (queryError) {
|
|
112
|
+
const resolvedError = queryError instanceof Error ? queryError : new Error("Graph query failed");
|
|
113
|
+
const hasRetriesRemaining = attempt < retryAttempts;
|
|
114
|
+
if (!hasRetriesRemaining) {
|
|
115
|
+
setError(resolvedError);
|
|
116
|
+
setStatus("error");
|
|
117
|
+
setLoading(false);
|
|
118
|
+
setRevalidating(false);
|
|
119
|
+
telemetry?.metric({
|
|
120
|
+
name: "graph.react.query.error",
|
|
121
|
+
value: 1,
|
|
122
|
+
unit: "count"
|
|
123
|
+
});
|
|
124
|
+
telemetry?.error({
|
|
125
|
+
message: resolvedError.message,
|
|
126
|
+
source: "graph-client-react.useGraphQuery",
|
|
127
|
+
code: "GRAPH_REACT_QUERY_FAILED"
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
telemetry?.metric({
|
|
132
|
+
name: "graph.react.query.retry",
|
|
133
|
+
value: 1,
|
|
134
|
+
unit: "count",
|
|
135
|
+
tags: {
|
|
136
|
+
attempt: String(attempt + 1)
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
await wait(retryDelayMs);
|
|
140
|
+
}
|
|
67
141
|
}
|
|
68
|
-
}, [client, query]);
|
|
142
|
+
}, [client, query, retryAttempts, retryDelayMs, telemetry]);
|
|
69
143
|
(0, import_react3.useEffect)(() => {
|
|
70
144
|
void run(false);
|
|
71
145
|
}, [run]);
|
|
@@ -73,26 +147,47 @@ var useGraphQuery = (query) => {
|
|
|
73
147
|
await run(true);
|
|
74
148
|
}, [run]);
|
|
75
149
|
return (0, import_react3.useMemo)(
|
|
76
|
-
() => ({ data, error, loading, refresh }),
|
|
77
|
-
[data, error, loading, refresh]
|
|
150
|
+
() => ({ data, error, loading, revalidating, status, refresh }),
|
|
151
|
+
[data, error, loading, revalidating, status, refresh]
|
|
78
152
|
);
|
|
79
153
|
};
|
|
80
|
-
var useGraphMutation = (mutationClient) => {
|
|
154
|
+
var useGraphMutation = (mutationClient, options = {}) => {
|
|
155
|
+
const telemetry = options.telemetry;
|
|
81
156
|
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
82
157
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
83
158
|
const mutate = (0, import_react3.useCallback)(async (command) => {
|
|
159
|
+
const startedAt = Date.now();
|
|
84
160
|
setLoading(true);
|
|
85
161
|
setError(null);
|
|
86
162
|
try {
|
|
87
|
-
|
|
163
|
+
const operation = await mutationClient.write(command);
|
|
164
|
+
telemetry?.metric({
|
|
165
|
+
name: "graph.react.mutation.success",
|
|
166
|
+
value: Date.now() - startedAt,
|
|
167
|
+
unit: "ms",
|
|
168
|
+
tags: {
|
|
169
|
+
state: operation.state
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return operation;
|
|
88
173
|
} catch (mutationError) {
|
|
89
174
|
const resolvedError = mutationError instanceof Error ? mutationError : new Error("Graph mutation failed");
|
|
90
175
|
setError(resolvedError);
|
|
176
|
+
telemetry?.metric({
|
|
177
|
+
name: "graph.react.mutation.error",
|
|
178
|
+
value: 1,
|
|
179
|
+
unit: "count"
|
|
180
|
+
});
|
|
181
|
+
telemetry?.error({
|
|
182
|
+
message: resolvedError.message,
|
|
183
|
+
source: "graph-client-react.useGraphMutation",
|
|
184
|
+
code: "GRAPH_REACT_MUTATION_FAILED"
|
|
185
|
+
});
|
|
91
186
|
throw resolvedError;
|
|
92
187
|
} finally {
|
|
93
188
|
setLoading(false);
|
|
94
189
|
}
|
|
95
|
-
}, [mutationClient]);
|
|
190
|
+
}, [mutationClient, telemetry]);
|
|
96
191
|
return {
|
|
97
192
|
mutate,
|
|
98
193
|
loading,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/context.ts","../src/provider.ts","../src/hooks.ts"],"sourcesContent":["export * from \"./context.js\";\nexport * from \"./provider.js\";\nexport * from \"./hooks.js\";\n","import { createContext, useContext } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\n\nexport const GraphClientContext = createContext<GraphClient | null>(null);\n\nexport const useGraphClient = (): GraphClient => {\n const client = useContext(GraphClientContext);\n if (!client) {\n throw new Error(\"GraphClientContext is missing. Wrap your tree in GraphClientProvider.\");\n }\n\n return client;\n};\n","import { createElement, type ReactNode } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\nimport { GraphClientContext } from \"./context.js\";\n\nexport interface GraphClientProviderProps {\n client: GraphClient;\n children: ReactNode;\n}\n\nexport function GraphClientProvider(props: GraphClientProviderProps) {\n const { client, children } = props;\n return createElement(GraphClientContext.Provider, { value: client }, children);\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport type { GraphQuery, GraphQueryResult, WriteCommand, WriteOperation } from \"@plasius/graph-contracts\";\nimport { useGraphClient } from \"./context.js\";\n\nexport interface UseGraphQueryState {\n data: GraphQueryResult | null;\n error: Error | null;\n loading: boolean;\n refresh: () => Promise<void>;\n}\n\nexport const useGraphQuery = (query: GraphQuery): UseGraphQueryState => {\n const client = useGraphClient();\n const [data, setData] = useState<GraphQueryResult | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n\n const run = useCallback(async (forceRefresh = false) => {\n setLoading(true);\n setError(null);\n try {\n const result = await client.query(query, { allowStale: true, forceRefresh });\n setData(result);\n } catch (queryError) {\n const resolvedError = queryError instanceof Error ? queryError : new Error(\"Graph query failed\");\n setError(resolvedError);\n } finally {\n setLoading(false);\n }\n }, [client, query]);\n\n useEffect(() => {\n void run(false);\n }, [run]);\n\n const refresh = useCallback(async () => {\n await run(true);\n }, [run]);\n\n return useMemo(\n () => ({ data, error, loading, refresh }),\n [data, error, loading, refresh],\n );\n};\n\nexport interface GraphMutationClient {\n write(command: WriteCommand): Promise<WriteOperation>;\n}\n\nexport const useGraphMutation = (mutationClient: GraphMutationClient) => {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const mutate = useCallback(async (command: WriteCommand) => {\n setLoading(true);\n setError(null);\n try {\n return await mutationClient.write(command);\n } catch (mutationError) {\n const resolvedError = mutationError instanceof Error ? mutationError : new Error(\"Graph mutation failed\");\n setError(resolvedError);\n throw resolvedError;\n } finally {\n setLoading(false);\n }\n }, [mutationClient]);\n\n return {\n mutate,\n loading,\n error,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA0C;AAInC,IAAM,yBAAqB,4BAAkC,IAAI;AAEjE,IAAM,iBAAiB,MAAmB;AAC/C,QAAM,aAAS,yBAAW,kBAAkB;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AAEA,SAAO;AACT;;;ACbA,IAAAA,gBAA8C;AAUvC,SAAS,oBAAoB,OAAiC;AACnE,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,aAAO,6BAAc,mBAAmB,UAAU,EAAE,OAAO,OAAO,GAAG,QAAQ;AAC/E;;;ACbA,IAAAC,gBAA0D;AAYnD,IAAM,gBAAgB,CAAC,UAA0C;AACtE,QAAM,SAAS,eAAe;AAC9B,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAkC,IAAI;AAC9D,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AACrD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAkB,IAAI;AAEpD,QAAM,UAAM,2BAAY,OAAO,eAAe,UAAU;AACtD,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,MAAM,OAAO,EAAE,YAAY,MAAM,aAAa,CAAC;AAC3E,cAAQ,MAAM;AAAA,IAChB,SAAS,YAAY;AACnB,YAAM,gBAAgB,sBAAsB,QAAQ,aAAa,IAAI,MAAM,oBAAoB;AAC/F,eAAS,aAAa;AAAA,IACxB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,+BAAU,MAAM;AACd,SAAK,IAAI,KAAK;AAAA,EAChB,GAAG,CAAC,GAAG,CAAC;AAER,QAAM,cAAU,2BAAY,YAAY;AACtC,UAAM,IAAI,IAAI;AAAA,EAChB,GAAG,CAAC,GAAG,CAAC;AAER,aAAO;AAAA,IACL,OAAO,EAAE,MAAM,OAAO,SAAS,QAAQ;AAAA,IACvC,CAAC,MAAM,OAAO,SAAS,OAAO;AAAA,EAChC;AACF;AAMO,IAAM,mBAAmB,CAAC,mBAAwC;AACvE,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,aAAS,2BAAY,OAAO,YAA0B;AAC1D,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,aAAO,MAAM,eAAe,MAAM,OAAO;AAAA,IAC3C,SAAS,eAAe;AACtB,YAAM,gBAAgB,yBAAyB,QAAQ,gBAAgB,IAAI,MAAM,uBAAuB;AACxG,eAAS,aAAa;AACtB,YAAM;AAAA,IACR,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,cAAc,CAAC;AAEnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["import_react","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context.ts","../src/provider.ts","../src/hooks.ts"],"sourcesContent":["export * from \"./context.js\";\nexport * from \"./provider.js\";\nexport * from \"./hooks.js\";\n","import { createContext, useContext } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\n\nexport const GraphClientContext = createContext<GraphClient | null>(null);\n\nexport const useGraphClient = (): GraphClient => {\n const client = useContext(GraphClientContext);\n if (!client) {\n throw new Error(\"GraphClientContext is missing. Wrap your tree in GraphClientProvider.\");\n }\n\n return client;\n};\n","import { createElement, type ReactNode } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\nimport { GraphClientContext } from \"./context.js\";\n\nexport interface GraphClientProviderProps {\n client: GraphClient;\n children: ReactNode;\n}\n\nexport function GraphClientProvider(props: GraphClientProviderProps) {\n const { client, children } = props;\n return createElement(GraphClientContext.Provider, { value: client }, children);\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { GraphQuery, GraphQueryResult, TelemetrySink, WriteCommand, WriteOperation } from \"@plasius/graph-contracts\";\nimport { useGraphClient } from \"./context.js\";\n\nexport interface UseGraphQueryState {\n data: GraphQueryResult | null;\n error: Error | null;\n loading: boolean;\n revalidating: boolean;\n status: \"loading\" | \"success\" | \"error\";\n refresh: () => Promise<void>;\n}\n\nexport interface UseGraphQueryOptions {\n retryAttempts?: number;\n retryDelayMs?: number;\n suspense?: boolean;\n telemetry?: TelemetrySink;\n}\n\nconst wait = async (delayMs: number): Promise<void> => {\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n};\n\nexport const useGraphQuery = (query: GraphQuery, options: UseGraphQueryOptions = {}): UseGraphQueryState => {\n const client = useGraphClient();\n const retryAttempts = Math.max(0, options.retryAttempts ?? 0);\n const retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);\n const telemetry = options.telemetry;\n if (options.suspense) {\n throw new Error(\"Suspense mode is not supported by useGraphQuery. Use loading/error/revalidating state mapping.\");\n }\n\n const [data, setData] = useState<GraphQueryResult | null>(null);\n const dataRef = useRef<GraphQueryResult | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const [revalidating, setRevalidating] = useState<boolean>(false);\n const [status, setStatus] = useState<\"loading\" | \"success\" | \"error\">(\"loading\");\n\n useEffect(() => {\n dataRef.current = data;\n }, [data]);\n\n const run = useCallback(async (forceRefresh = false) => {\n const startedAt = Date.now();\n const hasExistingData = dataRef.current !== null;\n setLoading(!hasExistingData);\n setRevalidating(hasExistingData);\n if (!hasExistingData) {\n setStatus(\"loading\");\n }\n telemetry?.metric({\n name: \"graph.react.query.run\",\n value: 1,\n unit: \"count\",\n tags: {\n forceRefresh: forceRefresh ? \"true\" : \"false\",\n hasExistingData: hasExistingData ? \"true\" : \"false\",\n },\n });\n setError(null);\n for (let attempt = 0; attempt <= retryAttempts; attempt += 1) {\n try {\n const result = await client.query(query, { allowStale: true, forceRefresh });\n dataRef.current = result;\n setData(result);\n setStatus(\"success\");\n setLoading(false);\n setRevalidating(false);\n telemetry?.metric({\n name: \"graph.react.query.success\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n attempt: String(attempt + 1),\n },\n });\n return;\n } catch (queryError) {\n const resolvedError = queryError instanceof Error ? queryError : new Error(\"Graph query failed\");\n const hasRetriesRemaining = attempt < retryAttempts;\n if (!hasRetriesRemaining) {\n setError(resolvedError);\n setStatus(\"error\");\n setLoading(false);\n setRevalidating(false);\n telemetry?.metric({\n name: \"graph.react.query.error\",\n value: 1,\n unit: \"count\",\n });\n telemetry?.error({\n message: resolvedError.message,\n source: \"graph-client-react.useGraphQuery\",\n code: \"GRAPH_REACT_QUERY_FAILED\",\n });\n return;\n }\n\n telemetry?.metric({\n name: \"graph.react.query.retry\",\n value: 1,\n unit: \"count\",\n tags: {\n attempt: String(attempt + 1),\n },\n });\n await wait(retryDelayMs);\n }\n }\n }, [client, query, retryAttempts, retryDelayMs, telemetry]);\n\n useEffect(() => {\n void run(false);\n }, [run]);\n\n const refresh = useCallback(async () => {\n await run(true);\n }, [run]);\n\n return useMemo(\n () => ({ data, error, loading, revalidating, status, refresh }),\n [data, error, loading, revalidating, status, refresh],\n );\n};\n\nexport interface GraphMutationClient {\n write(command: WriteCommand): Promise<WriteOperation>;\n}\n\nexport interface UseGraphMutationOptions {\n telemetry?: TelemetrySink;\n}\n\nexport const useGraphMutation = (mutationClient: GraphMutationClient, options: UseGraphMutationOptions = {}) => {\n const telemetry = options.telemetry;\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const mutate = useCallback(async (command: WriteCommand) => {\n const startedAt = Date.now();\n setLoading(true);\n setError(null);\n try {\n const operation = await mutationClient.write(command);\n telemetry?.metric({\n name: \"graph.react.mutation.success\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n state: operation.state,\n },\n });\n return operation;\n } catch (mutationError) {\n const resolvedError = mutationError instanceof Error ? mutationError : new Error(\"Graph mutation failed\");\n setError(resolvedError);\n telemetry?.metric({\n name: \"graph.react.mutation.error\",\n value: 1,\n unit: \"count\",\n });\n telemetry?.error({\n message: resolvedError.message,\n source: \"graph-client-react.useGraphMutation\",\n code: \"GRAPH_REACT_MUTATION_FAILED\",\n });\n throw resolvedError;\n } finally {\n setLoading(false);\n }\n }, [mutationClient, telemetry]);\n\n return {\n mutate,\n loading,\n error,\n };\n};\n"],"mappings":"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","names":["import_react","import_react"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { GraphClient } from '@plasius/graph-client-core';
|
|
4
|
-
import { WriteCommand, WriteOperation, GraphQueryResult, GraphQuery } from '@plasius/graph-contracts';
|
|
4
|
+
import { WriteCommand, WriteOperation, TelemetrySink, GraphQueryResult, GraphQuery } from '@plasius/graph-contracts';
|
|
5
5
|
|
|
6
6
|
declare const GraphClientContext: react.Context<GraphClient | null>;
|
|
7
7
|
declare const useGraphClient: () => GraphClient;
|
|
@@ -16,16 +16,27 @@ interface UseGraphQueryState {
|
|
|
16
16
|
data: GraphQueryResult | null;
|
|
17
17
|
error: Error | null;
|
|
18
18
|
loading: boolean;
|
|
19
|
+
revalidating: boolean;
|
|
20
|
+
status: "loading" | "success" | "error";
|
|
19
21
|
refresh: () => Promise<void>;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
interface UseGraphQueryOptions {
|
|
24
|
+
retryAttempts?: number;
|
|
25
|
+
retryDelayMs?: number;
|
|
26
|
+
suspense?: boolean;
|
|
27
|
+
telemetry?: TelemetrySink;
|
|
28
|
+
}
|
|
29
|
+
declare const useGraphQuery: (query: GraphQuery, options?: UseGraphQueryOptions) => UseGraphQueryState;
|
|
22
30
|
interface GraphMutationClient {
|
|
23
31
|
write(command: WriteCommand): Promise<WriteOperation>;
|
|
24
32
|
}
|
|
25
|
-
|
|
33
|
+
interface UseGraphMutationOptions {
|
|
34
|
+
telemetry?: TelemetrySink;
|
|
35
|
+
}
|
|
36
|
+
declare const useGraphMutation: (mutationClient: GraphMutationClient, options?: UseGraphMutationOptions) => {
|
|
26
37
|
mutate: (command: WriteCommand) => Promise<WriteOperation>;
|
|
27
38
|
loading: boolean;
|
|
28
39
|
error: Error | null;
|
|
29
40
|
};
|
|
30
41
|
|
|
31
|
-
export { GraphClientContext, GraphClientProvider, type GraphClientProviderProps, type GraphMutationClient, type UseGraphQueryState, useGraphClient, useGraphMutation, useGraphQuery };
|
|
42
|
+
export { GraphClientContext, GraphClientProvider, type GraphClientProviderProps, type GraphMutationClient, type UseGraphMutationOptions, type UseGraphQueryOptions, type UseGraphQueryState, useGraphClient, useGraphMutation, useGraphQuery };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { GraphClient } from '@plasius/graph-client-core';
|
|
4
|
-
import { WriteCommand, WriteOperation, GraphQueryResult, GraphQuery } from '@plasius/graph-contracts';
|
|
4
|
+
import { WriteCommand, WriteOperation, TelemetrySink, GraphQueryResult, GraphQuery } from '@plasius/graph-contracts';
|
|
5
5
|
|
|
6
6
|
declare const GraphClientContext: react.Context<GraphClient | null>;
|
|
7
7
|
declare const useGraphClient: () => GraphClient;
|
|
@@ -16,16 +16,27 @@ interface UseGraphQueryState {
|
|
|
16
16
|
data: GraphQueryResult | null;
|
|
17
17
|
error: Error | null;
|
|
18
18
|
loading: boolean;
|
|
19
|
+
revalidating: boolean;
|
|
20
|
+
status: "loading" | "success" | "error";
|
|
19
21
|
refresh: () => Promise<void>;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
interface UseGraphQueryOptions {
|
|
24
|
+
retryAttempts?: number;
|
|
25
|
+
retryDelayMs?: number;
|
|
26
|
+
suspense?: boolean;
|
|
27
|
+
telemetry?: TelemetrySink;
|
|
28
|
+
}
|
|
29
|
+
declare const useGraphQuery: (query: GraphQuery, options?: UseGraphQueryOptions) => UseGraphQueryState;
|
|
22
30
|
interface GraphMutationClient {
|
|
23
31
|
write(command: WriteCommand): Promise<WriteOperation>;
|
|
24
32
|
}
|
|
25
|
-
|
|
33
|
+
interface UseGraphMutationOptions {
|
|
34
|
+
telemetry?: TelemetrySink;
|
|
35
|
+
}
|
|
36
|
+
declare const useGraphMutation: (mutationClient: GraphMutationClient, options?: UseGraphMutationOptions) => {
|
|
26
37
|
mutate: (command: WriteCommand) => Promise<WriteOperation>;
|
|
27
38
|
loading: boolean;
|
|
28
39
|
error: Error | null;
|
|
29
40
|
};
|
|
30
41
|
|
|
31
|
-
export { GraphClientContext, GraphClientProvider, type GraphClientProviderProps, type GraphMutationClient, type UseGraphQueryState, useGraphClient, useGraphMutation, useGraphQuery };
|
|
42
|
+
export { GraphClientContext, GraphClientProvider, type GraphClientProviderProps, type GraphMutationClient, type UseGraphMutationOptions, type UseGraphQueryOptions, type UseGraphQueryState, useGraphClient, useGraphMutation, useGraphQuery };
|
package/dist/index.js
CHANGED
|
@@ -17,25 +17,99 @@ function GraphClientProvider(props) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// src/hooks.ts
|
|
20
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
21
|
-
var
|
|
20
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
21
|
+
var wait = async (delayMs) => {
|
|
22
|
+
if (delayMs <= 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await new Promise((resolve) => {
|
|
26
|
+
setTimeout(resolve, delayMs);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var useGraphQuery = (query, options = {}) => {
|
|
22
30
|
const client = useGraphClient();
|
|
31
|
+
const retryAttempts = Math.max(0, options.retryAttempts ?? 0);
|
|
32
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);
|
|
33
|
+
const telemetry = options.telemetry;
|
|
34
|
+
if (options.suspense) {
|
|
35
|
+
throw new Error("Suspense mode is not supported by useGraphQuery. Use loading/error/revalidating state mapping.");
|
|
36
|
+
}
|
|
23
37
|
const [data, setData] = useState(null);
|
|
38
|
+
const dataRef = useRef(null);
|
|
24
39
|
const [error, setError] = useState(null);
|
|
25
40
|
const [loading, setLoading] = useState(true);
|
|
41
|
+
const [revalidating, setRevalidating] = useState(false);
|
|
42
|
+
const [status, setStatus] = useState("loading");
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
dataRef.current = data;
|
|
45
|
+
}, [data]);
|
|
26
46
|
const run = useCallback(async (forceRefresh = false) => {
|
|
27
|
-
|
|
47
|
+
const startedAt = Date.now();
|
|
48
|
+
const hasExistingData = dataRef.current !== null;
|
|
49
|
+
setLoading(!hasExistingData);
|
|
50
|
+
setRevalidating(hasExistingData);
|
|
51
|
+
if (!hasExistingData) {
|
|
52
|
+
setStatus("loading");
|
|
53
|
+
}
|
|
54
|
+
telemetry?.metric({
|
|
55
|
+
name: "graph.react.query.run",
|
|
56
|
+
value: 1,
|
|
57
|
+
unit: "count",
|
|
58
|
+
tags: {
|
|
59
|
+
forceRefresh: forceRefresh ? "true" : "false",
|
|
60
|
+
hasExistingData: hasExistingData ? "true" : "false"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
28
63
|
setError(null);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
for (let attempt = 0; attempt <= retryAttempts; attempt += 1) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await client.query(query, { allowStale: true, forceRefresh });
|
|
67
|
+
dataRef.current = result;
|
|
68
|
+
setData(result);
|
|
69
|
+
setStatus("success");
|
|
70
|
+
setLoading(false);
|
|
71
|
+
setRevalidating(false);
|
|
72
|
+
telemetry?.metric({
|
|
73
|
+
name: "graph.react.query.success",
|
|
74
|
+
value: Date.now() - startedAt,
|
|
75
|
+
unit: "ms",
|
|
76
|
+
tags: {
|
|
77
|
+
attempt: String(attempt + 1)
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
} catch (queryError) {
|
|
82
|
+
const resolvedError = queryError instanceof Error ? queryError : new Error("Graph query failed");
|
|
83
|
+
const hasRetriesRemaining = attempt < retryAttempts;
|
|
84
|
+
if (!hasRetriesRemaining) {
|
|
85
|
+
setError(resolvedError);
|
|
86
|
+
setStatus("error");
|
|
87
|
+
setLoading(false);
|
|
88
|
+
setRevalidating(false);
|
|
89
|
+
telemetry?.metric({
|
|
90
|
+
name: "graph.react.query.error",
|
|
91
|
+
value: 1,
|
|
92
|
+
unit: "count"
|
|
93
|
+
});
|
|
94
|
+
telemetry?.error({
|
|
95
|
+
message: resolvedError.message,
|
|
96
|
+
source: "graph-client-react.useGraphQuery",
|
|
97
|
+
code: "GRAPH_REACT_QUERY_FAILED"
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
telemetry?.metric({
|
|
102
|
+
name: "graph.react.query.retry",
|
|
103
|
+
value: 1,
|
|
104
|
+
unit: "count",
|
|
105
|
+
tags: {
|
|
106
|
+
attempt: String(attempt + 1)
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
await wait(retryDelayMs);
|
|
110
|
+
}
|
|
37
111
|
}
|
|
38
|
-
}, [client, query]);
|
|
112
|
+
}, [client, query, retryAttempts, retryDelayMs, telemetry]);
|
|
39
113
|
useEffect(() => {
|
|
40
114
|
void run(false);
|
|
41
115
|
}, [run]);
|
|
@@ -43,26 +117,47 @@ var useGraphQuery = (query) => {
|
|
|
43
117
|
await run(true);
|
|
44
118
|
}, [run]);
|
|
45
119
|
return useMemo(
|
|
46
|
-
() => ({ data, error, loading, refresh }),
|
|
47
|
-
[data, error, loading, refresh]
|
|
120
|
+
() => ({ data, error, loading, revalidating, status, refresh }),
|
|
121
|
+
[data, error, loading, revalidating, status, refresh]
|
|
48
122
|
);
|
|
49
123
|
};
|
|
50
|
-
var useGraphMutation = (mutationClient) => {
|
|
124
|
+
var useGraphMutation = (mutationClient, options = {}) => {
|
|
125
|
+
const telemetry = options.telemetry;
|
|
51
126
|
const [loading, setLoading] = useState(false);
|
|
52
127
|
const [error, setError] = useState(null);
|
|
53
128
|
const mutate = useCallback(async (command) => {
|
|
129
|
+
const startedAt = Date.now();
|
|
54
130
|
setLoading(true);
|
|
55
131
|
setError(null);
|
|
56
132
|
try {
|
|
57
|
-
|
|
133
|
+
const operation = await mutationClient.write(command);
|
|
134
|
+
telemetry?.metric({
|
|
135
|
+
name: "graph.react.mutation.success",
|
|
136
|
+
value: Date.now() - startedAt,
|
|
137
|
+
unit: "ms",
|
|
138
|
+
tags: {
|
|
139
|
+
state: operation.state
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return operation;
|
|
58
143
|
} catch (mutationError) {
|
|
59
144
|
const resolvedError = mutationError instanceof Error ? mutationError : new Error("Graph mutation failed");
|
|
60
145
|
setError(resolvedError);
|
|
146
|
+
telemetry?.metric({
|
|
147
|
+
name: "graph.react.mutation.error",
|
|
148
|
+
value: 1,
|
|
149
|
+
unit: "count"
|
|
150
|
+
});
|
|
151
|
+
telemetry?.error({
|
|
152
|
+
message: resolvedError.message,
|
|
153
|
+
source: "graph-client-react.useGraphMutation",
|
|
154
|
+
code: "GRAPH_REACT_MUTATION_FAILED"
|
|
155
|
+
});
|
|
61
156
|
throw resolvedError;
|
|
62
157
|
} finally {
|
|
63
158
|
setLoading(false);
|
|
64
159
|
}
|
|
65
|
-
}, [mutationClient]);
|
|
160
|
+
}, [mutationClient, telemetry]);
|
|
66
161
|
return {
|
|
67
162
|
mutate,
|
|
68
163
|
loading,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.ts","../src/provider.ts","../src/hooks.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\n\nexport const GraphClientContext = createContext<GraphClient | null>(null);\n\nexport const useGraphClient = (): GraphClient => {\n const client = useContext(GraphClientContext);\n if (!client) {\n throw new Error(\"GraphClientContext is missing. Wrap your tree in GraphClientProvider.\");\n }\n\n return client;\n};\n","import { createElement, type ReactNode } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\nimport { GraphClientContext } from \"./context.js\";\n\nexport interface GraphClientProviderProps {\n client: GraphClient;\n children: ReactNode;\n}\n\nexport function GraphClientProvider(props: GraphClientProviderProps) {\n const { client, children } = props;\n return createElement(GraphClientContext.Provider, { value: client }, children);\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport type { GraphQuery, GraphQueryResult, WriteCommand, WriteOperation } from \"@plasius/graph-contracts\";\nimport { useGraphClient } from \"./context.js\";\n\nexport interface UseGraphQueryState {\n data: GraphQueryResult | null;\n error: Error | null;\n loading: boolean;\n refresh: () => Promise<void>;\n}\n\nexport const useGraphQuery = (query: GraphQuery): UseGraphQueryState => {\n const client = useGraphClient();\n const [data, setData] = useState<GraphQueryResult | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n\n const run = useCallback(async (forceRefresh = false) => {\n setLoading(true);\n setError(null);\n try {\n const result = await client.query(query, { allowStale: true, forceRefresh });\n setData(result);\n } catch (queryError) {\n const resolvedError = queryError instanceof Error ? queryError : new Error(\"Graph query failed\");\n setError(resolvedError);\n } finally {\n setLoading(false);\n }\n }, [client, query]);\n\n useEffect(() => {\n void run(false);\n }, [run]);\n\n const refresh = useCallback(async () => {\n await run(true);\n }, [run]);\n\n return useMemo(\n () => ({ data, error, loading, refresh }),\n [data, error, loading, refresh],\n );\n};\n\nexport interface GraphMutationClient {\n write(command: WriteCommand): Promise<WriteOperation>;\n}\n\nexport const useGraphMutation = (mutationClient: GraphMutationClient) => {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const mutate = useCallback(async (command: WriteCommand) => {\n setLoading(true);\n setError(null);\n try {\n return await mutationClient.write(command);\n } catch (mutationError) {\n const resolvedError = mutationError instanceof Error ? mutationError : new Error(\"Graph mutation failed\");\n setError(resolvedError);\n throw resolvedError;\n } finally {\n setLoading(false);\n }\n }, [mutationClient]);\n\n return {\n mutate,\n loading,\n error,\n };\n};\n"],"mappings":";AAAA,SAAS,eAAe,kBAAkB;AAInC,IAAM,qBAAqB,cAAkC,IAAI;AAEjE,IAAM,iBAAiB,MAAmB;AAC/C,QAAM,SAAS,WAAW,kBAAkB;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AAEA,SAAO;AACT;;;ACbA,SAAS,qBAAqC;AAUvC,SAAS,oBAAoB,OAAiC;AACnE,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,SAAO,cAAc,mBAAmB,UAAU,EAAE,OAAO,OAAO,GAAG,QAAQ;AAC/E;;;ACbA,SAAS,aAAa,WAAW,SAAS,gBAAgB;AAYnD,IAAM,gBAAgB,CAAC,UAA0C;AACtE,QAAM,SAAS,eAAe;AAC9B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAkC,IAAI;AAC9D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,IAAI;AAEpD,QAAM,MAAM,YAAY,OAAO,eAAe,UAAU;AACtD,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,MAAM,OAAO,EAAE,YAAY,MAAM,aAAa,CAAC;AAC3E,cAAQ,MAAM;AAAA,IAChB,SAAS,YAAY;AACnB,YAAM,gBAAgB,sBAAsB,QAAQ,aAAa,IAAI,MAAM,oBAAoB;AAC/F,eAAS,aAAa;AAAA,IACxB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,YAAU,MAAM;AACd,SAAK,IAAI,KAAK;AAAA,EAChB,GAAG,CAAC,GAAG,CAAC;AAER,QAAM,UAAU,YAAY,YAAY;AACtC,UAAM,IAAI,IAAI;AAAA,EAChB,GAAG,CAAC,GAAG,CAAC;AAER,SAAO;AAAA,IACL,OAAO,EAAE,MAAM,OAAO,SAAS,QAAQ;AAAA,IACvC,CAAC,MAAM,OAAO,SAAS,OAAO;AAAA,EAChC;AACF;AAMO,IAAM,mBAAmB,CAAC,mBAAwC;AACvE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,SAAS,YAAY,OAAO,YAA0B;AAC1D,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,aAAO,MAAM,eAAe,MAAM,OAAO;AAAA,IAC3C,SAAS,eAAe;AACtB,YAAM,gBAAgB,yBAAyB,QAAQ,gBAAgB,IAAI,MAAM,uBAAuB;AACxG,eAAS,aAAa;AACtB,YAAM;AAAA,IACR,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,cAAc,CAAC;AAEnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/provider.ts","../src/hooks.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\n\nexport const GraphClientContext = createContext<GraphClient | null>(null);\n\nexport const useGraphClient = (): GraphClient => {\n const client = useContext(GraphClientContext);\n if (!client) {\n throw new Error(\"GraphClientContext is missing. Wrap your tree in GraphClientProvider.\");\n }\n\n return client;\n};\n","import { createElement, type ReactNode } from \"react\";\n\nimport type { GraphClient } from \"@plasius/graph-client-core\";\nimport { GraphClientContext } from \"./context.js\";\n\nexport interface GraphClientProviderProps {\n client: GraphClient;\n children: ReactNode;\n}\n\nexport function GraphClientProvider(props: GraphClientProviderProps) {\n const { client, children } = props;\n return createElement(GraphClientContext.Provider, { value: client }, children);\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { GraphQuery, GraphQueryResult, TelemetrySink, WriteCommand, WriteOperation } from \"@plasius/graph-contracts\";\nimport { useGraphClient } from \"./context.js\";\n\nexport interface UseGraphQueryState {\n data: GraphQueryResult | null;\n error: Error | null;\n loading: boolean;\n revalidating: boolean;\n status: \"loading\" | \"success\" | \"error\";\n refresh: () => Promise<void>;\n}\n\nexport interface UseGraphQueryOptions {\n retryAttempts?: number;\n retryDelayMs?: number;\n suspense?: boolean;\n telemetry?: TelemetrySink;\n}\n\nconst wait = async (delayMs: number): Promise<void> => {\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n};\n\nexport const useGraphQuery = (query: GraphQuery, options: UseGraphQueryOptions = {}): UseGraphQueryState => {\n const client = useGraphClient();\n const retryAttempts = Math.max(0, options.retryAttempts ?? 0);\n const retryDelayMs = Math.max(0, options.retryDelayMs ?? 0);\n const telemetry = options.telemetry;\n if (options.suspense) {\n throw new Error(\"Suspense mode is not supported by useGraphQuery. Use loading/error/revalidating state mapping.\");\n }\n\n const [data, setData] = useState<GraphQueryResult | null>(null);\n const dataRef = useRef<GraphQueryResult | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const [revalidating, setRevalidating] = useState<boolean>(false);\n const [status, setStatus] = useState<\"loading\" | \"success\" | \"error\">(\"loading\");\n\n useEffect(() => {\n dataRef.current = data;\n }, [data]);\n\n const run = useCallback(async (forceRefresh = false) => {\n const startedAt = Date.now();\n const hasExistingData = dataRef.current !== null;\n setLoading(!hasExistingData);\n setRevalidating(hasExistingData);\n if (!hasExistingData) {\n setStatus(\"loading\");\n }\n telemetry?.metric({\n name: \"graph.react.query.run\",\n value: 1,\n unit: \"count\",\n tags: {\n forceRefresh: forceRefresh ? \"true\" : \"false\",\n hasExistingData: hasExistingData ? \"true\" : \"false\",\n },\n });\n setError(null);\n for (let attempt = 0; attempt <= retryAttempts; attempt += 1) {\n try {\n const result = await client.query(query, { allowStale: true, forceRefresh });\n dataRef.current = result;\n setData(result);\n setStatus(\"success\");\n setLoading(false);\n setRevalidating(false);\n telemetry?.metric({\n name: \"graph.react.query.success\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n attempt: String(attempt + 1),\n },\n });\n return;\n } catch (queryError) {\n const resolvedError = queryError instanceof Error ? queryError : new Error(\"Graph query failed\");\n const hasRetriesRemaining = attempt < retryAttempts;\n if (!hasRetriesRemaining) {\n setError(resolvedError);\n setStatus(\"error\");\n setLoading(false);\n setRevalidating(false);\n telemetry?.metric({\n name: \"graph.react.query.error\",\n value: 1,\n unit: \"count\",\n });\n telemetry?.error({\n message: resolvedError.message,\n source: \"graph-client-react.useGraphQuery\",\n code: \"GRAPH_REACT_QUERY_FAILED\",\n });\n return;\n }\n\n telemetry?.metric({\n name: \"graph.react.query.retry\",\n value: 1,\n unit: \"count\",\n tags: {\n attempt: String(attempt + 1),\n },\n });\n await wait(retryDelayMs);\n }\n }\n }, [client, query, retryAttempts, retryDelayMs, telemetry]);\n\n useEffect(() => {\n void run(false);\n }, [run]);\n\n const refresh = useCallback(async () => {\n await run(true);\n }, [run]);\n\n return useMemo(\n () => ({ data, error, loading, revalidating, status, refresh }),\n [data, error, loading, revalidating, status, refresh],\n );\n};\n\nexport interface GraphMutationClient {\n write(command: WriteCommand): Promise<WriteOperation>;\n}\n\nexport interface UseGraphMutationOptions {\n telemetry?: TelemetrySink;\n}\n\nexport const useGraphMutation = (mutationClient: GraphMutationClient, options: UseGraphMutationOptions = {}) => {\n const telemetry = options.telemetry;\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const mutate = useCallback(async (command: WriteCommand) => {\n const startedAt = Date.now();\n setLoading(true);\n setError(null);\n try {\n const operation = await mutationClient.write(command);\n telemetry?.metric({\n name: \"graph.react.mutation.success\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n state: operation.state,\n },\n });\n return operation;\n } catch (mutationError) {\n const resolvedError = mutationError instanceof Error ? mutationError : new Error(\"Graph mutation failed\");\n setError(resolvedError);\n telemetry?.metric({\n name: \"graph.react.mutation.error\",\n value: 1,\n unit: \"count\",\n });\n telemetry?.error({\n message: resolvedError.message,\n source: \"graph-client-react.useGraphMutation\",\n code: \"GRAPH_REACT_MUTATION_FAILED\",\n });\n throw resolvedError;\n } finally {\n setLoading(false);\n }\n }, [mutationClient, telemetry]);\n\n return {\n mutate,\n loading,\n error,\n };\n};\n"],"mappings":"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","names":[]}
|