@domino-sdk/relay 1.11.1 → 1.14.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.
Files changed (50) hide show
  1. package/README.md +1 -0
  2. package/build.mjs +15 -0
  3. package/dist/accounts-5ciAkCMP.d.ts +147 -0
  4. package/dist/{activity-CNiPDPK6.d.ts → activity-D_R3J-gU.d.ts} +115 -115
  5. package/dist/attempt-captures-DZldPZJc.d.ts +285 -0
  6. package/dist/authoring.d.ts +49 -47
  7. package/dist/authoring.js +6 -4
  8. package/dist/{automatic-outcomes-kPIDv6X_.d.ts → automatic-outcomes-6lJGnSDe.d.ts} +1 -1
  9. package/dist/browser.d.ts +8 -7
  10. package/dist/browser.js +7 -6
  11. package/dist/capability-error-oTd3eYFH.d.ts +151 -0
  12. package/dist/chunk-2UAVZCMH.js +314 -0
  13. package/dist/chunk-5TS3OAWS.js +470 -0
  14. package/dist/{chunk-PUDL4JBY.js → chunk-6DD7DVNQ.js} +2 -2
  15. package/dist/{chunk-AGFUDCNM.js → chunk-C3CB4WIQ.js} +1 -1
  16. package/dist/{chunk-T657WO56.js → chunk-LLMJ2SUT.js} +6 -2
  17. package/dist/chunk-NIYCAW2I.js +79 -0
  18. package/dist/chunk-UJACD6K2.js +987 -0
  19. package/dist/{chunk-XK5E4HAS.js → chunk-VBHFJH3P.js} +80 -28
  20. package/dist/chunk-W335CWYJ.js +53 -0
  21. package/dist/{chunk-X7ECZ3AK.js → chunk-XMBM2XD5.js} +1 -1
  22. package/dist/{chunk-2K2QBPAD.js → chunk-ZLYBUM7K.js} +6 -4
  23. package/dist/{client-fUSERMAC.d.ts → client-BTd_CyX3.d.ts} +26 -6
  24. package/dist/{groups-BK8ts2yK.d.ts → groups-4Jb3uYFO.d.ts} +1 -1
  25. package/dist/{images-aw6W7vFZ.d.ts → images-D5kpqHxv.d.ts} +1 -1
  26. package/dist/index.d.ts +9 -8
  27. package/dist/index.js +8 -6
  28. package/dist/{capability-error-ty3SqD_b.d.ts → integrations-Cr9EhGI_.d.ts} +1 -149
  29. package/dist/internal.d.ts +32 -5
  30. package/dist/internal.js +8 -3
  31. package/dist/{offers-C2FANzrG.d.ts → offers-BFI5t-e-.d.ts} +2 -2
  32. package/dist/{pages-Chb6OZSB.d.ts → pages-nIoMT9J3.d.ts} +1 -1
  33. package/dist/{participant-activity-DkL3nY5a.d.ts → participant-activity-DYdUUhw2.d.ts} +1 -0
  34. package/dist/portal-proxy.js +1 -1
  35. package/dist/react/index.d.ts +246 -177
  36. package/dist/react/index.js +954 -383
  37. package/dist/{referral-reward-kinds-M3f0PdiR.d.ts → referral-reward-kinds-DnvQXL8L.d.ts} +100 -3
  38. package/dist/{schema-DEM8y8Fm.d.ts → schema-K-Pg5lPz.d.ts} +1 -1
  39. package/dist/schema.d.ts +316 -314
  40. package/dist/schema.js +16 -14
  41. package/dist/server/index.d.ts +2546 -377
  42. package/dist/server/index.js +594 -841
  43. package/dist/{sign-in-options-iWOp9puZ.d.ts → sign-in-options-DBjMzgnu.d.ts} +2 -2
  44. package/dist/submission-input-CPIsJt-_.d.ts +146 -0
  45. package/dist/testing.d.ts +641 -0
  46. package/dist/testing.js +1282 -0
  47. package/package.json +6 -2
  48. package/dist/chunk-OCXGG75K.js +0 -171
  49. package/dist/chunk-ZFABFKIY.js +0 -1295
  50. package/dist/submission-input-BbBJTVlT.d.ts +0 -290
@@ -8,23 +8,25 @@ import {
8
8
  discoverSolanaWallets,
9
9
  readSignInMethods,
10
10
  signInOptionsFrom
11
- } from "../chunk-PUDL4JBY.js";
12
- import "../chunk-XK5E4HAS.js";
11
+ } from "../chunk-6DD7DVNQ.js";
12
+ import "../chunk-VBHFJH3P.js";
13
13
  import {
14
14
  createRelayAction
15
- } from "../chunk-X7ECZ3AK.js";
16
- import "../chunk-T657WO56.js";
15
+ } from "../chunk-XMBM2XD5.js";
16
+ import "../chunk-LLMJ2SUT.js";
17
+ import "../chunk-UJACD6K2.js";
17
18
  import {
18
19
  RelayError,
19
20
  RelayErrorCode,
20
21
  asRelayError,
22
+ clientError,
21
23
  isRelayError
22
- } from "../chunk-ZFABFKIY.js";
24
+ } from "../chunk-2UAVZCMH.js";
23
25
  import "../chunk-6QJ3LADK.js";
24
26
  import {
25
27
  isRequestError
26
28
  } from "../chunk-NPGENI7K.js";
27
- import "../chunk-AGFUDCNM.js";
29
+ import "../chunk-C3CB4WIQ.js";
28
30
  import {
29
31
  sessionMember
30
32
  } from "../chunk-PT5STHFH.js";
@@ -70,35 +72,41 @@ var QueryCache = class {
70
72
  get disposed() {
71
73
  return this.#disposed;
72
74
  }
73
- snapshot(key) {
74
- return this.#entries.get(hashKey(key))?.state ?? LOADING;
75
+ snapshot(key, fallback = LOADING) {
76
+ return this.#entries.get(hashKey(key))?.state ?? fallback;
75
77
  }
76
- /** Starts observing `key`, reading it now unless a read is cached or in flight. */
77
- subscribe(key, read, options, listener) {
78
- if (this.#disposed) {
79
- return () => {
80
- };
81
- }
78
+ /** The entry for `key`, created with `seed` as its value when there is none yet. */
79
+ #entry(key, read, seed) {
82
80
  const hash = hashKey(key);
83
81
  let entry = this.#entries.get(hash);
84
82
  if (!entry) {
85
83
  entry = {
86
84
  key,
87
85
  read,
88
- state: LOADING,
86
+ state: seed ? { status: "ready", data: seed.data, error: null } : LOADING,
89
87
  listeners: /* @__PURE__ */ new Set(),
90
88
  intervals: /* @__PURE__ */ new Map(),
91
89
  focus: /* @__PURE__ */ new Set(),
90
+ stale: /* @__PURE__ */ new Map(),
92
91
  controller: null,
93
92
  pending: null,
94
- settledAt: 0,
93
+ settledAt: seed?.at ?? 0,
95
94
  version: 0,
96
95
  timer: void 0,
97
96
  unused: void 0
98
97
  };
99
98
  this.#entries.set(hash, entry);
100
99
  }
101
- const current = entry;
100
+ return entry;
101
+ }
102
+ /** Starts observing `key`, reading it now unless a read is cached or in flight. */
103
+ subscribe(key, read, options, listener, seed) {
104
+ if (this.#disposed) {
105
+ return () => {
106
+ };
107
+ }
108
+ const hash = hashKey(key);
109
+ const current = this.#entry(key, read, seed);
102
110
  const token = {};
103
111
  const unseen = current.listeners.size === 0;
104
112
  current.read = read;
@@ -110,7 +118,11 @@ var QueryCache = class {
110
118
  if (options.refetchOnFocus) {
111
119
  current.focus.add(token);
112
120
  }
113
- if (!current.pending && (current.settledAt === 0 || unseen && Date.now() - current.settledAt >= STALE_MS)) {
121
+ if (options.staleMs !== void 0 && options.staleMs >= 0) {
122
+ current.stale.set(token, options.staleMs);
123
+ }
124
+ const staleMs = current.stale.size > 0 ? Math.min(...current.stale.values()) : STALE_MS;
125
+ if (!current.pending && (current.settledAt === 0 || unseen && Date.now() - current.settledAt >= staleMs)) {
114
126
  void this.#fetch(current, false);
115
127
  } else {
116
128
  this.#schedule(current);
@@ -119,6 +131,7 @@ var QueryCache = class {
119
131
  current.listeners.delete(listener);
120
132
  current.intervals.delete(token);
121
133
  current.focus.delete(token);
134
+ current.stale.delete(token);
122
135
  if (current.listeners.size === 0 && !this.#disposed) {
123
136
  clearTimeout(current.timer);
124
137
  current.unused = setTimeout(() => {
@@ -129,6 +142,29 @@ var QueryCache = class {
129
142
  }
130
143
  };
131
144
  }
145
+ /**
146
+ * Reads `key` unless it has a value or a read in flight, for a component that suspends until it
147
+ * has one: settles when the read does. A value nobody comes to show is forgotten like any other.
148
+ */
149
+ ensure(key, read, seed) {
150
+ if (this.#disposed) {
151
+ return Promise.resolve();
152
+ }
153
+ const hash = hashKey(key);
154
+ const entry = this.#entry(key, read, seed);
155
+ if (entry.listeners.size === 0) {
156
+ clearTimeout(entry.unused);
157
+ entry.unused = setTimeout(() => {
158
+ if (entry.listeners.size === 0) {
159
+ this.#drop(hash, entry);
160
+ }
161
+ }, UNUSED_MS);
162
+ }
163
+ if (entry.pending) {
164
+ return entry.pending;
165
+ }
166
+ return entry.settledAt === 0 ? this.#fetch(entry, false) : Promise.resolve();
167
+ }
132
168
  /** Reads `key` again now, replacing any read in flight. */
133
169
  refresh(key) {
134
170
  const entry = this.#entries.get(hashKey(key));
@@ -173,12 +209,13 @@ var QueryCache = class {
173
209
  }
174
210
  /**
175
211
  * Replaces the cached value of `key` with one a change returned, cancelling a read in flight that
176
- * may have started before the change. Does nothing for a key nobody has read.
212
+ * may have started before the change. Does nothing for a key nobody has read. Returns whether it
213
+ * applied.
177
214
  */
178
215
  set(key, data) {
179
216
  const entry = this.#entries.get(hashKey(key));
180
217
  if (this.#disposed || !entry) {
181
- return;
218
+ return false;
182
219
  }
183
220
  entry.controller?.abort();
184
221
  entry.controller = null;
@@ -187,6 +224,7 @@ var QueryCache = class {
187
224
  entry.settledAt = Date.now();
188
225
  this.#write(entry, { status: "ready", data, error: null });
189
226
  this.#schedule(entry);
227
+ return true;
190
228
  }
191
229
  version(key) {
192
230
  return this.#entries.get(hashKey(key))?.version ?? -1;
@@ -394,13 +432,26 @@ function useActivity(options = {}) {
394
432
 
395
433
  // src/react/action.ts
396
434
  import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
435
+ var IDLE = Object.freeze({
436
+ status: "idle",
437
+ data: void 0,
438
+ error: null
439
+ });
440
+ function actionError(cause) {
441
+ return isRequestError(cause) && !isRelayError(cause) ? new RelayError(cause.message, {
442
+ kind: cause.kind,
443
+ status: cause.status,
444
+ code: cause.code,
445
+ uncertain: cause.uncertain,
446
+ operationId: cause.operationId,
447
+ replaySafe: cause.recovery === "replay",
448
+ retryAfter: cause.retryAfter,
449
+ cause
450
+ }) : asRelayError(cause);
451
+ }
397
452
  function useRelayAction(scope) {
398
453
  const owner = useRef2({ scope, action: createRelayAction() });
399
- const [state, setState] = useState2({
400
- owner: owner.current,
401
- busy: false,
402
- error: null
403
- });
454
+ const [state, setState] = useState2({ owner: owner.current, ...IDLE });
404
455
  if (owner.current.scope !== scope) {
405
456
  owner.current = { scope, action: createRelayAction() };
406
457
  }
@@ -411,33 +462,29 @@ function useRelayAction(scope) {
411
462
  return { ok: false };
412
463
  }
413
464
  const result = task ? current2.action.run(task) : current2.action.retry();
414
- setState({ owner: current2, busy: true, error: null });
465
+ setState({
466
+ owner: current2,
467
+ status: "pending",
468
+ data: void 0,
469
+ error: null
470
+ });
415
471
  try {
416
472
  const value = await result;
417
473
  if (owner.current === current2) {
418
- setState({ owner: current2, busy: false, error: null });
474
+ setState({
475
+ owner: current2,
476
+ status: "success",
477
+ data: value,
478
+ error: null
479
+ });
419
480
  }
420
481
  return { ok: true, value };
421
482
  } catch (caught) {
483
+ const error = actionError(current2.action.error ?? caught);
422
484
  if (owner.current === current2) {
423
- const cause = current2.action.error ?? caught;
424
- const error = isRequestError(cause) && !isRelayError(cause) ? new RelayError(cause.message, {
425
- kind: cause.kind,
426
- status: cause.status,
427
- code: cause.code,
428
- uncertain: cause.uncertain,
429
- operationId: cause.operationId,
430
- replaySafe: cause.recovery === "replay",
431
- retryAfter: cause.retryAfter,
432
- cause
433
- }) : asRelayError(cause);
434
- setState({
435
- owner: current2,
436
- busy: false,
437
- error
438
- });
485
+ setState({ owner: current2, status: "error", data: void 0, error });
439
486
  }
440
- return { ok: false };
487
+ return { ok: false, error };
441
488
  }
442
489
  },
443
490
  []
@@ -448,31 +495,232 @@ function useRelayAction(scope) {
448
495
  if (!current2.action.settle()) {
449
496
  return false;
450
497
  }
451
- setState({ owner: current2, busy: false, error: null });
498
+ setState({ owner: current2, ...IDLE });
452
499
  return true;
453
500
  }, []);
454
- const current = state.owner === owner.current ? state : { busy: false, error: null };
501
+ const reset = useCallback2(() => {
502
+ const current2 = owner.current;
503
+ if (!current2.action.busy && current2.action.actionId === void 0) {
504
+ setState({ owner: current2, ...IDLE });
505
+ }
506
+ }, []);
507
+ const current = state.owner === owner.current ? state : IDLE;
508
+ const isPending = current.status === "pending";
455
509
  return {
456
510
  run: execute,
457
511
  retry,
458
512
  settle,
459
- busy: current.busy,
460
- error: current.error
513
+ reset,
514
+ status: current.status,
515
+ isPending,
516
+ data: current.data,
517
+ error: current.error,
518
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
519
+ busy: isPending
461
520
  };
462
521
  }
463
522
 
464
- // src/react/provider.tsx
465
- import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
523
+ // src/react/mutation.ts
524
+ import { useCallback as useCallback4, useLayoutEffect as useLayoutEffect3, useMemo as useMemo2, useRef as useRef4 } from "react";
466
525
 
467
- // src/react/experience.tsx
526
+ // src/react/query.ts
468
527
  import {
469
- createContext as createContext2,
470
528
  useCallback as useCallback3,
471
- useContext as useContext2,
529
+ useEffect as useEffect2,
530
+ useLayoutEffect as useLayoutEffect2,
472
531
  useMemo,
532
+ useRef as useRef3,
533
+ useState as useState3,
473
534
  useSyncExternalStore as useSyncExternalStore2
474
535
  } from "react";
475
536
 
537
+ // src/react/feed.ts
538
+ function feedOwner(session) {
539
+ return JSON.stringify([
540
+ session.organization,
541
+ session.project,
542
+ session.environment,
543
+ sessionMember(session)
544
+ ]);
545
+ }
546
+ var ActivityFeed = class {
547
+ /** The provider scope the feed was started for. */
548
+ scope;
549
+ owner;
550
+ #client;
551
+ #intervalMs;
552
+ #on;
553
+ #observer = null;
554
+ #generation = {};
555
+ #session;
556
+ #cursor;
557
+ /** The feed knows where it starts: events from now on are new. */
558
+ #ready = false;
559
+ /** A read answered since the feed (re)started. */
560
+ #answered = false;
561
+ #disposed = false;
562
+ /** When a read last answered, for a focus that should not read again so soon. */
563
+ settledAt = 0;
564
+ constructor(client, session, options, on) {
565
+ this.scope = options.scope;
566
+ this.owner = feedOwner(session);
567
+ this.#client = client;
568
+ this.#session = session.id;
569
+ this.#intervalMs = options.intervalMs;
570
+ this.#on = on;
571
+ this.#start(options.cursor, session);
572
+ }
573
+ /** The cursor after the last events read, for the same member's next session. */
574
+ get cursor() {
575
+ return this.#cursor;
576
+ }
577
+ /** True once the feed knows where it starts. */
578
+ get ready() {
579
+ return this.#ready;
580
+ }
581
+ refresh() {
582
+ return this.#observer?.refresh() ?? Promise.resolve();
583
+ }
584
+ dispose() {
585
+ this.#disposed = true;
586
+ this.#observer?.dispose();
587
+ this.#observer = null;
588
+ }
589
+ #start(cursor, session) {
590
+ this.#observer?.dispose();
591
+ const generation = {};
592
+ this.#generation = generation;
593
+ this.#cursor = cursor;
594
+ this.#ready = cursor !== void 0;
595
+ this.#answered = false;
596
+ this.#observer = this.#client.me.activity.observe(() => {
597
+ }, {
598
+ cursor,
599
+ // Without a cursor, and whenever the client drops it for another session, start from the
600
+ // newest event rather than replay the member's history.
601
+ start: "latest",
602
+ // The provider knows the session: the first poll reads the feed at once.
603
+ session,
604
+ intervalMs: this.#intervalMs,
605
+ maxBackoffMs: Math.max(6e4, this.#intervalMs),
606
+ onState: (state) => {
607
+ if (!this.#disposed && this.#generation === generation) {
608
+ this.#observe(state, cursor !== void 0);
609
+ }
610
+ }
611
+ });
612
+ }
613
+ #observe(state, carried) {
614
+ switch (state.kind) {
615
+ case "loading":
616
+ return;
617
+ case "session": {
618
+ const session = state.session;
619
+ if ((session?.id ?? null) === this.#session) {
620
+ return;
621
+ }
622
+ if (session && feedOwner(session) === this.owner) {
623
+ this.#session = session.id;
624
+ this.#start(this.#cursor, session);
625
+ } else {
626
+ this.dispose();
627
+ }
628
+ this.#on.session(session);
629
+ return;
630
+ }
631
+ case "ready":
632
+ this.settledAt = Date.now();
633
+ this.#answered = true;
634
+ this.#cursor = state.progress.nextCursor;
635
+ if (this.#ready) {
636
+ this.#on.events(state.progress.items);
637
+ } else {
638
+ this.#ready = true;
639
+ }
640
+ return;
641
+ case "error": {
642
+ this.settledAt = Date.now();
643
+ const error = relayError(state.error);
644
+ const status = error.kind === "http" ? error.status : void 0;
645
+ if (status === 400 && !this.#answered && carried) {
646
+ this.#start(void 0);
647
+ } else if (status === 404 || status === 400 && !this.#answered) {
648
+ this.#on.missing();
649
+ }
650
+ return;
651
+ }
652
+ default: {
653
+ const _exhaustive = state;
654
+ return _exhaustive;
655
+ }
656
+ }
657
+ }
658
+ };
659
+
660
+ // src/react/keys.ts
661
+ var relayKeys = {
662
+ /** `useExperience`: invalidating it reads the quest experience again. */
663
+ experience: ["experience"],
664
+ /** `useMe`. */
665
+ me: ["me"],
666
+ /** `useAccounts`. */
667
+ accounts: ["accounts"],
668
+ /** `useWallets`. */
669
+ wallets: ["wallets"],
670
+ /** `useMyProfile`, `useProfile`, `useProfileCompletions`, `useProfileReferral`. */
671
+ profiles: ["profiles"],
672
+ /** `useLeaderboard`, `useLeaderboardPlacement`. */
673
+ leaderboards: ["leaderboards"],
674
+ /** `useReferralSummary`. */
675
+ referrals: ["referrals"],
676
+ /** `usePointLedgers`. */
677
+ pointLedgers: ["point-ledgers"],
678
+ /** `useTieredRewards`. */
679
+ tieredRewards: ["tiered-rewards"],
680
+ /** `useSignInMethods`. */
681
+ signInMethods: ["sign-in-methods"],
682
+ /** `useEntitlements`, `useDeliveries`. */
683
+ rewards: ["rewards"],
684
+ /** `useBalances`, `usePointHistory`. */
685
+ balances: ["balances"],
686
+ /** `useQuestOutcomes`. */
687
+ outcomes: ["outcomes"],
688
+ /** `useOffers`, `useRedeemOffer`. */
689
+ offers: ["offers"]
690
+ };
691
+ var rewardKeys = {
692
+ entitlements: [...relayKeys.rewards, "entitlements"],
693
+ deliveries: [...relayKeys.rewards, "deliveries"]
694
+ };
695
+ var completionChanges = [
696
+ relayKeys.rewards,
697
+ relayKeys.balances,
698
+ relayKeys.leaderboards,
699
+ relayKeys.profiles,
700
+ relayKeys.referrals,
701
+ relayKeys.offers
702
+ ];
703
+ var activityChanges = {
704
+ "attempt.accepted": [
705
+ relayKeys.experience,
706
+ relayKeys.outcomes,
707
+ ...completionChanges
708
+ ],
709
+ "attempt.rejected": [relayKeys.experience, relayKeys.outcomes],
710
+ "attempt.review": [relayKeys.experience, relayKeys.outcomes],
711
+ "points.awarded": [
712
+ relayKeys.balances,
713
+ relayKeys.leaderboards,
714
+ relayKeys.offers,
715
+ rewardKeys.deliveries
716
+ ],
717
+ "entitlement.granted": [relayKeys.experience, relayKeys.rewards],
718
+ "entitlement.collected": [relayKeys.experience, relayKeys.rewards],
719
+ "delivery.updated": [rewardKeys.deliveries],
720
+ "referral.qualified": [relayKeys.referrals, relayKeys.profiles],
721
+ unknown: [relayKeys.experience]
722
+ };
723
+
476
724
  // src/react/role-delivery.ts
477
725
  function sanitizeDeliveries(deliveries) {
478
726
  return deliveries.map(({ quest, status, revokedAt, reconciliation }) => ({
@@ -578,113 +826,6 @@ function watchRoleDeliveries(options) {
578
826
  };
579
827
  }
580
828
 
581
- // src/react/use-role-deliveries.ts
582
- import { useEffect as useEffect2, useState as useState3 } from "react";
583
- var NONE = Object.freeze([]);
584
- function useRoleDeliveries(relay, member, completions) {
585
- const [state, setState] = useState3(null);
586
- useEffect2(() => {
587
- if (!member || !completions) {
588
- return;
589
- }
590
- const watch = watchRoleDeliveries({
591
- read: async () => (await relay.me.rewardDeliveries()).deliveries,
592
- onChange: (deliveries) => setState({ member, deliveries })
593
- });
594
- const wake = () => {
595
- if (document.visibilityState === "visible") {
596
- watch.wake();
597
- }
598
- };
599
- window.addEventListener("focus", wake);
600
- document.addEventListener("visibilitychange", wake);
601
- return () => {
602
- watch.dispose();
603
- window.removeEventListener("focus", wake);
604
- document.removeEventListener("visibilitychange", wake);
605
- };
606
- }, [relay, member, completions]);
607
- return member && completions && state?.member === member ? state.deliveries : NONE;
608
- }
609
-
610
- // src/react/experience.tsx
611
- import { jsx } from "react/jsx-runtime";
612
- var Deliveries = createContext2(Object.freeze([]));
613
- function useExperienceSnapshot() {
614
- const store = useStore();
615
- const get = () => store.experience;
616
- return useSyncExternalStore2(store.subscribe, get, get);
617
- }
618
- function DeliveriesProvider({ children }) {
619
- const store = useStore();
620
- const experience = useExperienceSnapshot();
621
- const member = useSyncExternalStore2(
622
- store.subscribe,
623
- () => store.scope?.member ?? null,
624
- () => null
625
- );
626
- const ready = experience.status === "ready";
627
- const deliveries = useRoleDeliveries(
628
- store.client,
629
- ready ? member : null,
630
- ready ? completionMark([
631
- ...experience.data.quests,
632
- ...experience.data.collections.flatMap((c) => c.quests)
633
- ]) : ""
634
- );
635
- return /* @__PURE__ */ jsx(Deliveries, { value: deliveries, children });
636
- }
637
- function useExperience() {
638
- const store = useStore();
639
- const experience = useExperienceSnapshot();
640
- const deliveries = useContext2(Deliveries);
641
- const refresh = useCallback3(() => store.refreshExperience(), [store]);
642
- return useMemo(
643
- () => ({ ...experience, refresh, deliveries }),
644
- [experience, refresh, deliveries]
645
- );
646
- }
647
-
648
- // src/react/keys.ts
649
- var relayKeys = {
650
- /** `useExperience`: invalidating it reads the quest experience again. */
651
- experience: ["experience"],
652
- /** `useMe`. */
653
- me: ["me"],
654
- /** `useAccounts`. */
655
- accounts: ["accounts"],
656
- /** `useWallets`. */
657
- wallets: ["wallets"],
658
- /** `useMyProfile`, `useProfile`, `useProfileCompletions`, `useProfileReferral`. */
659
- profiles: ["profiles"],
660
- /** `useLeaderboard`, `useLeaderboardPlacement`. */
661
- leaderboards: ["leaderboards"],
662
- /** `useReferralSummary`. */
663
- referrals: ["referrals"],
664
- /** `usePointLedgers`. */
665
- pointLedgers: ["point-ledgers"],
666
- /** `useTieredRewards`. */
667
- tieredRewards: ["tiered-rewards"],
668
- /** `useSignInMethods`. */
669
- signInMethods: ["sign-in-methods"],
670
- /** `useEntitlements`, `useDeliveries`. */
671
- rewards: ["rewards"],
672
- /** `useBalances`, `usePointHistory`. */
673
- balances: ["balances"],
674
- /** `useQuestOutcomes`. */
675
- outcomes: ["outcomes"],
676
- /** `useOffers`, `useRedeemOffer`. */
677
- offers: ["offers"]
678
- };
679
- var completionChanges = [
680
- relayKeys.rewards,
681
- relayKeys.balances,
682
- relayKeys.leaderboards,
683
- relayKeys.profiles,
684
- relayKeys.referrals,
685
- relayKeys.offers
686
- ];
687
-
688
829
  // src/react/status.ts
689
830
  function isFinished(view) {
690
831
  return view.availability.kind === "completed" || view.completion.kind !== "keyed" && view.completion.kind !== "progress" && view.completed;
@@ -956,6 +1097,9 @@ function scopeKey(session) {
956
1097
  function isVisible() {
957
1098
  return typeof document === "undefined" || document.visibilityState !== "hidden";
958
1099
  }
1100
+ var ACTIVITY_MS = 5e3;
1101
+ var SNAPSHOT_MS = 3e4;
1102
+ var FOCUS_GAP_MS = 2e3;
959
1103
  var RelayStore = class {
960
1104
  client;
961
1105
  #session = LOADING;
@@ -971,8 +1115,28 @@ var RelayStore = class {
971
1115
  #sessionRead = null;
972
1116
  /** The completions the experience last showed, and for whom. */
973
1117
  #completions = null;
974
- constructor(client) {
1118
+ #polling;
1119
+ #feed = null;
1120
+ /** Relay answered that it has no activity feed: the experience observer carries on alone. */
1121
+ #feedMissing = false;
1122
+ /**
1123
+ * The experience read in flight was asked for by an `attempt.accepted` event, which already
1124
+ * read what a completion changes.
1125
+ */
1126
+ #readForAcceptance = false;
1127
+ /** The latest experience read the store asked for. */
1128
+ #experienceRead = Promise.resolve();
1129
+ #shownAt = 0;
1130
+ #focusReadAt = 0;
1131
+ /** The first cache of this mount: a public read's `initialData` seeds only it. */
1132
+ #firstCache = null;
1133
+ #settled = null;
1134
+ constructor(client, polling = {}) {
975
1135
  this.client = client;
1136
+ this.#polling = {
1137
+ activityMs: polling.activityMs ?? ACTIVITY_MS,
1138
+ snapshotMs: polling.snapshotMs ?? SNAPSHOT_MS
1139
+ };
976
1140
  }
977
1141
  /** `refreshSession` with one identity per store, for hooks to hand out as `refresh`. */
978
1142
  rereadSession = () => this.refreshSession();
@@ -991,23 +1155,41 @@ var RelayStore = class {
991
1155
  get experience() {
992
1156
  return this.#experienceView;
993
1157
  }
1158
+ /** The first query cache of this mount, the only one a public read's `initialData` seeds. */
1159
+ get firstCache() {
1160
+ return this.#firstCache;
1161
+ }
1162
+ /** Settles once the session is known (or failed to read), for a component that suspends. */
1163
+ settled() {
1164
+ if (this.#session.status !== "loading") {
1165
+ return Promise.resolve();
1166
+ }
1167
+ if (!this.#settled) {
1168
+ let resolve = () => {
1169
+ };
1170
+ const promise = new Promise((done) => {
1171
+ resolve = done;
1172
+ });
1173
+ this.#settled = { promise, resolve };
1174
+ }
1175
+ return this.#settled.promise;
1176
+ }
994
1177
  /** Starts observing; the returned function stops every read, timer and listener. */
995
1178
  mount() {
996
1179
  this.#mounted = true;
997
- if (isVisible()) {
998
- this.#startObserver();
999
- }
1180
+ this.#feedMissing = false;
1181
+ this.#startObserver();
1000
1182
  void this.refreshSession();
1001
1183
  const onVisibility = () => {
1002
1184
  if (isVisible()) {
1003
- this.#startObserver();
1185
+ this.#shownAt = Date.now();
1004
1186
  this.#scope?.cache.resume();
1005
- } else {
1006
- this.#observer?.dispose();
1007
- this.#observer = null;
1008
1187
  }
1009
1188
  };
1010
- const onFocus = () => this.#scope?.cache.focus();
1189
+ const onFocus = () => {
1190
+ this.#scope?.cache.focus();
1191
+ this.#readOnFocus();
1192
+ };
1011
1193
  if (typeof window !== "undefined") {
1012
1194
  document.addEventListener("visibilitychange", onVisibility);
1013
1195
  window.addEventListener("focus", onFocus);
@@ -1020,11 +1202,13 @@ var RelayStore = class {
1020
1202
  }
1021
1203
  this.#observer?.dispose();
1022
1204
  this.#observer = null;
1205
+ this.#stopFeed();
1023
1206
  this.#sessionRead?.abort();
1024
1207
  this.#sessionRead = null;
1025
1208
  this.#sessionVersion++;
1026
1209
  this.#scope?.cache.dispose();
1027
1210
  this.#scope = null;
1211
+ this.#firstCache = null;
1028
1212
  this.#session = LOADING;
1029
1213
  this.#experience = { owner: null, state: LOADING };
1030
1214
  this.#emit();
@@ -1078,8 +1262,21 @@ var RelayStore = class {
1078
1262
  this.#setSession({ status: "ready", data: null, error: null });
1079
1263
  this.#emit();
1080
1264
  }
1081
- refreshExperience() {
1082
- return this.#observer?.refresh() ?? Promise.resolve();
1265
+ /**
1266
+ * Reads the experience again. With a feed, it is read first: the events it finds read the
1267
+ * experience and whatever else they changed, so a change the participant just made shows its
1268
+ * effects at once, and the experience is read only once.
1269
+ */
1270
+ async refreshExperience() {
1271
+ const feed = this.#feed;
1272
+ if (feed?.ready) {
1273
+ const before = this.#experienceRead;
1274
+ await feed.refresh();
1275
+ if (this.#experienceRead !== before) {
1276
+ return this.#experienceRead;
1277
+ }
1278
+ }
1279
+ return this.#readExperience(false);
1083
1280
  }
1084
1281
  /**
1085
1282
  * Marks queries out of date (see `QueryCache.invalidate`). The `["experience"]` key, or no keys at
@@ -1094,90 +1291,179 @@ var RelayStore = class {
1094
1291
  return Promise.all(reads).then(() => {
1095
1292
  });
1096
1293
  }
1294
+ #readExperience(forAcceptance) {
1295
+ this.#readForAcceptance = forAcceptance && isVisible();
1296
+ this.#experienceRead = this.#observer?.refresh() ?? Promise.resolve();
1297
+ return this.#experienceRead;
1298
+ }
1299
+ /** The window regained focus: read for changes, unless that just happened. */
1300
+ #readOnFocus() {
1301
+ const now = Date.now();
1302
+ const last = Math.max(
1303
+ this.#shownAt,
1304
+ this.#focusReadAt,
1305
+ this.#feed?.settledAt ?? 0
1306
+ );
1307
+ if (now - last < FOCUS_GAP_MS || !this.#scope) {
1308
+ return;
1309
+ }
1310
+ this.#focusReadAt = now;
1311
+ if (this.#feed) {
1312
+ void this.#feed.refresh();
1313
+ } else if (!this.#scope.member) {
1314
+ void this.refreshSession();
1315
+ }
1316
+ if (this.#scope.member && (!this.#feed || this.#experience.state.error)) {
1317
+ void this.#readExperience(false);
1318
+ }
1319
+ }
1320
+ /** Follows the signed-in member's activity feed while there is one; stops it otherwise. */
1321
+ #syncFeed() {
1322
+ const scope = this.#scope;
1323
+ const available = typeof this.client.me?.activity?.observe === "function";
1324
+ const wanted = this.#mounted && available && !this.#feedMissing && scope?.member ? scope : null;
1325
+ const previous = this.#feed;
1326
+ if (previous && previous.scope === wanted?.key) {
1327
+ return;
1328
+ }
1329
+ this.#stopFeed();
1330
+ if (!wanted?.session) {
1331
+ return;
1332
+ }
1333
+ const feed = new ActivityFeed(
1334
+ this.client,
1335
+ wanted.session,
1336
+ {
1337
+ scope: wanted.key,
1338
+ intervalMs: this.#polling.activityMs,
1339
+ // The same member's new session carries on where the last one left off.
1340
+ cursor: previous?.owner === feedOwner(wanted.session) ? previous.cursor : void 0
1341
+ },
1342
+ {
1343
+ events: (events) => this.#applyEvents(events),
1344
+ session: () => void this.refreshSession(),
1345
+ missing: () => {
1346
+ this.#feedMissing = true;
1347
+ this.#stopFeed();
1348
+ }
1349
+ }
1350
+ );
1351
+ this.#feed = feed;
1352
+ }
1353
+ #stopFeed() {
1354
+ const feed = this.#feed;
1355
+ this.#feed = null;
1356
+ feed?.dispose();
1357
+ }
1358
+ /** Reads again, once each, the queries the events change. */
1359
+ #applyEvents(events) {
1360
+ const keys = /* @__PURE__ */ new Map();
1361
+ for (const event of events) {
1362
+ for (const key of activityChanges[event.kind] ?? activityChanges.unknown) {
1363
+ keys.set(hashKey(key), key);
1364
+ }
1365
+ }
1366
+ if (keys.delete(hashKey(relayKeys.experience))) {
1367
+ void this.#readExperience(
1368
+ events.some((event) => event.kind === "attempt.accepted")
1369
+ );
1370
+ }
1371
+ if (keys.size > 0) {
1372
+ void this.#scope?.cache.invalidate([...keys.values()]);
1373
+ }
1374
+ }
1097
1375
  #startObserver() {
1098
1376
  if (this.#observer || !this.#mounted) {
1099
1377
  return;
1100
1378
  }
1101
1379
  this.#starting = true;
1380
+ const { snapshotMs } = this.#polling;
1102
1381
  try {
1103
- this.#observer = this.client.quests.observe((observation) => {
1104
- switch (observation.kind) {
1105
- case "loading":
1106
- if (this.#starting) {
1382
+ this.#observer = this.client.quests.observe(
1383
+ (observation) => {
1384
+ switch (observation.kind) {
1385
+ case "loading":
1386
+ this.#readForAcceptance = false;
1387
+ if (this.#starting) {
1388
+ return;
1389
+ }
1390
+ this.#sessionVersion++;
1391
+ this.#sessionRead?.abort();
1107
1392
  return;
1108
- }
1109
- this.#sessionVersion++;
1110
- this.#sessionRead?.abort();
1111
- return;
1112
- case "session":
1113
- this.#sessionVersion++;
1114
- this.#setSession({
1115
- status: "ready",
1116
- data: observation.session,
1117
- error: null
1118
- });
1119
- break;
1120
- case "ready":
1121
- this.#sessionVersion++;
1122
- this.#setSession({
1123
- status: "ready",
1124
- data: observation.session,
1125
- error: null
1126
- });
1127
- this.#completed(observation.progress);
1128
- this.#experience = {
1129
- owner: observation.progress.member,
1130
- state: {
1393
+ case "session":
1394
+ this.#sessionVersion++;
1395
+ this.#setSession({
1396
+ status: "ready",
1397
+ data: observation.session,
1398
+ error: null
1399
+ });
1400
+ break;
1401
+ case "ready":
1402
+ this.#sessionVersion++;
1403
+ this.#setSession({
1131
1404
  status: "ready",
1132
- // Hidden quests never reach the UI, on any poll.
1133
- data: {
1134
- entitlements: observation.progress.entitlements,
1135
- quests: visibleQuests(observation.progress.quests),
1136
- collections: observation.progress.collections.map(
1137
- (collection) => ({
1138
- ...collection,
1139
- quests: visibleQuests(collection.quests)
1140
- })
1141
- )
1142
- },
1405
+ data: observation.session,
1143
1406
  error: null
1407
+ });
1408
+ this.#completed(observation.progress, this.#readForAcceptance);
1409
+ this.#readForAcceptance = false;
1410
+ this.#experience = {
1411
+ owner: observation.progress.member,
1412
+ state: {
1413
+ status: "ready",
1414
+ // Hidden quests never reach the UI, on any poll.
1415
+ data: {
1416
+ entitlements: observation.progress.entitlements,
1417
+ quests: visibleQuests(observation.progress.quests),
1418
+ collections: observation.progress.collections.map(
1419
+ (collection) => ({
1420
+ ...collection,
1421
+ quests: visibleQuests(collection.quests)
1422
+ })
1423
+ )
1424
+ },
1425
+ error: null
1426
+ }
1427
+ };
1428
+ break;
1429
+ case "error": {
1430
+ const error = relayError(observation.error);
1431
+ const previous = this.#experience;
1432
+ this.#experience = previous.state.status === "ready" ? {
1433
+ owner: previous.owner,
1434
+ state: { ...previous.state, error }
1435
+ } : {
1436
+ owner: this.#scope?.member ?? null,
1437
+ state: { status: "error", data: void 0, error }
1438
+ };
1439
+ if (this.#session.status === "loading") {
1440
+ void this.refreshSession();
1144
1441
  }
1145
- };
1146
- break;
1147
- case "error": {
1148
- const error = relayError(observation.error);
1149
- const previous = this.#experience;
1150
- this.#experience = previous.state.status === "ready" ? {
1151
- owner: previous.owner,
1152
- state: { ...previous.state, error }
1153
- } : {
1154
- owner: this.#scope?.member ?? null,
1155
- state: { status: "error", data: void 0, error }
1156
- };
1157
- if (this.#session.status === "loading") {
1158
- void this.refreshSession();
1442
+ break;
1159
1443
  }
1160
- break;
1161
1444
  }
1162
- }
1163
- this.#emit();
1164
- });
1445
+ this.#emit();
1446
+ },
1447
+ // A failed read retries within seconds, not after the slow interval.
1448
+ { intervalMs: snapshotMs, maxBackoffMs: snapshotMs, retryMs: 3e3 }
1449
+ );
1165
1450
  } finally {
1166
1451
  this.#starting = false;
1167
1452
  }
1168
1453
  }
1169
1454
  /**
1170
1455
  * A new completion changes points, scores, profile completions and referral progress, whether
1171
- * the participant submitted it or Domino completed it by itself: read them again.
1456
+ * the participant submitted it or Domino completed it by itself: read them again, unless the
1457
+ * `attempt.accepted` event that asked for this read already did.
1172
1458
  */
1173
- #completed(progress) {
1459
+ #completed(progress, alreadyRead) {
1174
1460
  const mark = completionMark([
1175
1461
  ...progress.quests,
1176
1462
  ...progress.collections.flatMap((collection) => collection.quests)
1177
1463
  ]);
1178
1464
  const previous = this.#completions;
1179
1465
  this.#completions = { member: progress.member, mark };
1180
- if (previous?.member === progress.member && previous.mark !== mark) {
1466
+ if (previous?.member === progress.member && previous.mark !== mark && !alreadyRead) {
1181
1467
  void this.#scope?.cache.invalidate(completionChanges);
1182
1468
  }
1183
1469
  }
@@ -1195,6 +1481,7 @@ var RelayStore = class {
1195
1481
  this.#scope?.cache.dispose();
1196
1482
  if (key === null) {
1197
1483
  this.#scope = null;
1484
+ this.#syncFeed();
1198
1485
  return;
1199
1486
  }
1200
1487
  const session = next.status === "ready" ? next.data : null;
@@ -1204,6 +1491,8 @@ var RelayStore = class {
1204
1491
  member: sessionMember(session),
1205
1492
  cache: new QueryCache(session?.id, () => void this.refreshSession())
1206
1493
  };
1494
+ this.#firstCache ??= this.#scope.cache;
1495
+ this.#syncFeed();
1207
1496
  }
1208
1497
  #deriveExperience() {
1209
1498
  const session = this.#session;
@@ -1221,6 +1510,10 @@ var RelayStore = class {
1221
1510
  }
1222
1511
  #emit() {
1223
1512
  const experience = this.#deriveExperience();
1513
+ if (this.#settled && this.#session.status !== "loading") {
1514
+ this.#settled.resolve();
1515
+ this.#settled = null;
1516
+ }
1224
1517
  if (experience !== this.#experienceView) {
1225
1518
  this.#experienceView = experience;
1226
1519
  }
@@ -1230,35 +1523,7 @@ var RelayStore = class {
1230
1523
  }
1231
1524
  };
1232
1525
 
1233
- // src/react/provider.tsx
1234
- import { jsx as jsx2 } from "react/jsx-runtime";
1235
- function RelayProvider({
1236
- client,
1237
- baseUrl = "/relay",
1238
- children
1239
- }) {
1240
- const origin = client ? null : baseUrl;
1241
- const store = useMemo2(
1242
- () => new RelayStore(
1243
- client ?? createBrowserRelayClient({ baseUrl: origin ?? baseUrl })
1244
- ),
1245
- // A given client ignores `baseUrl`.
1246
- [client, origin]
1247
- );
1248
- useEffect3(() => store.mount(), [store]);
1249
- return /* @__PURE__ */ jsx2(StoreContext, { value: store, children: /* @__PURE__ */ jsx2(DeliveriesProvider, { children }) });
1250
- }
1251
-
1252
1526
  // src/react/query.ts
1253
- import {
1254
- useCallback as useCallback4,
1255
- useEffect as useEffect4,
1256
- useLayoutEffect as useLayoutEffect2,
1257
- useMemo as useMemo3,
1258
- useRef as useRef3,
1259
- useState as useState4,
1260
- useSyncExternalStore as useSyncExternalStore3
1261
- } from "react";
1262
1527
  function useLatest(value) {
1263
1528
  const ref = useRef3(value);
1264
1529
  useLayoutEffect2(() => {
@@ -1268,17 +1533,34 @@ function useLatest(value) {
1268
1533
  }
1269
1534
  function useSessionSnapshot(store) {
1270
1535
  const get = () => store.session;
1271
- return useSyncExternalStore3(store.subscribe, get, get);
1536
+ return useSyncExternalStore2(store.subscribe, get, get);
1272
1537
  }
1273
1538
  function useScope(store) {
1274
1539
  const get = () => store.scope;
1275
- return useSyncExternalStore3(store.subscribe, get, get);
1540
+ return useSyncExternalStore2(store.subscribe, get, get);
1541
+ }
1542
+ function isInitializer(value) {
1543
+ return typeof value === "function";
1276
1544
  }
1277
- function useCached(cache, key, read, options) {
1545
+ function useSeed(key, initialData) {
1278
1546
  const hash = key ? hashKey(key) : null;
1279
- const { intervalMs, refetchOnFocus } = options;
1280
- const latest = useLatest({ key, read });
1281
- const subscribe = useCallback4(
1547
+ return useMemo(() => {
1548
+ if (initialData === void 0) {
1549
+ return null;
1550
+ }
1551
+ const data = isInitializer(initialData) ? initialData() : initialData;
1552
+ return {
1553
+ seed: { data, at: Date.now() },
1554
+ shown: { status: "ready", data, error: null }
1555
+ };
1556
+ }, [hash]);
1557
+ }
1558
+ function useCached(cache, key, read, options, seeded = null) {
1559
+ const { intervalMs, refetchOnFocus, staleMs } = options;
1560
+ const active = options.enabled === false ? null : key;
1561
+ const hash = active ? hashKey(active) : null;
1562
+ const latest = useLatest({ key: active, read });
1563
+ const subscribe = useCallback3(
1282
1564
  (listener) => {
1283
1565
  const { key: current } = latest.current;
1284
1566
  if (!cache || !current) {
@@ -1288,41 +1570,89 @@ function useCached(cache, key, read, options) {
1288
1570
  return cache.subscribe(
1289
1571
  current,
1290
1572
  (context) => latest.current.read(context),
1291
- { intervalMs, refetchOnFocus },
1292
- listener
1573
+ { intervalMs, refetchOnFocus, staleMs },
1574
+ listener,
1575
+ seeded?.seed
1293
1576
  );
1294
1577
  },
1295
1578
  // `hash` stands for the key.
1296
- [cache, hash, intervalMs, refetchOnFocus]
1579
+ [cache, hash, intervalMs, refetchOnFocus, staleMs, seeded]
1297
1580
  );
1298
- const get = () => cache && key ? cache.snapshot(key) : LOADING;
1299
- const snapshot = useSyncExternalStore3(subscribe, get, get);
1300
- const refresh = useCallback4(
1581
+ const get = () => {
1582
+ const fallback = seeded?.shown ?? LOADING;
1583
+ return cache && active ? cache.snapshot(active, fallback) : fallback;
1584
+ };
1585
+ const snapshot = useSyncExternalStore2(subscribe, get, get);
1586
+ const refresh = useCallback3(
1301
1587
  () => cache && latest.current.key ? cache.refresh(latest.current.key) : Promise.resolve(),
1302
1588
  [cache, hash]
1303
1589
  );
1304
- return useMemo3(() => ({ ...snapshot, refresh }), [snapshot, refresh]);
1590
+ return useMemo(() => ({ ...snapshot, refresh }), [snapshot, refresh]);
1591
+ }
1592
+ function selected(query, select) {
1593
+ return query.status === "ready" ? { ...query, data: select(query.data) } : query;
1594
+ }
1595
+ function suspend(query, store, cache, key, read, seed) {
1596
+ if (query.status === "error") {
1597
+ throw query.error;
1598
+ }
1599
+ if (query.status !== "loading" || !key) {
1600
+ return;
1601
+ }
1602
+ if (typeof window === "undefined") {
1603
+ throw new Error("Domino suspense queries read in the browser");
1604
+ }
1605
+ throw cache ? cache.ensure(key, read, seed) : store.settled();
1305
1606
  }
1306
1607
  function useRelayQuery(key, read, options = {}) {
1307
1608
  const store = useStore();
1308
1609
  const scope = useScope(store);
1309
- return useCached(scope?.cache ?? null, key, read, options);
1610
+ const cache = scope?.cache ?? null;
1611
+ const seeded = useSeed(key, options.initialData);
1612
+ const seed = !cache || cache === store.firstCache ? seeded : null;
1613
+ const query = useCached(cache, key, read, options, seed);
1614
+ const { select } = options;
1615
+ if (options.suspense) {
1616
+ suspend(
1617
+ query,
1618
+ store,
1619
+ cache,
1620
+ options.enabled === false ? null : key,
1621
+ read,
1622
+ seed?.seed
1623
+ );
1624
+ }
1625
+ return useMemo(
1626
+ () => select ? selected(query, select) : query,
1627
+ [query, select]
1628
+ );
1310
1629
  }
1311
1630
  function useMemberQuery(key, read, options = {}) {
1312
1631
  const store = useStore();
1313
1632
  const scope = useScope(store);
1314
1633
  const member = scope?.member ?? null;
1315
- const query = useCached(
1316
- member ? scope.cache : null,
1317
- member ? key : null,
1318
- read,
1319
- options
1634
+ const cache = member ? scope.cache : null;
1635
+ const query = useCached(cache, member ? key : null, read, options);
1636
+ const gated = useMemberGate(store, member, query);
1637
+ if (options.suspense) {
1638
+ suspend(
1639
+ gated,
1640
+ store,
1641
+ cache,
1642
+ options.enabled === false ? null : key,
1643
+ read,
1644
+ void 0
1645
+ );
1646
+ }
1647
+ const { select } = options;
1648
+ return useMemo(
1649
+ () => select ? selected(gated, select) : gated,
1650
+ [gated, select]
1320
1651
  );
1321
- return useMemberGate(store, member, query);
1322
1652
  }
1323
1653
  function useMemberGate(store, member, query) {
1324
1654
  const session = useSessionSnapshot(store);
1325
- return useMemo3(() => {
1655
+ return useMemo(() => {
1326
1656
  if (session.status === "loading") {
1327
1657
  return { ...LOADING, refresh: query.refresh };
1328
1658
  }
@@ -1337,7 +1667,8 @@ function useMemberGate(store, member, query) {
1337
1667
  return member ? query : { ...SIGNED_OUT, refresh: query.refresh };
1338
1668
  }, [session, member, query, store]);
1339
1669
  }
1340
- function usePages(cache, key, read, options) {
1670
+ function usePages(cache, requested, read, options) {
1671
+ const key = options.enabled === false ? null : requested;
1341
1672
  const hash = key ? hashKey(key) : null;
1342
1673
  const query = useCached(
1343
1674
  cache,
@@ -1345,17 +1676,17 @@ function usePages(cache, key, read, options) {
1345
1676
  (context) => read(context),
1346
1677
  options
1347
1678
  );
1348
- const [more, setMore] = useState4(null);
1679
+ const [more, setMore] = useState3(null);
1349
1680
  const busy = useRef3(null);
1350
1681
  const latest = useLatest({ key, read, query });
1351
- useEffect4(
1682
+ useEffect2(
1352
1683
  () => () => {
1353
1684
  busy.current?.abort();
1354
1685
  busy.current = null;
1355
1686
  },
1356
1687
  [cache, hash]
1357
1688
  );
1358
- const loadMore = useCallback4(async () => {
1689
+ const loadMore = useCallback3(async () => {
1359
1690
  const { key: current, read: reader, query: shown } = latest.current;
1360
1691
  if (!cache || !current || busy.current || shown.status !== "ready" || shown.data.nextCursor === null) {
1361
1692
  return;
@@ -1391,7 +1722,7 @@ function usePages(cache, key, read, options) {
1391
1722
  }
1392
1723
  }, [cache, hash]);
1393
1724
  const mine = more && more.owner.cache === cache && more.owner.hash === hash ? more : null;
1394
- return useMemo3(
1725
+ return useMemo(
1395
1726
  () => ({
1396
1727
  ...query,
1397
1728
  loadMore,
@@ -1417,7 +1748,7 @@ function useMemberPages(key, read, options = {}) {
1417
1748
  options
1418
1749
  );
1419
1750
  const gate = useMemberGate(store, member, pages);
1420
- return useMemo3(
1751
+ return useMemo(
1421
1752
  () => gate === pages ? pages : {
1422
1753
  ...gate,
1423
1754
  loadMore: pages.loadMore,
@@ -1429,13 +1760,33 @@ function useMemberPages(key, read, options = {}) {
1429
1760
  }
1430
1761
  function useInvalidate() {
1431
1762
  const store = useStore();
1432
- return useCallback4((keys = []) => store.invalidate(keys), [store]);
1763
+ return useCallback3((keys = []) => store.invalidate(keys), [store]);
1764
+ }
1765
+ function isUpdater(update) {
1766
+ return typeof update === "function";
1767
+ }
1768
+ function useSetQueryData() {
1769
+ const store = useStore();
1770
+ return useCallback3(
1771
+ (key, update) => {
1772
+ const cache = store.scope?.cache;
1773
+ if (!cache) {
1774
+ return false;
1775
+ }
1776
+ const current = cache.snapshot(key);
1777
+ return cache.set(
1778
+ key,
1779
+ isUpdater(update) ? update(current.status === "ready" ? current.data : void 0) : update
1780
+ );
1781
+ },
1782
+ [store]
1783
+ );
1433
1784
  }
1434
1785
  function useSession() {
1435
1786
  const store = useStore();
1436
1787
  const session = useSessionSnapshot(store);
1437
1788
  const refresh = store.rereadSession;
1438
- return useMemo3(
1789
+ return useMemo(
1439
1790
  () => ({
1440
1791
  ...session,
1441
1792
  refresh,
@@ -1447,41 +1798,227 @@ function useSession() {
1447
1798
  function useAction() {
1448
1799
  const session = useScopeSession();
1449
1800
  const store = useStore();
1450
- const scope = useMemo3(() => ({ store, session }), [store, session]);
1801
+ const scope = useMemo(() => ({ store, session }), [store, session]);
1451
1802
  const {
1452
1803
  run: execute,
1453
1804
  retry: replay,
1454
- busy,
1455
- error: actionError,
1456
- settle
1805
+ isPending,
1806
+ error: actionError2,
1807
+ settle,
1808
+ reset: resetAction
1457
1809
  } = useRelayAction(scope);
1458
- const run = useCallback4(
1810
+ const run = useCallback3(
1459
1811
  async (task) => (await execute(task)).ok,
1460
1812
  [execute]
1461
1813
  );
1462
- const retry = useCallback4(async () => (await replay()).ok, [replay]);
1463
- return { busy, actionError, run, retry, settle };
1814
+ const retry = useCallback3(async () => (await replay()).ok, [replay]);
1815
+ return { isPending, actionError: actionError2, resetAction, run, retry, settle };
1464
1816
  }
1465
1817
  function useScopeSession() {
1466
1818
  const store = useStore();
1467
1819
  return useScope(store)?.session?.id;
1468
1820
  }
1469
1821
 
1822
+ // src/react/mutation.ts
1823
+ function useMutation(mutation, options = {}) {
1824
+ const store = useStore();
1825
+ const invalidate = useInvalidate();
1826
+ const session = useScopeSession();
1827
+ const scope = useMemo2(() => ({ store, session }), [store, session]);
1828
+ const action = useRelayAction(scope);
1829
+ const { run, retry, reset, settle } = action;
1830
+ const latest = useRef4({ mutation, options });
1831
+ const ran = useRef4(null);
1832
+ useLayoutEffect3(() => {
1833
+ latest.current = { mutation, options };
1834
+ });
1835
+ const after = useCallback4(
1836
+ async (result) => {
1837
+ if (!result.ok) {
1838
+ throw result.error ?? clientError(
1839
+ "Another change is still running; wait for it to finish",
1840
+ RelayErrorCode.InvalidInput
1841
+ );
1842
+ }
1843
+ const { invalidates } = latest.current.options;
1844
+ const keys = typeof invalidates === "function" ? ran.current ? invalidates(result.value, ran.current.variables) : [] : invalidates;
1845
+ if (keys?.length) {
1846
+ await invalidate(keys);
1847
+ }
1848
+ return result.value;
1849
+ },
1850
+ [invalidate]
1851
+ );
1852
+ const mutateAsync = useCallback4(
1853
+ async (variables) => {
1854
+ const write = latest.current.mutation;
1855
+ const result = await run((actionId) => {
1856
+ ran.current = { variables };
1857
+ return write(variables, { actionId, session });
1858
+ });
1859
+ return after(result);
1860
+ },
1861
+ [run, after, session]
1862
+ );
1863
+ const mutate = useCallback4(
1864
+ (variables) => {
1865
+ mutateAsync(variables).catch(() => {
1866
+ });
1867
+ },
1868
+ [mutateAsync]
1869
+ );
1870
+ const replay = useCallback4(async () => {
1871
+ try {
1872
+ return await after(await retry());
1873
+ } catch {
1874
+ return null;
1875
+ }
1876
+ }, [retry, after]);
1877
+ const shape = useMemo2(
1878
+ () => ({
1879
+ mutate,
1880
+ mutateAsync,
1881
+ status: action.status,
1882
+ isPending: action.isPending,
1883
+ data: action.data,
1884
+ error: action.error,
1885
+ reset,
1886
+ settle
1887
+ }),
1888
+ [
1889
+ mutate,
1890
+ mutateAsync,
1891
+ action.status,
1892
+ action.isPending,
1893
+ action.data,
1894
+ action.error,
1895
+ reset,
1896
+ settle
1897
+ ]
1898
+ );
1899
+ return { mutation: shape, replay };
1900
+ }
1901
+ function useRelayMutation(mutation, options = {}) {
1902
+ return useMutation(mutation, options).mutation;
1903
+ }
1904
+
1905
+ // src/react/provider.tsx
1906
+ import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
1907
+
1908
+ // src/react/experience.tsx
1909
+ import {
1910
+ createContext as createContext2,
1911
+ useCallback as useCallback5,
1912
+ useContext as useContext2,
1913
+ useMemo as useMemo3,
1914
+ useSyncExternalStore as useSyncExternalStore3
1915
+ } from "react";
1916
+
1917
+ // src/react/use-role-deliveries.ts
1918
+ import { useEffect as useEffect3, useState as useState4 } from "react";
1919
+ var NONE = Object.freeze([]);
1920
+ function useRoleDeliveries(relay, member, completions) {
1921
+ const [state, setState] = useState4(null);
1922
+ useEffect3(() => {
1923
+ if (!member || !completions) {
1924
+ return;
1925
+ }
1926
+ const watch = watchRoleDeliveries({
1927
+ read: async () => (await relay.me.rewardDeliveries()).deliveries,
1928
+ onChange: (deliveries) => setState({ member, deliveries })
1929
+ });
1930
+ const wake = () => {
1931
+ if (document.visibilityState === "visible") {
1932
+ watch.wake();
1933
+ }
1934
+ };
1935
+ window.addEventListener("focus", wake);
1936
+ document.addEventListener("visibilitychange", wake);
1937
+ return () => {
1938
+ watch.dispose();
1939
+ window.removeEventListener("focus", wake);
1940
+ document.removeEventListener("visibilitychange", wake);
1941
+ };
1942
+ }, [relay, member, completions]);
1943
+ return member && completions && state?.member === member ? state.deliveries : NONE;
1944
+ }
1945
+
1946
+ // src/react/experience.tsx
1947
+ import { jsx } from "react/jsx-runtime";
1948
+ var Deliveries = createContext2(Object.freeze([]));
1949
+ function useExperienceSnapshot() {
1950
+ const store = useStore();
1951
+ const get = () => store.experience;
1952
+ return useSyncExternalStore3(store.subscribe, get, get);
1953
+ }
1954
+ function DeliveriesProvider({ children }) {
1955
+ const store = useStore();
1956
+ const experience = useExperienceSnapshot();
1957
+ const member = useSyncExternalStore3(
1958
+ store.subscribe,
1959
+ () => store.scope?.member ?? null,
1960
+ () => null
1961
+ );
1962
+ const ready = experience.status === "ready";
1963
+ const deliveries = useRoleDeliveries(
1964
+ store.client,
1965
+ ready ? member : null,
1966
+ ready ? completionMark([
1967
+ ...experience.data.quests,
1968
+ ...experience.data.collections.flatMap((c) => c.quests)
1969
+ ]) : ""
1970
+ );
1971
+ return /* @__PURE__ */ jsx(Deliveries, { value: deliveries, children });
1972
+ }
1973
+ function useExperience() {
1974
+ const store = useStore();
1975
+ const experience = useExperienceSnapshot();
1976
+ const deliveries = useContext2(Deliveries);
1977
+ const refresh = useCallback5(() => store.refreshExperience(), [store]);
1978
+ return useMemo3(
1979
+ () => ({ ...experience, refresh, deliveries }),
1980
+ [experience, refresh, deliveries]
1981
+ );
1982
+ }
1983
+
1984
+ // src/react/provider.tsx
1985
+ import { jsx as jsx2 } from "react/jsx-runtime";
1986
+ function RelayProvider({
1987
+ client,
1988
+ baseUrl = "/relay",
1989
+ polling,
1990
+ children
1991
+ }) {
1992
+ const origin = client ? null : baseUrl;
1993
+ const activityMs = polling?.activityMs;
1994
+ const snapshotMs = polling?.snapshotMs;
1995
+ const store = useMemo4(
1996
+ () => new RelayStore(
1997
+ client ?? createBrowserRelayClient({ baseUrl: origin ?? baseUrl }),
1998
+ { activityMs, snapshotMs }
1999
+ ),
2000
+ // A given client ignores `baseUrl`; `polling` is compared by value.
2001
+ [client, origin, activityMs, snapshotMs]
2002
+ );
2003
+ useEffect4(() => store.mount(), [store]);
2004
+ return /* @__PURE__ */ jsx2(StoreContext, { value: store, children: /* @__PURE__ */ jsx2(DeliveriesProvider, { children }) });
2005
+ }
2006
+
1470
2007
  // src/react/rewards.ts
1471
2008
  function useEntitlements(options = {}) {
1472
2009
  const relay = useRelay();
1473
2010
  return useMemberQuery(
1474
- [...relayKeys.rewards, "entitlements"],
2011
+ rewardKeys.entitlements,
1475
2012
  (context) => relay.rewards.entitlements(context),
1476
- { intervalMs: 5e3, ...options }
2013
+ { intervalMs: 3e4, ...options }
1477
2014
  );
1478
2015
  }
1479
2016
  function useDeliveries(options = {}) {
1480
2017
  const relay = useRelay();
1481
2018
  return useMemberQuery(
1482
- [...relayKeys.rewards, "deliveries"],
2019
+ rewardKeys.deliveries,
1483
2020
  (context) => relay.rewards.deliveries(context),
1484
- { intervalMs: 5e3, ...options }
2021
+ { intervalMs: 3e4, ...options }
1485
2022
  );
1486
2023
  }
1487
2024
 
@@ -1507,11 +2044,11 @@ function questGroups(experience, order = []) {
1507
2044
 
1508
2045
  // src/react/use-quest.ts
1509
2046
  import {
1510
- useCallback as useCallback5,
2047
+ useCallback as useCallback6,
1511
2048
  useEffect as useEffect5,
1512
- useLayoutEffect as useLayoutEffect3,
1513
- useMemo as useMemo4,
1514
- useRef as useRef4,
2049
+ useLayoutEffect as useLayoutEffect4,
2050
+ useMemo as useMemo5,
2051
+ useRef as useRef5,
1515
2052
  useState as useState5
1516
2053
  } from "react";
1517
2054
  function useQuest(observed) {
@@ -1522,7 +2059,7 @@ function useQuest(observed) {
1522
2059
  const [notice, setNotice] = useState5(null);
1523
2060
  const [error, setError] = useState5(null);
1524
2061
  const [busy, setBusy] = useState5(false);
1525
- const lock = useRef4(false);
2062
+ const lock = useRef5(false);
1526
2063
  const [discardedAttempt, setDiscardedAttempt] = useState5(null);
1527
2064
  const [saved, setSaved] = useState5("none");
1528
2065
  const [recoveryRevision, setRecoveryRevision] = useState5(0);
@@ -1551,7 +2088,7 @@ function useQuest(observed) {
1551
2088
  active = false;
1552
2089
  };
1553
2090
  }, [relay, quest, release, completed, attempt, busy, recoveryRevision]);
1554
- const run = useCallback5(
2091
+ const run = useCallback6(
1555
2092
  async (action) => {
1556
2093
  if (lock.current) {
1557
2094
  return;
@@ -1582,11 +2119,11 @@ function useQuest(observed) {
1582
2119
  },
1583
2120
  [store]
1584
2121
  );
1585
- const shown = useRef4(view);
1586
- useLayoutEffect3(() => {
2122
+ const shown = useRef5(view);
2123
+ useLayoutEffect4(() => {
1587
2124
  shown.current = view;
1588
2125
  });
1589
- const actions = useMemo4(
2126
+ const actions = useMemo5(
1590
2127
  () => ({
1591
2128
  restart: async () => {
1592
2129
  if (lock.current) {
@@ -1659,7 +2196,7 @@ function useQuest(observed) {
1659
2196
  [relay, run]
1660
2197
  );
1661
2198
  const startingFresh = view.attempt?.id === discardedAttempt && view.attempt?.status === "failed";
1662
- return useMemo4(
2199
+ return useMemo5(
1663
2200
  () => ({
1664
2201
  /** The newest known version of the quest. Render this, not the prop. */
1665
2202
  view,
@@ -1667,6 +2204,9 @@ function useQuest(observed) {
1667
2204
  draft,
1668
2205
  notice,
1669
2206
  error,
2207
+ /** True while `submit`, `resume`, `retry` or `restart` runs. */
2208
+ isPending: busy,
2209
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
1670
2210
  busy,
1671
2211
  saved,
1672
2212
  /** Review and explicitly restart this terminally retired saved submission. */
@@ -1692,10 +2232,10 @@ function useQuest(observed) {
1692
2232
  }
1693
2233
 
1694
2234
  // src/react/auth.ts
1695
- import { useCallback as useCallback6, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
2235
+ import { useCallback as useCallback7, useMemo as useMemo7, useRef as useRef6, useState as useState7 } from "react";
1696
2236
 
1697
2237
  // src/react/wallets.ts
1698
- import { useEffect as useEffect6, useMemo as useMemo5, useState as useState6 } from "react";
2238
+ import { useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
1699
2239
  function useWallets() {
1700
2240
  const relay = useRelay();
1701
2241
  const invalidate = useInvalidate();
@@ -1705,8 +2245,8 @@ function useWallets() {
1705
2245
  (options) => relay.wallets.list(options),
1706
2246
  { refetchOnFocus: true }
1707
2247
  );
1708
- const { busy, actionError, run, retry, settle } = useAction();
1709
- const actions = useMemo5(() => {
2248
+ const { isPending, actionError: actionError2, resetAction, run, retry, settle } = useAction();
2249
+ const actions = useMemo6(() => {
1710
2250
  const changed = () => void invalidate([relayKeys.wallets, relayKeys.experience]);
1711
2251
  return {
1712
2252
  /** Links the wallet to the member the list was read for; another session is refused. */
@@ -1723,9 +2263,19 @@ function useWallets() {
1723
2263
  })
1724
2264
  };
1725
2265
  }, [relay, invalidate, session, run]);
1726
- return useMemo5(
1727
- () => ({ ...query, busy, actionError, retry, settle, ...actions }),
1728
- [query, busy, actionError, retry, settle, actions]
2266
+ return useMemo6(
2267
+ () => ({
2268
+ ...query,
2269
+ isPending,
2270
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
2271
+ busy: isPending,
2272
+ actionError: actionError2,
2273
+ resetAction,
2274
+ retry,
2275
+ settle,
2276
+ ...actions
2277
+ }),
2278
+ [query, isPending, actionError2, resetAction, retry, settle, actions]
1729
2279
  );
1730
2280
  }
1731
2281
  function discoverChains(key) {
@@ -1793,15 +2343,15 @@ function useMe() {
1793
2343
  function useSignIn() {
1794
2344
  const relay = useRelay();
1795
2345
  const [challenge, setChallengeState] = useState7(null);
1796
- const latest = useRef5(null);
2346
+ const latest = useRef6(null);
1797
2347
  const [busy, setBusy] = useState7(false);
1798
- const lock = useRef5(false);
2348
+ const lock = useRef6(false);
1799
2349
  const [error, setError] = useState7(null);
1800
- const setChallenge = useCallback6((value) => {
2350
+ const setChallenge = useCallback7((value) => {
1801
2351
  latest.current = value;
1802
2352
  setChallengeState(value);
1803
2353
  }, []);
1804
- const run = useCallback6(
2354
+ const run = useCallback7(
1805
2355
  async (task, fallback) => {
1806
2356
  if (lock.current) {
1807
2357
  return;
@@ -1820,14 +2370,14 @@ function useSignIn() {
1820
2370
  },
1821
2371
  []
1822
2372
  );
1823
- const sendCode = useCallback6(
2373
+ const sendCode = useCallback7(
1824
2374
  (email) => run(async () => {
1825
2375
  setChallenge(null);
1826
2376
  setChallenge((await relay.auth.emailStart(email)).challenge);
1827
2377
  }, "Couldn't send a code. Check the address and try again."),
1828
2378
  [relay, run, setChallenge]
1829
2379
  );
1830
- const verifyCode = useCallback6(
2380
+ const verifyCode = useCallback7(
1831
2381
  (code) => run(async () => {
1832
2382
  const sent = latest.current;
1833
2383
  if (!sent) {
@@ -1838,18 +2388,19 @@ function useSignIn() {
1838
2388
  }, "That code didn't work. Check it, or request a new one."),
1839
2389
  [relay, run, setChallenge]
1840
2390
  );
1841
- const startOver = useCallback6(() => {
2391
+ const startOver = useCallback7(() => {
1842
2392
  setChallenge(null);
1843
2393
  setError(null);
1844
2394
  }, [setChallenge]);
1845
- const withProvider = useCallback6(
2395
+ const reset = useCallback7(() => setError(null), []);
2396
+ const withProvider = useCallback7(
1846
2397
  (provider) => run(async () => {
1847
2398
  const { url } = await relay.auth.start(provider, "sign-in");
1848
2399
  window.location.assign(url);
1849
2400
  }, "Couldn't start sign-in. Try again."),
1850
2401
  [relay, run]
1851
2402
  );
1852
- const withWallet = useCallback6(
2403
+ const withWallet = useCallback7(
1853
2404
  (signer) => run(async () => {
1854
2405
  await relay.auth.wallet.signIn(signer);
1855
2406
  setChallenge(null);
@@ -1857,10 +2408,15 @@ function useSignIn() {
1857
2408
  [relay, run, setChallenge]
1858
2409
  );
1859
2410
  const codeSent = challenge !== null;
1860
- return useMemo6(
2411
+ return useMemo7(
1861
2412
  () => ({
2413
+ /** True while a sign-in action runs. */
2414
+ isPending: busy,
2415
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
1862
2416
  busy,
1863
2417
  error,
2418
+ /** Clears `error`, keeping a code already sent. */
2419
+ reset,
1864
2420
  /** True once a code was sent and the form should ask for it. */
1865
2421
  codeSent,
1866
2422
  sendCode,
@@ -1873,6 +2429,7 @@ function useSignIn() {
1873
2429
  [
1874
2430
  busy,
1875
2431
  error,
2432
+ reset,
1876
2433
  codeSent,
1877
2434
  sendCode,
1878
2435
  verifyCode,
@@ -1884,26 +2441,28 @@ function useSignIn() {
1884
2441
  }
1885
2442
  function useSignOut() {
1886
2443
  const store = useStore();
1887
- const [busy, setBusy] = useState7(false);
1888
- const lock = useRef5(false);
1889
- const signOut = useCallback6(async () => {
2444
+ const { mutation } = useMutation(() => store.signOut());
2445
+ const { mutateAsync } = mutation;
2446
+ const lock = useRef6(false);
2447
+ const signOut = useCallback7(async () => {
1890
2448
  if (lock.current) {
1891
2449
  return;
1892
2450
  }
1893
2451
  lock.current = true;
1894
- setBusy(true);
1895
2452
  try {
1896
- await store.signOut();
2453
+ await mutateAsync();
1897
2454
  } finally {
1898
2455
  lock.current = false;
1899
- setBusy(false);
1900
2456
  }
1901
- }, [store]);
1902
- return useMemo6(() => ({ busy, signOut }), [busy, signOut]);
2457
+ }, [mutateAsync]);
2458
+ return useMemo7(
2459
+ () => ({ ...mutation, signOut, busy: mutation.isPending }),
2460
+ [mutation, signOut]
2461
+ );
1903
2462
  }
1904
2463
  function useSignInOptions(input = {}) {
1905
2464
  const chains = JSON.stringify(cosmosChainList(input.cosmos));
1906
- const cosmos = useMemo6(
2465
+ const cosmos = useMemo7(
1907
2466
  () => JSON.parse(chains),
1908
2467
  [chains]
1909
2468
  );
@@ -1912,6 +2471,7 @@ function useSignInOptions(input = {}) {
1912
2471
  const {
1913
2472
  busy,
1914
2473
  error,
2474
+ reset,
1915
2475
  codeSent,
1916
2476
  startOver,
1917
2477
  sendCode,
@@ -1919,7 +2479,7 @@ function useSignInOptions(input = {}) {
1919
2479
  withProvider,
1920
2480
  withWallet
1921
2481
  } = useSignIn();
1922
- const built = useMemo6(() => {
2482
+ const built = useMemo7(() => {
1923
2483
  if (!methods) {
1924
2484
  return null;
1925
2485
  }
@@ -1938,22 +2498,24 @@ function useSignInOptions(input = {}) {
1938
2498
  });
1939
2499
  }, [methods, wallets, cosmos, withProvider, sendCode, verifyCode, withWallet]);
1940
2500
  const methodsError = methods?.error ?? null;
1941
- return useMemo6(
2501
+ return useMemo7(
1942
2502
  () => ({
1943
2503
  options: built?.options ?? null,
1944
2504
  unavailable: built?.unavailable ?? NONE_UNAVAILABLE,
1945
2505
  methodsError,
2506
+ isPending: busy,
1946
2507
  busy,
1947
2508
  error,
2509
+ reset,
1948
2510
  codeSent,
1949
2511
  startOver
1950
2512
  }),
1951
- [built, methodsError, busy, error, codeSent, startOver]
2513
+ [built, methodsError, busy, error, reset, codeSent, startOver]
1952
2514
  );
1953
2515
  }
1954
2516
 
1955
2517
  // src/react/accounts.ts
1956
- import { useMemo as useMemo7 } from "react";
2518
+ import { useMemo as useMemo8 } from "react";
1957
2519
  function isConnected(accounts, provider) {
1958
2520
  return Boolean(
1959
2521
  accounts?.some(
@@ -1970,8 +2532,8 @@ function useAccounts() {
1970
2532
  (options) => relay.auth.accounts(options),
1971
2533
  { refetchOnFocus: true }
1972
2534
  );
1973
- const { busy, actionError, run, retry, settle } = useAction();
1974
- const actions = useMemo7(
2535
+ const { isPending, actionError: actionError2, resetAction, run, retry, settle } = useAction();
2536
+ const actions = useMemo8(
1975
2537
  () => ({
1976
2538
  connect: (provider) => run(async () => {
1977
2539
  const { url } = await relay.auth.start(provider, "connect", {
@@ -1991,22 +2553,34 @@ function useAccounts() {
1991
2553
  [relay, invalidate, session, run]
1992
2554
  );
1993
2555
  const accounts = query.data;
1994
- return useMemo7(
2556
+ return useMemo8(
1995
2557
  () => ({
1996
2558
  ...query,
1997
- busy,
1998
- actionError,
2559
+ isPending,
2560
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
2561
+ busy: isPending,
2562
+ actionError: actionError2,
2563
+ resetAction,
1999
2564
  retry,
2000
2565
  settle,
2001
2566
  isConnected: (provider) => isConnected(accounts, provider),
2002
2567
  ...actions
2003
2568
  }),
2004
- [query, busy, actionError, retry, settle, accounts, actions]
2569
+ [
2570
+ query,
2571
+ isPending,
2572
+ actionError2,
2573
+ resetAction,
2574
+ retry,
2575
+ settle,
2576
+ accounts,
2577
+ actions
2578
+ ]
2005
2579
  );
2006
2580
  }
2007
2581
 
2008
2582
  // src/react/profiles.ts
2009
- import { useMemo as useMemo8 } from "react";
2583
+ import { useMemo as useMemo9 } from "react";
2010
2584
  var MINE = [...relayKeys.profiles, "mine"];
2011
2585
  function useMyProfile() {
2012
2586
  const relay = useRelay();
@@ -2017,8 +2591,8 @@ function useMyProfile() {
2017
2591
  (options) => relay.profiles.mine(options),
2018
2592
  { refetchOnFocus: true }
2019
2593
  );
2020
- const { busy, actionError, run, retry, settle } = useAction();
2021
- const actions = useMemo8(() => {
2594
+ const { isPending, actionError: actionError2, resetAction, run, retry, settle } = useAction();
2595
+ const actions = useMemo9(() => {
2022
2596
  const change = (task) => run(async (actionId) => {
2023
2597
  const cache = store.scope?.cache;
2024
2598
  const saved = await task(actionId);
@@ -2041,9 +2615,19 @@ function useMyProfile() {
2041
2615
  )
2042
2616
  };
2043
2617
  }, [relay, store, session, run]);
2044
- return useMemo8(
2045
- () => ({ ...query, busy, actionError, retry, settle, ...actions }),
2046
- [query, busy, actionError, retry, settle, actions]
2618
+ return useMemo9(
2619
+ () => ({
2620
+ ...query,
2621
+ isPending,
2622
+ /** @deprecated Use `isPending`. Removed in 2.0.0. */
2623
+ busy: isPending,
2624
+ actionError: actionError2,
2625
+ resetAction,
2626
+ retry,
2627
+ settle,
2628
+ ...actions
2629
+ }),
2630
+ [query, isPending, actionError2, resetAction, retry, settle, actions]
2047
2631
  );
2048
2632
  }
2049
2633
  function useProfile(id, options = {}) {
@@ -2084,7 +2668,7 @@ function useProfileCompletions(profile, options = {}) {
2084
2668
  }
2085
2669
 
2086
2670
  // src/react/referrals.ts
2087
- import { useCallback as useCallback7, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef6, useState as useState8 } from "react";
2671
+ import { useCallback as useCallback8, useEffect as useEffect7, useMemo as useMemo10, useRef as useRef7, useState as useState8 } from "react";
2088
2672
  var PENDING = "domino:pending-referral";
2089
2673
  var CODE = /^[a-zA-Z0-9_-]{1,100}$/;
2090
2674
  function captureReferralCode(param = "ref") {
@@ -2117,8 +2701,8 @@ function useInvitation(param = "ref") {
2117
2701
  const code = captureReferralCode(param);
2118
2702
  return code ? { status: "pending", code } : { status: "none" };
2119
2703
  });
2120
- const started = useRef6(null);
2121
- const pending = useRef6({
2704
+ const started = useRef7(null);
2705
+ const pending = useRef7({
2122
2706
  member,
2123
2707
  relay,
2124
2708
  session,
@@ -2134,7 +2718,7 @@ function useInvitation(param = "ref") {
2134
2718
  action: createRelayAction()
2135
2719
  };
2136
2720
  }
2137
- const accept = useCallback7(
2721
+ const accept = useCallback8(
2138
2722
  async (code) => {
2139
2723
  const owner = pending.current;
2140
2724
  if (owner.action.busy) {
@@ -2179,7 +2763,7 @@ function useInvitation(param = "ref") {
2179
2763
  void accept(state.code);
2180
2764
  }
2181
2765
  }, [member, state, accept]);
2182
- return useMemo9(() => ({ invitation: state, accept }), [state, accept]);
2766
+ return useMemo10(() => ({ invitation: state, accept }), [state, accept]);
2183
2767
  }
2184
2768
  function useReferralSummary(options = {}) {
2185
2769
  const relay = useRelay();
@@ -2189,7 +2773,7 @@ function useReferralSummary(options = {}) {
2189
2773
  (request) => relay.referrals.summary(request),
2190
2774
  { intervalMs }
2191
2775
  );
2192
- return useMemo9(() => {
2776
+ return useMemo10(() => {
2193
2777
  let link = null;
2194
2778
  if (query.data && typeof window !== "undefined") {
2195
2779
  const url = new URL(path, window.location.origin);
@@ -2246,7 +2830,7 @@ function useQuestOutcomes(query = {}) {
2246
2830
  }
2247
2831
 
2248
2832
  // src/react/offers.ts
2249
- import { useCallback as useCallback8, useMemo as useMemo10 } from "react";
2833
+ import { useCallback as useCallback9, useMemo as useMemo11 } from "react";
2250
2834
  function useOffers() {
2251
2835
  const relay = useRelay();
2252
2836
  return useRelayQuery(
@@ -2256,34 +2840,19 @@ function useOffers() {
2256
2840
  }
2257
2841
  function useRedeemOffer() {
2258
2842
  const relay = useRelay();
2259
- const invalidate = useInvalidate();
2260
- const session = useScopeSession();
2261
- const scope = useMemo10(() => ({ relay, session }), [relay, session]);
2262
- const {
2263
- run,
2264
- retry: replay,
2265
- settle,
2266
- busy,
2267
- error
2268
- } = useRelayAction(scope);
2269
- const refresh = useCallback8(
2270
- async (result) => {
2271
- const redemption = result.ok ? result.value : null;
2272
- if (redemption) {
2273
- await invalidate([relayKeys.offers, relayKeys.balances]);
2274
- }
2275
- return redemption;
2276
- },
2277
- [invalidate]
2843
+ const { mutation, replay } = useMutation(
2844
+ (offer, { actionId, session }) => relay.offers.redeem(offer, { actionId }, { session }),
2845
+ { invalidates: [relayKeys.offers, relayKeys.balances] }
2846
+ );
2847
+ const { mutateAsync } = mutation;
2848
+ const redeem = useCallback9(
2849
+ (offer) => mutateAsync(offer).catch(() => null),
2850
+ [mutateAsync]
2278
2851
  );
2279
- const redeem = useCallback8(
2280
- (offer) => run(
2281
- (actionId) => relay.offers.redeem(offer, { actionId }, { session })
2282
- ).then(refresh),
2283
- [run, relay, session, refresh]
2852
+ return useMemo11(
2853
+ () => ({ ...mutation, redeem, retry: replay, busy: mutation.isPending }),
2854
+ [mutation, redeem, replay]
2284
2855
  );
2285
- const retry = useCallback8(() => replay().then(refresh), [replay, refresh]);
2286
- return { redeem, retry, settle, busy, error };
2287
2856
  }
2288
2857
 
2289
2858
  // src/react/references.ts
@@ -2343,9 +2912,11 @@ export {
2343
2912
  useReferralSummary,
2344
2913
  useRelay,
2345
2914
  useRelayAction,
2915
+ useRelayMutation,
2346
2916
  useRelayPages,
2347
2917
  useRelayQuery,
2348
2918
  useSession,
2919
+ useSetQueryData,
2349
2920
  useSignIn,
2350
2921
  useSignInMethods,
2351
2922
  useSignInOptions,