@pylo/react 0.7.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.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/chunk-DOPZ5GRU.js +540 -0
- package/dist/chunk-IKQ52QLH.js +347 -0
- package/dist/chunk-SBMMJYJT.js +346 -0
- package/dist/chunk-VUY6OTMP.js +540 -0
- package/dist/cli.js +27 -0
- package/dist/codegen.d.ts +1 -0
- package/dist/codegen.js +5 -0
- package/dist/hooks.d.ts +97 -0
- package/dist/hooks.js +8 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +28 -0
- package/dist/provider-DTTtfhib.d.ts +50 -0
- package/dist/provider.d.ts +6 -0
- package/dist/provider.js +12 -0
- package/dist/transport-55cSrvQo.d.ts +14 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Okeano GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# @pylo/react
|
|
2
|
+
|
|
3
|
+
Type-safe Pylo SDK for React single-page apps. Hooks, a session provider, and a
|
|
4
|
+
typed client, generated from your own Pylo schema.
|
|
5
|
+
|
|
6
|
+
For Next.js use [`@pylo/nextjs`](../nextjs), which keeps tokens in httpOnly
|
|
7
|
+
cookies. For Expo and React Native use [`@pylo/expo`](../expo).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @pylo/react @tanstack/react-query
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Generate types
|
|
16
|
+
|
|
17
|
+
Add a `pylo.config.ts` at your project root, then run the generator. It writes
|
|
18
|
+
`.pylo/index.ts` and registers the schema, so no `declare module` is needed.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx pylo generate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Set up
|
|
25
|
+
|
|
26
|
+
Your app owns the QueryClient. `PyloProvider` goes inside it, because signing
|
|
27
|
+
out clears the query cache.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
31
|
+
import { PyloProvider } from "@pylo/react";
|
|
32
|
+
|
|
33
|
+
const queryClient = new QueryClient();
|
|
34
|
+
|
|
35
|
+
export function App() {
|
|
36
|
+
return (
|
|
37
|
+
<QueryClientProvider client={queryClient}>
|
|
38
|
+
<PyloProvider appId={import.meta.env.VITE_PYLO_APP_ID}>
|
|
39
|
+
<Routes />
|
|
40
|
+
</PyloProvider>
|
|
41
|
+
</QueryClientProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Sign in
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { usePyloAuth } from "@pylo/react";
|
|
50
|
+
|
|
51
|
+
function SignIn() {
|
|
52
|
+
const { isLoading, isSignedIn, user, login, logout } = usePyloAuth();
|
|
53
|
+
|
|
54
|
+
if (isLoading) return <Spinner />;
|
|
55
|
+
if (isSignedIn) return <button onClick={() => logout()}>Sign out {user?.email}</button>;
|
|
56
|
+
|
|
57
|
+
return <button onClick={() => login(email, password)}>Sign in</button>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`isLoading` stays true until stored tokens have been read, so an already
|
|
62
|
+
signed-in user never sees a login screen flash.
|
|
63
|
+
|
|
64
|
+
## Query data
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { createPyloHooks } from "@pylo/react";
|
|
68
|
+
import type { PyloSchema } from "@pylo/types";
|
|
69
|
+
|
|
70
|
+
const { usePyloList, usePyloUpsert } = createPyloHooks<PyloSchema>();
|
|
71
|
+
|
|
72
|
+
function Contacts() {
|
|
73
|
+
const { data, isLoading } = usePyloList("contact", {
|
|
74
|
+
select: { id: true, name: true, company: { select: { name: true } } },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const upsert = usePyloUpsert("contact");
|
|
78
|
+
|
|
79
|
+
return <List rows={data} onRename={(id, name) => upsert.mutate({ id, name })} />;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For imperative calls outside a query, `usePyloClient<PyloSchema>()` returns the
|
|
84
|
+
same typed client the server SDK exposes.
|
|
85
|
+
|
|
86
|
+
## Token storage
|
|
87
|
+
|
|
88
|
+
Tokens live in `localStorage`, which any script running on your page can read.
|
|
89
|
+
Refresh tokens rotate on every use, but a strong Content Security Policy is what
|
|
90
|
+
actually protects them. If your app has a backend, keep the tokens there and
|
|
91
|
+
proxy requests through it instead.
|
|
92
|
+
|
|
93
|
+
The token is refreshed before it expires and once more if a request comes back
|
|
94
|
+
unauthorized. Only a rejected refresh token ends the session: a transient server
|
|
95
|
+
error leaves it intact, so a backend deploy does not sign everyone out.
|
|
96
|
+
|
|
97
|
+
## Bringing your own auth
|
|
98
|
+
|
|
99
|
+
Pass `getToken` instead, and the provider skips login, logout and refresh
|
|
100
|
+
entirely.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<PyloProvider getToken={() => auth.getAccessToken()}>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Custom storage
|
|
107
|
+
|
|
108
|
+
`PyloProvider` takes any `PyloStorage`: three async methods for get, set and
|
|
109
|
+
remove.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import type { PyloStorage } from "@pylo/react";
|
|
113
|
+
```
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import {
|
|
2
|
+
usePyloTransport
|
|
3
|
+
} from "./chunk-IKQ52QLH.js";
|
|
4
|
+
|
|
5
|
+
// src/hooks.ts
|
|
6
|
+
import { useCallback, useRef, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
useQuery,
|
|
9
|
+
useInfiniteQuery,
|
|
10
|
+
useMutation,
|
|
11
|
+
useQueryClient
|
|
12
|
+
} from "@tanstack/react-query";
|
|
13
|
+
import {
|
|
14
|
+
buildListQuery,
|
|
15
|
+
buildByIdQuery,
|
|
16
|
+
buildMeQuery,
|
|
17
|
+
mergeHeaders,
|
|
18
|
+
flagsToHeaders,
|
|
19
|
+
capitalize,
|
|
20
|
+
toAggregateResult
|
|
21
|
+
} from "@pylo/core";
|
|
22
|
+
import {
|
|
23
|
+
buildUpsertMutation,
|
|
24
|
+
buildBulkUpsertMutation,
|
|
25
|
+
buildDeleteMutation,
|
|
26
|
+
buildIngestEventsMutation,
|
|
27
|
+
buildEntityAggregateQuery,
|
|
28
|
+
buildEventAggregateQuery,
|
|
29
|
+
buildEventListQuery,
|
|
30
|
+
buildEventPropertyKeysQuery,
|
|
31
|
+
buildEventFieldValuesQuery
|
|
32
|
+
} from "@pylo/core";
|
|
33
|
+
import {
|
|
34
|
+
CREATE_UPLOAD_MUTATION,
|
|
35
|
+
CREATE_DOWNLOAD_MUTATION,
|
|
36
|
+
buildCreateUploadInput,
|
|
37
|
+
buildAttachMutation,
|
|
38
|
+
uploadToUrl,
|
|
39
|
+
toUploadPart
|
|
40
|
+
} from "@pylo/core";
|
|
41
|
+
function createPyloHooks(options) {
|
|
42
|
+
var _a;
|
|
43
|
+
const useTransport = (_a = options == null ? void 0 : options.useTransport) != null ? _a : usePyloTransport;
|
|
44
|
+
const globalHeaders = options == null ? void 0 : options.headers;
|
|
45
|
+
function usePyloList(entity, queryOptions) {
|
|
46
|
+
var _a2, _b, _c;
|
|
47
|
+
const transport = useTransport();
|
|
48
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
49
|
+
const queryKey = [
|
|
50
|
+
"pylo",
|
|
51
|
+
entity,
|
|
52
|
+
"list",
|
|
53
|
+
{
|
|
54
|
+
filter: queryOptions == null ? void 0 : queryOptions.filter,
|
|
55
|
+
pagination: queryOptions == null ? void 0 : queryOptions.pagination,
|
|
56
|
+
select: queryOptions == null ? void 0 : queryOptions.select,
|
|
57
|
+
headers: merged
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
const result = useQuery({
|
|
61
|
+
queryKey,
|
|
62
|
+
queryFn: async () => {
|
|
63
|
+
const { query, variables } = buildListQuery(
|
|
64
|
+
entity,
|
|
65
|
+
queryOptions
|
|
66
|
+
);
|
|
67
|
+
const data = await transport(query, variables, merged);
|
|
68
|
+
const listKey = `${entity}List`;
|
|
69
|
+
return data[listKey];
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
data: (_b = (_a2 = result.data) == null ? void 0 : _a2.data) != null ? _b : void 0,
|
|
74
|
+
pagination: (_c = result.data) == null ? void 0 : _c.pagination,
|
|
75
|
+
isLoading: result.isLoading,
|
|
76
|
+
error: result.error,
|
|
77
|
+
refetch: result.refetch
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function usePyloInfiniteList(entity, infiniteOptions) {
|
|
81
|
+
var _a2;
|
|
82
|
+
const transport = useTransport();
|
|
83
|
+
const perPage = (_a2 = infiniteOptions == null ? void 0 : infiniteOptions.perPage) != null ? _a2 : 20;
|
|
84
|
+
const merged = mergeHeaders(globalHeaders, infiniteOptions == null ? void 0 : infiniteOptions.headers);
|
|
85
|
+
return useInfiniteQuery({
|
|
86
|
+
queryKey: [
|
|
87
|
+
"pylo",
|
|
88
|
+
entity,
|
|
89
|
+
"infiniteList",
|
|
90
|
+
{
|
|
91
|
+
perPage,
|
|
92
|
+
filter: infiniteOptions == null ? void 0 : infiniteOptions.filter,
|
|
93
|
+
select: infiniteOptions == null ? void 0 : infiniteOptions.select,
|
|
94
|
+
headers: merged
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
queryFn: async ({ pageParam }) => {
|
|
98
|
+
const runtimeOptions = {
|
|
99
|
+
select: infiniteOptions == null ? void 0 : infiniteOptions.select,
|
|
100
|
+
filter: infiniteOptions == null ? void 0 : infiniteOptions.filter,
|
|
101
|
+
pagination: { page: pageParam, per_page: perPage }
|
|
102
|
+
};
|
|
103
|
+
const { query, variables } = buildListQuery(
|
|
104
|
+
entity,
|
|
105
|
+
runtimeOptions
|
|
106
|
+
);
|
|
107
|
+
const data = await transport(query, variables, merged);
|
|
108
|
+
const listKey = `${entity}List`;
|
|
109
|
+
return data[listKey];
|
|
110
|
+
},
|
|
111
|
+
initialPageParam: 1,
|
|
112
|
+
getNextPageParam: (lastPage) => {
|
|
113
|
+
if (lastPage.pagination.has_more_pages) {
|
|
114
|
+
return lastPage.pagination.current_page + 1;
|
|
115
|
+
}
|
|
116
|
+
return void 0;
|
|
117
|
+
},
|
|
118
|
+
select: (queryData) => {
|
|
119
|
+
const allData = queryData.pages.flatMap(
|
|
120
|
+
(page) => page.data
|
|
121
|
+
);
|
|
122
|
+
return {
|
|
123
|
+
...queryData,
|
|
124
|
+
data: allData,
|
|
125
|
+
pages: queryData.pages
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function usePyloById(entity, id, queryOptions) {
|
|
131
|
+
const transport = useTransport();
|
|
132
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
133
|
+
return useQuery({
|
|
134
|
+
queryKey: ["pylo", entity, "byId", id, { select: queryOptions == null ? void 0 : queryOptions.select, headers: merged }],
|
|
135
|
+
queryFn: async () => {
|
|
136
|
+
const { query, variables } = buildByIdQuery(
|
|
137
|
+
entity,
|
|
138
|
+
id,
|
|
139
|
+
queryOptions
|
|
140
|
+
);
|
|
141
|
+
const data = await transport(query, variables, merged);
|
|
142
|
+
const byIdKey = `${entity}ById`;
|
|
143
|
+
const result = data[byIdKey];
|
|
144
|
+
if (!result) return null;
|
|
145
|
+
return result.data;
|
|
146
|
+
},
|
|
147
|
+
enabled: !!id
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function usePyloMe(queryOptions) {
|
|
151
|
+
const transport = useTransport();
|
|
152
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
153
|
+
return useQuery({
|
|
154
|
+
queryKey: ["pylo", "me", { select: queryOptions == null ? void 0 : queryOptions.select, headers: merged }],
|
|
155
|
+
queryFn: async () => {
|
|
156
|
+
const { query, variables } = buildMeQuery(
|
|
157
|
+
queryOptions
|
|
158
|
+
);
|
|
159
|
+
const data = await transport(query, variables, merged);
|
|
160
|
+
return data["me"];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function usePyloAggregate(entity, queryOptions) {
|
|
165
|
+
const transport = useTransport();
|
|
166
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
167
|
+
return useQuery({
|
|
168
|
+
queryKey: [
|
|
169
|
+
"pylo",
|
|
170
|
+
entity,
|
|
171
|
+
"aggregate",
|
|
172
|
+
{
|
|
173
|
+
metrics: queryOptions == null ? void 0 : queryOptions.metrics,
|
|
174
|
+
groupBy: queryOptions == null ? void 0 : queryOptions.groupBy,
|
|
175
|
+
filter: queryOptions == null ? void 0 : queryOptions.filter,
|
|
176
|
+
limit: queryOptions == null ? void 0 : queryOptions.limit,
|
|
177
|
+
headers: merged
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
queryFn: async () => {
|
|
181
|
+
const { query, variables } = buildEntityAggregateQuery(
|
|
182
|
+
capitalize(entity),
|
|
183
|
+
queryOptions
|
|
184
|
+
);
|
|
185
|
+
const data = await transport(query, variables, merged);
|
|
186
|
+
const result = data["entityInstanceAggregate"];
|
|
187
|
+
return toAggregateResult(
|
|
188
|
+
result.rows,
|
|
189
|
+
result.total,
|
|
190
|
+
queryOptions
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function usePyloEventAggregate(queryOptions) {
|
|
196
|
+
const transport = useTransport();
|
|
197
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
198
|
+
return useQuery({
|
|
199
|
+
queryKey: [
|
|
200
|
+
"pylo",
|
|
201
|
+
"events",
|
|
202
|
+
"aggregate",
|
|
203
|
+
{
|
|
204
|
+
metrics: queryOptions == null ? void 0 : queryOptions.metrics,
|
|
205
|
+
groupBy: queryOptions == null ? void 0 : queryOptions.groupBy,
|
|
206
|
+
filter: queryOptions == null ? void 0 : queryOptions.filter,
|
|
207
|
+
limit: queryOptions == null ? void 0 : queryOptions.limit,
|
|
208
|
+
startTime: queryOptions == null ? void 0 : queryOptions.startTime,
|
|
209
|
+
headers: merged
|
|
210
|
+
}
|
|
211
|
+
],
|
|
212
|
+
queryFn: async () => {
|
|
213
|
+
const { query, variables } = buildEventAggregateQuery(
|
|
214
|
+
queryOptions
|
|
215
|
+
);
|
|
216
|
+
const data = await transport(query, variables, merged);
|
|
217
|
+
const result = data["pyloEventList"];
|
|
218
|
+
return toAggregateResult(
|
|
219
|
+
result.data,
|
|
220
|
+
result.aggregations,
|
|
221
|
+
queryOptions
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function usePyloUpsert(entity, mutationOptions) {
|
|
227
|
+
const transport = useTransport();
|
|
228
|
+
const queryClient = useQueryClient();
|
|
229
|
+
const merged = mergeHeaders(
|
|
230
|
+
mergeHeaders(globalHeaders, mutationOptions == null ? void 0 : mutationOptions.headers),
|
|
231
|
+
flagsToHeaders(mutationOptions != null ? mutationOptions : {})
|
|
232
|
+
);
|
|
233
|
+
const isDryRun = (mutationOptions == null ? void 0 : mutationOptions.dryRun) === true;
|
|
234
|
+
return useMutation({
|
|
235
|
+
...mutationOptions,
|
|
236
|
+
mutationFn: async (input) => {
|
|
237
|
+
const pascalName = capitalize(entity);
|
|
238
|
+
const { query, variables, field } = buildUpsertMutation(
|
|
239
|
+
entity,
|
|
240
|
+
pascalName,
|
|
241
|
+
input
|
|
242
|
+
);
|
|
243
|
+
const data = await transport(query, variables, merged);
|
|
244
|
+
return data[field].data;
|
|
245
|
+
},
|
|
246
|
+
onSuccess: (data, variables, onMutateResult, context) => {
|
|
247
|
+
var _a2;
|
|
248
|
+
if (!isDryRun) {
|
|
249
|
+
void queryClient.invalidateQueries({
|
|
250
|
+
queryKey: ["pylo", entity]
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
(_a2 = mutationOptions == null ? void 0 : mutationOptions.onSuccess) == null ? void 0 : _a2.call(mutationOptions, data, variables, onMutateResult, context);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function usePyloBulkUpsert(entity, mutationOptions) {
|
|
258
|
+
const transport = useTransport();
|
|
259
|
+
const queryClient = useQueryClient();
|
|
260
|
+
const merged = mergeHeaders(
|
|
261
|
+
mergeHeaders(globalHeaders, mutationOptions == null ? void 0 : mutationOptions.headers),
|
|
262
|
+
flagsToHeaders(mutationOptions != null ? mutationOptions : {})
|
|
263
|
+
);
|
|
264
|
+
const isDryRun = (mutationOptions == null ? void 0 : mutationOptions.dryRun) === true;
|
|
265
|
+
return useMutation({
|
|
266
|
+
...mutationOptions,
|
|
267
|
+
mutationFn: async (inputs) => {
|
|
268
|
+
const pascalName = capitalize(entity);
|
|
269
|
+
const { query, variables, field } = buildBulkUpsertMutation(
|
|
270
|
+
entity,
|
|
271
|
+
pascalName,
|
|
272
|
+
inputs
|
|
273
|
+
);
|
|
274
|
+
const data = await transport(query, variables, merged);
|
|
275
|
+
return data[field].data;
|
|
276
|
+
},
|
|
277
|
+
onSuccess: (data, variables, onMutateResult, context) => {
|
|
278
|
+
var _a2;
|
|
279
|
+
if (!isDryRun) {
|
|
280
|
+
void queryClient.invalidateQueries({
|
|
281
|
+
queryKey: ["pylo", entity]
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
(_a2 = mutationOptions == null ? void 0 : mutationOptions.onSuccess) == null ? void 0 : _a2.call(mutationOptions, data, variables, onMutateResult, context);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function usePyloDelete(entity, mutationOptions) {
|
|
289
|
+
const transport = useTransport();
|
|
290
|
+
const queryClient = useQueryClient();
|
|
291
|
+
const merged = mergeHeaders(
|
|
292
|
+
mergeHeaders(globalHeaders, mutationOptions == null ? void 0 : mutationOptions.headers),
|
|
293
|
+
flagsToHeaders(mutationOptions != null ? mutationOptions : {})
|
|
294
|
+
);
|
|
295
|
+
const isDryRun = (mutationOptions == null ? void 0 : mutationOptions.dryRun) === true;
|
|
296
|
+
return useMutation({
|
|
297
|
+
...mutationOptions,
|
|
298
|
+
mutationFn: async (ids) => {
|
|
299
|
+
const pascalName = capitalize(entity);
|
|
300
|
+
const { query, variables } = buildDeleteMutation(
|
|
301
|
+
entity,
|
|
302
|
+
pascalName,
|
|
303
|
+
ids
|
|
304
|
+
);
|
|
305
|
+
const data = await transport(query, variables, merged);
|
|
306
|
+
const mutationKey = `delete${pascalName}`;
|
|
307
|
+
return data[mutationKey].data;
|
|
308
|
+
},
|
|
309
|
+
onSuccess: (data, variables, onMutateResult, context) => {
|
|
310
|
+
var _a2;
|
|
311
|
+
if (!isDryRun) {
|
|
312
|
+
void queryClient.invalidateQueries({
|
|
313
|
+
queryKey: ["pylo", entity]
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
(_a2 = mutationOptions == null ? void 0 : mutationOptions.onSuccess) == null ? void 0 : _a2.call(mutationOptions, data, variables, onMutateResult, context);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function usePyloIngestEvents(mutationOptions) {
|
|
321
|
+
const transport = useTransport();
|
|
322
|
+
const merged = mergeHeaders(
|
|
323
|
+
mergeHeaders(globalHeaders, mutationOptions == null ? void 0 : mutationOptions.headers),
|
|
324
|
+
flagsToHeaders(mutationOptions != null ? mutationOptions : {})
|
|
325
|
+
);
|
|
326
|
+
return useMutation({
|
|
327
|
+
...mutationOptions,
|
|
328
|
+
mutationFn: async (events) => {
|
|
329
|
+
const { query, variables } = buildIngestEventsMutation(events);
|
|
330
|
+
const data = await transport(query, variables, merged);
|
|
331
|
+
return data["ingestPyloEventData"].data;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function usePyloEventList(queryOptions) {
|
|
336
|
+
const transport = useTransport();
|
|
337
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
338
|
+
return useQuery({
|
|
339
|
+
queryKey: ["pylo", "events", "list", { ...queryOptions, headers: merged }],
|
|
340
|
+
queryFn: async () => {
|
|
341
|
+
const { query, variables } = buildEventListQuery(queryOptions);
|
|
342
|
+
const data = await transport(query, variables, merged);
|
|
343
|
+
return data["pyloEventList"];
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function usePyloEventPropertyKeys(queryOptions) {
|
|
348
|
+
const transport = useTransport();
|
|
349
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
350
|
+
return useQuery({
|
|
351
|
+
queryKey: [
|
|
352
|
+
"pylo",
|
|
353
|
+
"events",
|
|
354
|
+
"propertyKeys",
|
|
355
|
+
{ filter: queryOptions == null ? void 0 : queryOptions.filter, headers: merged }
|
|
356
|
+
],
|
|
357
|
+
queryFn: async () => {
|
|
358
|
+
var _a2;
|
|
359
|
+
const { query, variables } = buildEventPropertyKeysQuery(queryOptions);
|
|
360
|
+
const data = await transport(query, variables, merged);
|
|
361
|
+
return (_a2 = data["pyloEventPropertyKeys"]) != null ? _a2 : [];
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function usePyloEventFieldValues(field, queryOptions) {
|
|
366
|
+
const transport = useTransport();
|
|
367
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
368
|
+
return useQuery({
|
|
369
|
+
queryKey: [
|
|
370
|
+
"pylo",
|
|
371
|
+
"events",
|
|
372
|
+
"fieldValues",
|
|
373
|
+
field,
|
|
374
|
+
{
|
|
375
|
+
startTime: queryOptions == null ? void 0 : queryOptions.startTime,
|
|
376
|
+
limit: queryOptions == null ? void 0 : queryOptions.limit,
|
|
377
|
+
headers: merged
|
|
378
|
+
}
|
|
379
|
+
],
|
|
380
|
+
queryFn: async () => {
|
|
381
|
+
var _a2;
|
|
382
|
+
const { query, variables } = buildEventFieldValuesQuery(field, queryOptions);
|
|
383
|
+
const data = await transport(query, variables, merged);
|
|
384
|
+
return (_a2 = data["pyloEventFieldValues"]) != null ? _a2 : [];
|
|
385
|
+
},
|
|
386
|
+
enabled: !!field
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
function usePyloUpload(hookOptions) {
|
|
390
|
+
const transport = useTransport();
|
|
391
|
+
const queryClient = useQueryClient();
|
|
392
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
393
|
+
const [progress, setProgress] = useState(0);
|
|
394
|
+
const [error, setError] = useState(null);
|
|
395
|
+
const [uploadedFiles, setUploadedFiles] = useState([]);
|
|
396
|
+
const abortRef = useRef(null);
|
|
397
|
+
const optionsRef = useRef(hookOptions);
|
|
398
|
+
optionsRef.current = hookOptions;
|
|
399
|
+
const startUpload = useCallback(
|
|
400
|
+
async (files, overrides) => {
|
|
401
|
+
var _a2, _b, _c, _d;
|
|
402
|
+
const opts = { ...optionsRef.current, ...overrides };
|
|
403
|
+
const merged = mergeHeaders(globalHeaders, opts.headers);
|
|
404
|
+
const list = Array.isArray(files) ? files : [files];
|
|
405
|
+
if (list.length === 0) return [];
|
|
406
|
+
const controller = new AbortController();
|
|
407
|
+
abortRef.current = controller;
|
|
408
|
+
setIsUploading(true);
|
|
409
|
+
setProgress(0);
|
|
410
|
+
setError(null);
|
|
411
|
+
setUploadedFiles([]);
|
|
412
|
+
let parts = [];
|
|
413
|
+
let grandTotal;
|
|
414
|
+
const loadedPerFile = new Array(list.length).fill(0);
|
|
415
|
+
const doneFlags = new Array(list.length).fill(false);
|
|
416
|
+
const reportProgress = () => {
|
|
417
|
+
var _a3, _b2;
|
|
418
|
+
const loaded = loadedPerFile.reduce((sum, bytes) => sum + bytes, 0);
|
|
419
|
+
const percent = grandTotal !== void 0 ? grandTotal > 0 ? Math.round(loaded / grandTotal * 100) : 0 : Math.round(
|
|
420
|
+
doneFlags.filter(Boolean).length / doneFlags.length * 100
|
|
421
|
+
);
|
|
422
|
+
setProgress(percent);
|
|
423
|
+
(_b2 = (_a3 = optionsRef.current) == null ? void 0 : _a3.onProgress) == null ? void 0 : _b2.call(_a3, {
|
|
424
|
+
loaded,
|
|
425
|
+
total: grandTotal != null ? grandTotal : loaded,
|
|
426
|
+
percent
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
try {
|
|
430
|
+
parts = list.map((source) => toUploadPart(source));
|
|
431
|
+
const sizes = parts.map((part) => part.size);
|
|
432
|
+
grandTotal = sizes.every((size) => size !== void 0) ? sizes.reduce((sum, size) => sum + (size != null ? size : 0), 0) : void 0;
|
|
433
|
+
const input = buildCreateUploadInput(opts, list.length);
|
|
434
|
+
const results = await Promise.all(
|
|
435
|
+
list.map(async (_source, index) => {
|
|
436
|
+
var _a3, _b2;
|
|
437
|
+
const part = parts[index];
|
|
438
|
+
const data = await transport(
|
|
439
|
+
CREATE_UPLOAD_MUTATION,
|
|
440
|
+
{ input },
|
|
441
|
+
merged
|
|
442
|
+
);
|
|
443
|
+
const uploadUrl = data.createUpload;
|
|
444
|
+
await uploadToUrl(uploadUrl.url, part.part, part.fileName, {
|
|
445
|
+
signal: controller.signal,
|
|
446
|
+
onProgress: (fileProgress) => {
|
|
447
|
+
loadedPerFile[index] = fileProgress.loaded;
|
|
448
|
+
reportProgress();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
loadedPerFile[index] = (_a3 = part.size) != null ? _a3 : loadedPerFile[index];
|
|
452
|
+
doneFlags[index] = true;
|
|
453
|
+
reportProgress();
|
|
454
|
+
return {
|
|
455
|
+
id: uploadUrl.id,
|
|
456
|
+
fileName: part.fileName,
|
|
457
|
+
mimeType: part.mimeType,
|
|
458
|
+
size: (_b2 = part.size) != null ? _b2 : 0
|
|
459
|
+
};
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
if (opts.attachTo) {
|
|
463
|
+
const { query, variables, entityKey } = buildAttachMutation(
|
|
464
|
+
opts.entityRelationPath,
|
|
465
|
+
results.map((result) => result.id),
|
|
466
|
+
opts.attachTo
|
|
467
|
+
);
|
|
468
|
+
await transport(query, variables, merged);
|
|
469
|
+
void queryClient.invalidateQueries({ queryKey: ["pylo", entityKey] });
|
|
470
|
+
}
|
|
471
|
+
void queryClient.invalidateQueries({ queryKey: ["pylo", "pyloMedia"] });
|
|
472
|
+
setUploadedFiles(results);
|
|
473
|
+
setProgress(100);
|
|
474
|
+
(_b = (_a2 = optionsRef.current) == null ? void 0 : _a2.onSuccess) == null ? void 0 : _b.call(_a2, results);
|
|
475
|
+
return results;
|
|
476
|
+
} catch (thrown) {
|
|
477
|
+
controller.abort();
|
|
478
|
+
const uploadError = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
479
|
+
setError(uploadError);
|
|
480
|
+
(_d = (_c = optionsRef.current) == null ? void 0 : _c.onError) == null ? void 0 : _d.call(_c, uploadError);
|
|
481
|
+
throw uploadError;
|
|
482
|
+
} finally {
|
|
483
|
+
setIsUploading(false);
|
|
484
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
[queryClient]
|
|
488
|
+
);
|
|
489
|
+
const abort = useCallback(() => {
|
|
490
|
+
var _a2;
|
|
491
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
492
|
+
}, []);
|
|
493
|
+
const reset = useCallback(() => {
|
|
494
|
+
setIsUploading(false);
|
|
495
|
+
setProgress(0);
|
|
496
|
+
setError(null);
|
|
497
|
+
setUploadedFiles([]);
|
|
498
|
+
}, []);
|
|
499
|
+
return { startUpload, isUploading, progress, error, uploadedFiles, abort, reset };
|
|
500
|
+
}
|
|
501
|
+
function usePyloFileUrl(id, queryOptions) {
|
|
502
|
+
var _a2;
|
|
503
|
+
const transport = useTransport();
|
|
504
|
+
const merged = mergeHeaders(globalHeaders, queryOptions == null ? void 0 : queryOptions.headers);
|
|
505
|
+
return useQuery({
|
|
506
|
+
queryKey: ["pylo", "files", "downloadUrl", id, { headers: merged }],
|
|
507
|
+
queryFn: async () => {
|
|
508
|
+
const data = await transport(
|
|
509
|
+
CREATE_DOWNLOAD_MUTATION,
|
|
510
|
+
{ id },
|
|
511
|
+
merged
|
|
512
|
+
);
|
|
513
|
+
return data.createDownload.url;
|
|
514
|
+
},
|
|
515
|
+
enabled: !!id,
|
|
516
|
+
staleTime: (_a2 = queryOptions == null ? void 0 : queryOptions.staleTimeMs) != null ? _a2 : 4 * 60 * 1e3
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
usePyloList,
|
|
521
|
+
usePyloInfiniteList,
|
|
522
|
+
usePyloById,
|
|
523
|
+
usePyloMe,
|
|
524
|
+
usePyloAggregate,
|
|
525
|
+
usePyloEventAggregate,
|
|
526
|
+
usePyloUpsert,
|
|
527
|
+
usePyloBulkUpsert,
|
|
528
|
+
usePyloDelete,
|
|
529
|
+
usePyloIngestEvents,
|
|
530
|
+
usePyloEventList,
|
|
531
|
+
usePyloEventPropertyKeys,
|
|
532
|
+
usePyloEventFieldValues,
|
|
533
|
+
usePyloUpload,
|
|
534
|
+
usePyloFileUrl
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export {
|
|
539
|
+
createPyloHooks
|
|
540
|
+
};
|