@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 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
+ };