@postrun/react 0.1.0 → 1.0.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/dist/index.cjs CHANGED
@@ -4,8 +4,10 @@
4
4
  var react = require('react');
5
5
  var reactQuery = require('@tanstack/react-query');
6
6
  var js = require('@postrun/js');
7
- var pRetry = require('p-retry');
7
+ var Nango = require('@nangohq/frontend');
8
8
  var pWaitFor = require('p-wait-for');
9
+ var pLimit = require('p-limit');
10
+ var pRetry = require('p-retry');
9
11
  var axios = require('axios');
10
12
  var reactTweet = require('react-tweet');
11
13
  var fi = require('react-icons/fi');
@@ -15,8 +17,10 @@ var lu = require('react-icons/lu');
15
17
 
16
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
19
 
18
- var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
20
+ var Nango__default = /*#__PURE__*/_interopDefault(Nango);
19
21
  var pWaitFor__default = /*#__PURE__*/_interopDefault(pWaitFor);
22
+ var pLimit__default = /*#__PURE__*/_interopDefault(pLimit);
23
+ var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
20
24
  var axios__default = /*#__PURE__*/_interopDefault(axios);
21
25
  var twitterText__default = /*#__PURE__*/_interopDefault(twitterText);
22
26
 
@@ -98,7 +102,8 @@ var profileKeys = {
98
102
  list: (query) => [...profileKeys.lists(), query ?? {}],
99
103
  // Nested under lists() so a create/update/delete invalidating lists() also
100
104
  // refreshes the infinite cache; distinct tail so the two cache shapes (a
101
- // single Page vs accumulated pages) never collide on one key.
105
+ // single Page vs accumulated pages) never collide on one key. The filter omits
106
+ // limit/offset — the infinite hook owns pagination, so they never key the cache.
102
107
  infinite: (query) => [...profileKeys.lists(), "infinite", query ?? {}],
103
108
  details: () => [...profileKeys.all, "detail"],
104
109
  detail: (id) => [...profileKeys.details(), id]
@@ -109,20 +114,28 @@ var postKeys = {
109
114
  list: (query) => [...postKeys.lists(), query ?? {}],
110
115
  // Nested under lists() so a create/update/delete invalidating lists() also
111
116
  // refreshes the infinite cache; distinct tail so the two cache shapes (a
112
- // single Page vs accumulated pages) never collide on one key.
117
+ // single Page vs accumulated pages) never collide on one key. The filter omits
118
+ // limit/offset — the infinite hook owns pagination, so they never key the cache.
113
119
  infinite: (query) => [...postKeys.lists(), "infinite", query ?? {}],
114
120
  details: () => [...postKeys.all, "detail"],
115
121
  detail: (id) => [...postKeys.details(), id]
116
122
  };
117
123
  var mediaKeys = {
118
124
  all: [ROOT, "media"],
125
+ lists: () => [...mediaKeys.all, "list"],
126
+ list: (query) => [...mediaKeys.lists(), query ?? {}],
127
+ // Nested under lists() so an upload/update/delete invalidating lists() also
128
+ // refreshes the infinite cache; distinct tail so the two cache shapes (a
129
+ // single Page vs accumulated pages) never collide on one key. The filter omits
130
+ // limit/offset — the infinite hook owns pagination, so they never key the cache.
131
+ infinite: (query) => [...mediaKeys.lists(), "infinite", query ?? {}],
119
132
  details: () => [...mediaKeys.all, "detail"],
120
133
  detail: (id) => [...mediaKeys.details(), id]
121
134
  };
122
135
  var connectionKeys = {
123
136
  all: [ROOT, "connections"],
124
137
  lists: () => [...connectionKeys.all, "list"],
125
- list: (profileId) => [...connectionKeys.lists(), profileId],
138
+ list: (profileId, filter) => [...connectionKeys.lists(), profileId, filter ?? {}],
126
139
  details: () => [...connectionKeys.all, "detail"],
127
140
  detail: (id) => [...connectionKeys.details(), id],
128
141
  accounts: (id) => [...connectionKeys.all, "accounts", id]
@@ -194,36 +207,256 @@ function useDeleteProfile() {
194
207
  queryClient
195
208
  );
196
209
  }
197
-
198
- // src/navigate.ts
199
- function navigate(url) {
200
- window.location.assign(url);
210
+ var PENDING = { status: "connected_pending" };
211
+ var CANCELLED = { status: "cancelled" };
212
+ var active = (connection) => ({
213
+ status: "active",
214
+ connection
215
+ });
216
+ var failed = (reason) => ({
217
+ status: "error",
218
+ reason
219
+ });
220
+ function outcomeForAuthError(error) {
221
+ switch (error.type) {
222
+ case "window_closed":
223
+ return CANCELLED;
224
+ case "blocked_by_browser":
225
+ return failed("popup_blocked");
226
+ case "missing_auth_token":
227
+ case "invalid_host_url":
228
+ case "missing_credentials":
229
+ case "connection_test_failed":
230
+ case "missing_connect_session_token":
231
+ case "connection_validation_failed":
232
+ case "resource_capped":
233
+ case "unknown_error":
234
+ return failed("auth_failed");
235
+ }
236
+ }
237
+ async function grant(authorize) {
238
+ try {
239
+ return { ok: true, connectionId: await authorize() };
240
+ } catch (error) {
241
+ if (error instanceof Nango.AuthError) {
242
+ return { ok: false, outcome: outcomeForAuthError(error) };
243
+ }
244
+ return { ok: false, outcome: failed("auth_failed") };
245
+ }
246
+ }
247
+ async function awaitGrantedConnection(deps, nangoConnectionId) {
248
+ try {
249
+ return await pWaitFor__default.default(
250
+ async () => {
251
+ const rows = await deps.listByNangoConnectionId(nangoConnectionId);
252
+ const match = rows[0];
253
+ return match ? pWaitFor__default.default.resolveWith(match) : false;
254
+ },
255
+ { interval: deps.pollIntervalMs, timeout: deps.pollTimeoutMs }
256
+ );
257
+ } catch (error) {
258
+ if (error instanceof pWaitFor.TimeoutError) {
259
+ return null;
260
+ }
261
+ throw error;
262
+ }
263
+ }
264
+ async function bindPendingConnection(deps, connection) {
265
+ let accounts;
266
+ try {
267
+ accounts = await deps.discoverAccounts(connection.id);
268
+ } catch (error) {
269
+ if (error instanceof js.PostrunError && error.code === "not_implemented") {
270
+ return PENDING;
271
+ }
272
+ throw error;
273
+ }
274
+ if (accounts.length === 0) {
275
+ return PENDING;
276
+ }
277
+ const chosen = await deps.chooseAccount(accounts);
278
+ try {
279
+ return active(await deps.selectAccount(connection.id, chosen));
280
+ } catch (error) {
281
+ if (error instanceof js.PostrunError) {
282
+ return error.code === "connection_reauth_required" ? failed("reauth_required") : failed("select_failed");
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+ async function runEmbeddedConnect(deps) {
288
+ const granted = await grant(deps.authorize);
289
+ if (!granted.ok) {
290
+ return granted.outcome;
291
+ }
292
+ let connection;
293
+ try {
294
+ connection = await awaitGrantedConnection(deps, granted.connectionId);
295
+ } catch {
296
+ return failed("connection_not_found");
297
+ }
298
+ if (connection === null) {
299
+ return PENDING;
300
+ }
301
+ if (connection.external_account_id !== null) {
302
+ return active(connection);
303
+ }
304
+ try {
305
+ return await bindPendingConnection(deps, connection);
306
+ } catch {
307
+ return failed("select_failed");
308
+ }
201
309
  }
202
310
 
203
311
  // src/connections.ts
204
- function useConnect() {
205
- const { client, queryClient } = usePostrun();
206
- return reactQuery.useMutation(
207
- {
208
- mutationFn: async ({ profileId, platform }) => {
209
- const session = (await js.connectionsConnect({
312
+ var POLL_INTERVAL_MS = 1500;
313
+ var POLL_TIMEOUT_MS = 15e3;
314
+ function useConnect({
315
+ profileId,
316
+ platform,
317
+ onConnected
318
+ }) {
319
+ const { client } = usePostrun();
320
+ const [state, setState] = react.useState({ phase: "preparing" });
321
+ const [remintNonce, setRemintNonce] = react.useState(0);
322
+ const sessionRef = react.useRef(null);
323
+ const pickRef = react.useRef(null);
324
+ const inFlightRef = react.useRef(false);
325
+ const flowGenRef = react.useRef(0);
326
+ const onConnectedRef = react.useRef(onConnected);
327
+ react.useEffect(() => {
328
+ onConnectedRef.current = onConnected;
329
+ }, [onConnected]);
330
+ const abandonFlow = react.useCallback(() => {
331
+ flowGenRef.current += 1;
332
+ inFlightRef.current = false;
333
+ const pick = pickRef.current;
334
+ pickRef.current = null;
335
+ pick?.reject(new Error("connect flow abandoned"));
336
+ }, []);
337
+ react.useEffect(() => {
338
+ let abandoned = false;
339
+ setState({ phase: "preparing" });
340
+ sessionRef.current = null;
341
+ js.connectionsConnect({ client, path: { id: profileId }, body: { platform } }).then(({ data }) => {
342
+ if (abandoned || !data) return;
343
+ sessionRef.current = {
344
+ token: data.connect_session_token,
345
+ providerConfigKey: data.provider_config_key,
346
+ host: data.nango_host
347
+ };
348
+ setState({ phase: "idle" });
349
+ }).catch(() => {
350
+ if (!abandoned) setState({ phase: "error", reason: "auth_failed" });
351
+ });
352
+ return () => {
353
+ abandoned = true;
354
+ abandonFlow();
355
+ };
356
+ }, [client, profileId, platform, remintNonce, abandonFlow]);
357
+ const start = react.useCallback(() => {
358
+ const session = sessionRef.current;
359
+ if (!session || inFlightRef.current) return;
360
+ inFlightRef.current = true;
361
+ const gen = flowGenRef.current;
362
+ const isCurrent = () => flowGenRef.current === gen;
363
+ setState({ phase: "connecting" });
364
+ void runEmbeddedConnect({
365
+ // Nango lives INSIDE `authorize` so a SYNCHRONOUS throw (invalid host /
366
+ // missing token — the Nango SDK throws `AuthError` synchronously) becomes a
367
+ // promise rejection that `grant()` maps to `auth_failed`, never an uncaught
368
+ // throw escaping the click and wedging `inFlightRef`. Gesture timing still
369
+ // holds: `authorize()` is invoked SYNCHRONOUSLY down the
370
+ // start → runEmbeddedConnect → grant chain (each `await`'s operand is
371
+ // evaluated before it suspends), so `nango.auth()`'s `window.open` fires
372
+ // inside the user gesture, with no `await` before it.
373
+ authorize: async () => {
374
+ const nango = new Nango__default.default({
375
+ host: session.host,
376
+ connectSessionToken: session.token
377
+ });
378
+ const result = await nango.auth(session.providerConfigKey, {
379
+ detectClosedAuthWindow: true
380
+ });
381
+ return result.connectionId;
382
+ },
383
+ chooseAccount: (accounts) => new Promise((resolve, reject) => {
384
+ if (!isCurrent()) {
385
+ reject(new Error("connect flow abandoned"));
386
+ return;
387
+ }
388
+ pickRef.current = { resolve, reject };
389
+ setState({ phase: "picking", accounts });
390
+ }),
391
+ listByNangoConnectionId: async (nangoConnectionId) => {
392
+ const { data } = await js.connectionsListByProfile({
210
393
  client,
211
394
  path: { id: profileId },
212
- body: { platform }
213
- })).data;
214
- navigate(session.connect_url);
215
- return session;
395
+ query: { nango_connection_id: nangoConnectionId }
396
+ });
397
+ return data?.data ?? [];
398
+ },
399
+ discoverAccounts: async (connectionId) => {
400
+ const { data } = await js.connectionsListAccounts({
401
+ client,
402
+ path: { id: connectionId }
403
+ });
404
+ return data?.data ?? [];
405
+ },
406
+ selectAccount: async (connectionId, externalAccountId) => {
407
+ const { data } = await js.connectionsSelect({
408
+ client,
409
+ path: { id: connectionId },
410
+ body: { external_account_id: externalAccountId }
411
+ });
412
+ if (!data) throw new Error("select returned no connection");
413
+ return data;
414
+ },
415
+ pollIntervalMs: POLL_INTERVAL_MS,
416
+ pollTimeoutMs: POLL_TIMEOUT_MS
417
+ }).then((outcome) => {
418
+ if (!isCurrent()) return;
419
+ inFlightRef.current = false;
420
+ pickRef.current = null;
421
+ switch (outcome.status) {
422
+ case "active":
423
+ setState({ phase: "active", connection: outcome.connection });
424
+ onConnectedRef.current?.(outcome.connection);
425
+ return;
426
+ case "connected_pending":
427
+ setState({ phase: "connected_pending" });
428
+ return;
429
+ case "cancelled":
430
+ setState({ phase: "cancelled" });
431
+ return;
432
+ case "error":
433
+ setState({ phase: "error", reason: outcome.reason });
434
+ return;
216
435
  }
217
- },
218
- queryClient
219
- );
436
+ });
437
+ }, [client, profileId]);
438
+ const select = react.useCallback((externalAccountId) => {
439
+ const pick = pickRef.current;
440
+ if (!pick) return;
441
+ pickRef.current = null;
442
+ setState({ phase: "connecting" });
443
+ pick.resolve(externalAccountId);
444
+ }, []);
445
+ const reset = react.useCallback(() => {
446
+ setRemintNonce((n) => n + 1);
447
+ }, []);
448
+ return { state, start, select, reset };
220
449
  }
221
- function useConnections(profileId) {
450
+ function useConnections(profileId, filter) {
222
451
  const { client, queryClient } = usePostrun();
223
452
  return reactQuery.useQuery(
224
453
  {
225
- queryKey: connectionKeys.list(profileId),
226
- queryFn: async () => (await js.connectionsListByProfile({ client, path: { id: profileId } })).data,
454
+ queryKey: connectionKeys.list(profileId, filter),
455
+ queryFn: async () => (await js.connectionsListByProfile({
456
+ client,
457
+ path: { id: profileId },
458
+ query: filter
459
+ })).data,
227
460
  enabled: Boolean(profileId)
228
461
  },
229
462
  queryClient
@@ -278,6 +511,17 @@ function useDisconnect() {
278
511
  queryClient
279
512
  );
280
513
  }
514
+
515
+ // src/Connect.tsx
516
+ function Connect({
517
+ profileId,
518
+ platform,
519
+ onConnected,
520
+ children
521
+ }) {
522
+ const api = useConnect({ profileId, platform, onConnected });
523
+ return children(api);
524
+ }
281
525
  var UploadError = class extends Error {
282
526
  status;
283
527
  constructor(status, message) {
@@ -318,17 +562,7 @@ async function uploadBytes(target, file, options = {}) {
318
562
  }
319
563
 
320
564
  // src/media.ts
321
- var DOCUMENT_MIME = /^application\/(pdf|msword|vnd\.(openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation)|ms-powerpoint))$/;
322
- function inferKind(contentType) {
323
- if (contentType === "image/gif") return "gif";
324
- if (contentType.startsWith("image/")) return "image";
325
- if (contentType.startsWith("video/")) return "video";
326
- if (DOCUMENT_MIME.test(contentType)) return "document";
327
- throw new Error(
328
- `Could not infer media kind from "${contentType}". Pass { kind } explicitly.`
329
- );
330
- }
331
- async function pollUntilSettled(client, id, signal) {
565
+ async function pollUntilSettled(client, id, signal, onTick) {
332
566
  let latest;
333
567
  await pWaitFor__default.default(
334
568
  async () => {
@@ -336,6 +570,7 @@ async function pollUntilSettled(client, id, signal) {
336
570
  throw new DOMException("Upload aborted", "AbortError");
337
571
  }
338
572
  latest = (await js.mediaGet({ client, path: { id } })).data;
573
+ onTick?.(latest);
339
574
  return latest.status === "ready" || latest.status === "failed";
340
575
  },
341
576
  { interval: 1500, timeout: 3e5 }
@@ -345,92 +580,134 @@ async function pollUntilSettled(client, id, signal) {
345
580
  }
346
581
  return latest;
347
582
  }
348
- function useMediaUpload() {
349
- const { client, queryClient } = usePostrun();
350
- const [status, setStatus] = react.useState("idle");
351
- const [progress, setProgress] = react.useState(0);
352
- const [media, setMedia] = react.useState(null);
353
- const [error, setError] = react.useState(null);
354
- const abortRef = react.useRef(null);
355
- const upload = react.useCallback(
356
- async (file, options) => {
357
- const contentType = options.contentType || file.type;
358
- if (!contentType) {
359
- throw new Error(
360
- "Could not determine the file's content type. Pass { contentType } explicitly."
361
- );
362
- }
363
- const kind = options.kind ?? inferKind(contentType);
364
- abortRef.current?.abort();
365
- const controller = new AbortController();
366
- abortRef.current = controller;
367
- setStatus("uploading");
368
- setProgress(0);
369
- setMedia(null);
370
- setError(null);
371
- try {
372
- const created = (await js.mediaCreate({
373
- client,
374
- body: {
375
- profile_id: options.profileId,
376
- kind,
377
- content_type: contentType,
378
- targets: options.targets,
379
- raw: options.raw,
380
- alt_text: options.altText,
381
- external_id: options.externalId,
382
- metadata: options.metadata
583
+ async function runUpload(client, file, options, signal, callbacks) {
584
+ const created = (await js.mediaCreate({
585
+ client,
586
+ body: {
587
+ profile_id: options.profileId,
588
+ kind: options.kind,
589
+ content_type: options.contentType,
590
+ targets: options.targets,
591
+ raw: options.raw,
592
+ alt_text: options.altText,
593
+ external_id: options.externalId,
594
+ metadata: options.metadata
595
+ }
596
+ })).data;
597
+ if (created.upload) {
598
+ const target = created.upload;
599
+ await pRetry__default.default(
600
+ async () => {
601
+ try {
602
+ await uploadBytes(target, file, {
603
+ onProgress: callbacks.onProgress,
604
+ signal
605
+ });
606
+ } catch (uploadError) {
607
+ if (uploadError instanceof UploadError && uploadError.status >= 400 && uploadError.status < 500) {
608
+ throw new pRetry.AbortError(uploadError);
383
609
  }
384
- })).data;
385
- if (created.upload) {
386
- const target = created.upload;
387
- await pRetry__default.default(
388
- async () => {
389
- try {
390
- await uploadBytes(target, file, {
391
- onProgress: setProgress,
392
- signal: controller.signal
393
- });
394
- } catch (uploadError) {
395
- if (uploadError instanceof UploadError && uploadError.status >= 400 && uploadError.status < 500) {
396
- throw new pRetry.AbortError(uploadError);
397
- }
398
- throw uploadError;
399
- }
400
- },
401
- { retries: 3, signal: controller.signal }
402
- );
403
- }
404
- setStatus("processing");
405
- const settled = await pollUntilSettled(
406
- client,
407
- created.id,
408
- controller.signal
409
- );
410
- queryClient.setQueryData(mediaKeys.detail(created.id), settled);
411
- setMedia(settled);
412
- setStatus(settled.status === "failed" ? "failed" : "ready");
413
- return settled;
414
- } catch (caught) {
415
- setError(caught);
416
- setStatus("failed");
417
- throw caught;
418
- } finally {
419
- if (abortRef.current === controller) {
420
- abortRef.current = null;
610
+ throw uploadError;
421
611
  }
612
+ },
613
+ { retries: 3, signal }
614
+ );
615
+ }
616
+ callbacks.onProcessing();
617
+ return pollUntilSettled(client, created.id, signal, callbacks.onPoll);
618
+ }
619
+ function toFileArray(files) {
620
+ if (files instanceof File) return [files];
621
+ return Array.from(files);
622
+ }
623
+ function useMediaUpload(options) {
624
+ const { client, queryClient } = usePostrun();
625
+ const [items, setItems] = react.useState([]);
626
+ const controllers = react.useRef(/* @__PURE__ */ new Map());
627
+ const limitRef = react.useRef(null);
628
+ if (!limitRef.current) {
629
+ limitRef.current = pLimit__default.default(options?.concurrency ?? 3);
630
+ }
631
+ const patch = react.useCallback(
632
+ (id, changes) => {
633
+ setItems(
634
+ (current) => current.map(
635
+ (item) => item.id === id ? { ...item, ...changes } : item
636
+ )
637
+ );
638
+ },
639
+ []
640
+ );
641
+ const add = react.useCallback(
642
+ (files, uploadOptions) => {
643
+ const queued = toFileArray(files).map((file) => ({
644
+ id: crypto.randomUUID(),
645
+ file,
646
+ status: "uploading",
647
+ progress: 0,
648
+ media: null,
649
+ error: null
650
+ }));
651
+ setItems((current) => [...current, ...queued]);
652
+ const limit = limitRef.current;
653
+ if (!limit) {
654
+ return Promise.resolve([]);
422
655
  }
656
+ const settlements = queued.map((item) => {
657
+ const controller = new AbortController();
658
+ controllers.current.set(item.id, controller);
659
+ return limit(() => {
660
+ if (controller.signal.aborted) {
661
+ throw new DOMException("Upload aborted", "AbortError");
662
+ }
663
+ return runUpload(client, item.file, uploadOptions, controller.signal, {
664
+ onProgress: (progress) => patch(item.id, { progress }),
665
+ onProcessing: () => patch(item.id, { status: "processing" }),
666
+ // Live server progress (stage + percent) each poll tick.
667
+ onPoll: (media) => patch(item.id, { media })
668
+ });
669
+ }).then((settled) => {
670
+ patch(item.id, {
671
+ status: settled.status === "failed" ? "failed" : "ready",
672
+ media: settled,
673
+ progress: 1
674
+ });
675
+ queryClient.setQueryData(mediaKeys.detail(settled.id), settled);
676
+ void queryClient.invalidateQueries({ queryKey: mediaKeys.lists() });
677
+ return settled;
678
+ }).catch((error) => {
679
+ if (controller.signal.aborted) {
680
+ return null;
681
+ }
682
+ patch(item.id, { status: "failed", error });
683
+ return null;
684
+ }).finally(() => {
685
+ controllers.current.delete(item.id);
686
+ });
687
+ });
688
+ return Promise.all(settlements).then(
689
+ (results) => results.filter((result) => result !== null)
690
+ );
423
691
  },
424
- [client, queryClient]
692
+ [client, queryClient, patch]
425
693
  );
426
- const cancel = react.useCallback(() => abortRef.current?.abort(), []);
694
+ const remove = react.useCallback((id) => {
695
+ controllers.current.get(id)?.abort();
696
+ controllers.current.delete(id);
697
+ setItems((current) => current.filter((item) => item.id !== id));
698
+ }, []);
427
699
  const reset = react.useCallback(() => {
428
- setStatus("idle");
429
- setProgress(0);
430
- setMedia(null);
431
- setError(null);
700
+ controllers.current.forEach((controller) => controller.abort());
701
+ controllers.current.clear();
702
+ setItems([]);
432
703
  }, []);
433
- return { upload, cancel, reset, status, progress, media, error };
704
+ const ready = items.flatMap(
705
+ (item) => item.status === "ready" && item.media ? [item.media] : []
706
+ );
707
+ const isUploading = items.some(
708
+ (item) => item.status === "uploading" || item.status === "processing"
709
+ );
710
+ return { items, ready, isUploading, add, remove, reset };
434
711
  }
435
712
  function useMedia(id) {
436
713
  const { client, queryClient } = usePostrun();
@@ -447,12 +724,33 @@ function useMedia(id) {
447
724
  queryClient
448
725
  );
449
726
  }
727
+ function useMediaList(query) {
728
+ const { client, queryClient } = usePostrun();
729
+ return reactQuery.useQuery(
730
+ {
731
+ queryKey: mediaKeys.list(query),
732
+ queryFn: async () => (await js.mediaList({ client, query })).data
733
+ },
734
+ queryClient
735
+ );
736
+ }
737
+ function useMediaInfinite(filters, options) {
738
+ const { client } = usePostrun();
739
+ return useInfiniteList({
740
+ queryKey: mediaKeys.infinite(filters),
741
+ limit: options?.pageSize,
742
+ fetchPage: async ({ limit, offset }) => (await js.mediaList({ client, query: { ...filters, limit, offset } })).data
743
+ });
744
+ }
450
745
  function useUpdateMedia() {
451
746
  const { client, queryClient } = usePostrun();
452
747
  return reactQuery.useMutation(
453
748
  {
454
749
  mutationFn: async ({ id, ...body }) => (await js.mediaUpdate({ client, path: { id }, body })).data,
455
- onSuccess: (result, { id }) => queryClient.setQueryData(mediaKeys.detail(id), result)
750
+ onSuccess: (result, { id }) => {
751
+ queryClient.setQueryData(mediaKeys.detail(id), result);
752
+ void queryClient.invalidateQueries({ queryKey: mediaKeys.lists() });
753
+ }
456
754
  },
457
755
  queryClient
458
756
  );
@@ -462,7 +760,10 @@ function useDeleteMedia() {
462
760
  return reactQuery.useMutation(
463
761
  {
464
762
  mutationFn: async (id) => (await js.mediaDelete({ client, path: { id } })).data,
465
- onSuccess: (_result, id) => queryClient.removeQueries({ queryKey: mediaKeys.detail(id) })
763
+ onSuccess: (_result, id) => {
764
+ queryClient.removeQueries({ queryKey: mediaKeys.detail(id) });
765
+ void queryClient.invalidateQueries({ queryKey: mediaKeys.lists() });
766
+ }
466
767
  },
467
768
  queryClient
468
769
  );
@@ -1288,6 +1589,7 @@ function LinkedInPostPreviewImpl({
1288
1589
  }
1289
1590
  var LinkedInPostPreview = react.memo(LinkedInPostPreviewImpl);
1290
1591
 
1592
+ exports.Connect = Connect;
1291
1593
  exports.LinkedInPostPreview = LinkedInPostPreview;
1292
1594
  exports.PostrunProvider = PostrunProvider;
1293
1595
  exports.UploadError = UploadError;
@@ -1309,6 +1611,8 @@ exports.useDisconnect = useDisconnect;
1309
1611
  exports.useDiscoverableAccounts = useDiscoverableAccounts;
1310
1612
  exports.useInfiniteList = useInfiniteList;
1311
1613
  exports.useMedia = useMedia;
1614
+ exports.useMediaInfinite = useMediaInfinite;
1615
+ exports.useMediaList = useMediaList;
1312
1616
  exports.useMediaUpload = useMediaUpload;
1313
1617
  exports.usePost = usePost;
1314
1618
  exports.usePostrun = usePostrun;