@octanejs/apollo-client 0.1.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 (85) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +97 -0
  3. package/package.json +77 -0
  4. package/src/index.d.ts +1 -0
  5. package/src/index.js +3 -0
  6. package/src/react/context/ApolloContext.d.ts +8 -0
  7. package/src/react/context/ApolloContext.js +21 -0
  8. package/src/react/context/ApolloProvider.d.ts +9 -0
  9. package/src/react/context/ApolloProvider.js +15 -0
  10. package/src/react/hooks/constants.d.ts +2 -0
  11. package/src/react/hooks/constants.js +1 -0
  12. package/src/react/hooks/internal/__use.d.ts +3 -0
  13. package/src/react/hooks/internal/__use.js +9 -0
  14. package/src/react/hooks/internal/index.d.ts +5 -0
  15. package/src/react/hooks/internal/index.js +6 -0
  16. package/src/react/hooks/internal/site.d.ts +2 -0
  17. package/src/react/hooks/internal/site.js +20 -0
  18. package/src/react/hooks/internal/useDeepMemo.d.ts +5 -0
  19. package/src/react/hooks/internal/useDeepMemo.js +11 -0
  20. package/src/react/hooks/internal/useIsomorphicLayoutEffect.d.ts +2 -0
  21. package/src/react/hooks/internal/useIsomorphicLayoutEffect.js +8 -0
  22. package/src/react/hooks/internal/useRenderGuard.d.ts +1 -0
  23. package/src/react/hooks/internal/useRenderGuard.js +14 -0
  24. package/src/react/hooks/internal/useSuspenseHookCacheKey.d.ts +13 -0
  25. package/src/react/hooks/internal/useSuspenseHookCacheKey.js +19 -0
  26. package/src/react/hooks/internal/validateSuspenseHookOptions.d.ts +4 -0
  27. package/src/react/hooks/internal/validateSuspenseHookOptions.js +16 -0
  28. package/src/react/hooks/internal/wrapHook.d.ts +77 -0
  29. package/src/react/hooks/internal/wrapHook.js +64 -0
  30. package/src/react/hooks/useApolloClient.d.ts +17 -0
  31. package/src/react/hooks/useApolloClient.js +27 -0
  32. package/src/react/hooks/useBackgroundQuery.d.ts +3061 -0
  33. package/src/react/hooks/useBackgroundQuery.js +88 -0
  34. package/src/react/hooks/useFragment.d.ts +205 -0
  35. package/src/react/hooks/useFragment.js +75 -0
  36. package/src/react/hooks/useLazyQuery.d.ts +947 -0
  37. package/src/react/hooks/useLazyQuery.js +156 -0
  38. package/src/react/hooks/useLoadableQuery.d.ts +955 -0
  39. package/src/react/hooks/useLoadableQuery.js +98 -0
  40. package/src/react/hooks/useMutation.d.ts +585 -0
  41. package/src/react/hooks/useMutation.js +121 -0
  42. package/src/react/hooks/useQuery.d.ts +1401 -0
  43. package/src/react/hooks/useQuery.js +234 -0
  44. package/src/react/hooks/useQueryRefHandlers.d.ts +85 -0
  45. package/src/react/hooks/useQueryRefHandlers.js +79 -0
  46. package/src/react/hooks/useReactiveVar.d.ts +21 -0
  47. package/src/react/hooks/useReactiveVar.js +41 -0
  48. package/src/react/hooks/useReadQuery.d.ts +104 -0
  49. package/src/react/hooks/useReadQuery.js +83 -0
  50. package/src/react/hooks/useSubscription.d.ts +289 -0
  51. package/src/react/hooks/useSubscription.js +258 -0
  52. package/src/react/hooks/useSuspenseFragment.d.ts +154 -0
  53. package/src/react/hooks/useSuspenseFragment.js +50 -0
  54. package/src/react/hooks/useSuspenseQuery.d.ts +1629 -0
  55. package/src/react/hooks/useSuspenseQuery.js +130 -0
  56. package/src/react/hooks/useSyncExternalStore.d.ts +2 -0
  57. package/src/react/hooks/useSyncExternalStore.js +125 -0
  58. package/src/react/index.d.ts +68 -0
  59. package/src/react/index.js +18 -0
  60. package/src/react/internal/cache/FragmentReference.d.ts +41 -0
  61. package/src/react/internal/cache/FragmentReference.js +117 -0
  62. package/src/react/internal/cache/QueryReference.d.ts +180 -0
  63. package/src/react/internal/cache/QueryReference.js +310 -0
  64. package/src/react/internal/cache/SuspenseCache.d.ts +38 -0
  65. package/src/react/internal/cache/SuspenseCache.js +39 -0
  66. package/src/react/internal/cache/getSuspenseCache.d.ts +26 -0
  67. package/src/react/internal/cache/getSuspenseCache.js +8 -0
  68. package/src/react/internal/cache/types.d.ts +13 -0
  69. package/src/react/internal/cache/types.js +1 -0
  70. package/src/react/internal/index.d.ts +16 -0
  71. package/src/react/internal/index.js +10 -0
  72. package/src/react/internal/types.d.ts +15 -0
  73. package/src/react/internal/types.js +1 -0
  74. package/src/react/query-preloader/createQueryPreloader.d.ts +630 -0
  75. package/src/react/query-preloader/createQueryPreloader.js +89 -0
  76. package/src/react/types/deprecated.d.ts +140 -0
  77. package/src/react/types/deprecated.js +1 -0
  78. package/src/react/types/types.documentation.d.ts +551 -0
  79. package/src/react/types/types.documentation.js +1 -0
  80. package/src/testing/index.d.ts +1 -0
  81. package/src/testing/index.js +1 -0
  82. package/src/testing/react/MockedProvider.d.ts +21 -0
  83. package/src/testing/react/MockedProvider.js +53 -0
  84. package/src/testing/react/index.d.ts +2 -0
  85. package/src/testing/react/index.js +1 -0
@@ -0,0 +1,130 @@
1
+ import * as React from 'octane';
2
+ import { NetworkStatus } from '@apollo/client';
3
+ import { getSuspenseCache } from '@octanejs/apollo-client/react/internal';
4
+ import { __DEV__ } from '@apollo/client/utilities/environment';
5
+ import { variablesUnknownSymbol } from '@apollo/client/utilities/internal';
6
+ import { skipToken } from './constants.js';
7
+ import { __use, useDeepMemo, useSuspenseHookCacheKey, wrapHook } from './internal/index.js';
8
+ import { isCompilerSite } from './internal/site.js';
9
+ import { validateSuspenseHookOptions } from './internal/validateSuspenseHookOptions.js';
10
+ import { useApolloClient } from './useApolloClient.js';
11
+ export const useSuspenseQuery = function useSuspenseQuery(query, options, site) {
12
+ 'use no memo';
13
+ if (isCompilerSite(options, skipToken)) {
14
+ site = options;
15
+ options = undefined;
16
+ }
17
+ return wrapHook(
18
+ 'useSuspenseQuery',
19
+ useSuspenseQuery_,
20
+ useApolloClient(typeof options === 'object' ? options.client : undefined),
21
+ )(query, options ?? {}, site);
22
+ };
23
+ function useSuspenseQuery_(query, options, site) {
24
+ const client = useApolloClient(options.client);
25
+ const suspenseCache = getSuspenseCache(client);
26
+ const watchQueryOptions = useWatchQueryOptions({
27
+ client,
28
+ query,
29
+ options,
30
+ });
31
+ const { fetchPolicy } = watchQueryOptions;
32
+ const cacheKey = useSuspenseHookCacheKey(query, options);
33
+ const queryRef = suspenseCache.getQueryRef(cacheKey, () => client.watchQuery(watchQueryOptions));
34
+ let [current, setPromise] = React.useState([queryRef.key, queryRef.promise]);
35
+ // This saves us a re-execution of the render function when a variable changed.
36
+ if (current[0] !== queryRef.key) {
37
+ // eslint-disable-next-line react-hooks/immutability
38
+ current[0] = queryRef.key;
39
+ // eslint-disable-next-line react-hooks/immutability
40
+ current[1] = queryRef.promise;
41
+ }
42
+ let promise = current[1];
43
+ if (queryRef.didChangeOptions(watchQueryOptions)) {
44
+ // eslint-disable-next-line react-hooks/immutability
45
+ current[1] = promise = queryRef.applyOptions(watchQueryOptions);
46
+ }
47
+ React.useEffect(() => {
48
+ const dispose = queryRef.retain();
49
+ const removeListener = queryRef.listen((promise) => {
50
+ setPromise([queryRef.key, promise]);
51
+ });
52
+ return () => {
53
+ removeListener();
54
+ dispose();
55
+ };
56
+ }, [queryRef]);
57
+ const skipResult = React.useMemo(() => {
58
+ const error = queryRef.result.error;
59
+ const complete = !!queryRef.result.data;
60
+ return {
61
+ loading: false,
62
+ data: queryRef.result.data,
63
+ dataState: queryRef.result.dataState,
64
+ networkStatus: error ? NetworkStatus.error : NetworkStatus.ready,
65
+ error,
66
+ complete,
67
+ partial: !complete,
68
+ };
69
+ }, [queryRef.result]);
70
+ const result = fetchPolicy === 'standby' ? skipResult : __use(promise, site);
71
+ const fetchMore = React.useCallback(
72
+ (options) => {
73
+ const promise = queryRef.fetchMore(options);
74
+ setPromise([queryRef.key, queryRef.promise]);
75
+ return promise;
76
+ },
77
+ [queryRef],
78
+ );
79
+ const refetch = React.useCallback(
80
+ (variables) => {
81
+ const promise = queryRef.refetch(variables);
82
+ setPromise([queryRef.key, queryRef.promise]);
83
+ return promise;
84
+ },
85
+ [queryRef],
86
+ );
87
+ // TODO: The internalQueryRef doesn't have TVariables' type information so we have to cast it here
88
+ const subscribeToMore = queryRef.observable.subscribeToMore;
89
+ return React.useMemo(() => {
90
+ return {
91
+ client,
92
+ data: result.data,
93
+ dataState: result.dataState,
94
+ error: result.error,
95
+ networkStatus: result.networkStatus,
96
+ fetchMore,
97
+ refetch,
98
+ subscribeToMore,
99
+ };
100
+ }, [client, fetchMore, refetch, result, subscribeToMore]);
101
+ }
102
+ export function useWatchQueryOptions({ client, query, options }) {
103
+ return useDeepMemo(() => {
104
+ if (options === skipToken) {
105
+ return {
106
+ query,
107
+ fetchPolicy: 'standby',
108
+ [variablesUnknownSymbol]: true,
109
+ };
110
+ }
111
+ const fetchPolicy =
112
+ options.fetchPolicy || client.defaultOptions.watchQuery?.fetchPolicy || 'cache-first';
113
+ const watchQueryOptions = {
114
+ ...options,
115
+ fetchPolicy,
116
+ query,
117
+ notifyOnNetworkStatusChange: false,
118
+ nextFetchPolicy: void 0,
119
+ };
120
+ if (__DEV__) {
121
+ validateSuspenseHookOptions(watchQueryOptions);
122
+ }
123
+ // Assign the updated fetch policy after our validation since `standby` is
124
+ // not a supported fetch policy on its own without the use of `skip`.
125
+ if (options.skip) {
126
+ watchQueryOptions.fetchPolicy = 'standby';
127
+ }
128
+ return watchQueryOptions;
129
+ }, [client, options, query]);
130
+ }
@@ -0,0 +1,2 @@
1
+ export declare const useSyncExternalStore: typeof import('octane').useSyncExternalStore;
2
+ export {};
@@ -0,0 +1,125 @@
1
+ import * as React from 'octane';
2
+ import { __DEV__ } from '@apollo/client/utilities/environment';
3
+ import { canUseDOM } from '@apollo/client/utilities/internal';
4
+ import { maybe } from '@apollo/client/utilities/internal/globals';
5
+ import { invariant } from '@apollo/client/utilities/invariant';
6
+ let didWarnUncachedGetSnapshot = false;
7
+ // Prevent webpack from complaining about our feature detection of the
8
+ // useSyncExternalStore property of the React namespace, which is expected not
9
+ // to exist when using React 17 and earlier, and that's fine.
10
+ const uSESKey = 'useSyncExternalStore';
11
+ const realHook = React[uSESKey];
12
+ const isReactNative = maybe(() => navigator.product) == 'ReactNative';
13
+ const usingJSDOM =
14
+ // Following advice found in this comment from @domenic (maintainer of jsdom):
15
+ // https://github.com/jsdom/jsdom/issues/1537#issuecomment-229405327
16
+ //
17
+ // Since we control the version of Jest and jsdom used when running Apollo
18
+ // Client tests, and that version is recent enough to include " jsdom/x.y.z"
19
+ // at the end of the user agent string, I believe this case is all we need to
20
+ // check. Testing for "Node.js" was recommended for backwards compatibility
21
+ // with older version of jsdom, but we don't have that problem.
22
+ maybe(() => navigator.userAgent.indexOf('jsdom') >= 0) || false;
23
+ // Our tests should all continue to pass if we remove this !usingJSDOM
24
+ // condition, thereby allowing useLayoutEffect when using jsdom. Unfortunately,
25
+ // if we allow useLayoutEffect, then useSyncExternalStore generates many
26
+ // warnings about useLayoutEffect doing nothing on the server. While these
27
+ // warnings are harmless, this !usingJSDOM condition seems to be the best way to
28
+ // prevent them (i.e. skipping useLayoutEffect when using jsdom).
29
+ const canUseLayoutEffect = (canUseDOM || isReactNative) && !usingJSDOM;
30
+ // Adapted from https://www.npmjs.com/package/use-sync-external-store, with
31
+ // Apollo Client deviations called out by "// DEVIATION ..." comments.
32
+ // When/if React.useSyncExternalStore is defined, delegate fully to it.
33
+ export const useSyncExternalStore =
34
+ realHook ||
35
+ ((subscribe, getSnapshot, getServerSnapshot) => {
36
+ // Read the current snapshot from the store on every render. Again, this
37
+ // breaks the rules of React, and only works here because of specific
38
+ // implementation details, most importantly that updates are
39
+ // always synchronous.
40
+ const value = getSnapshot();
41
+ if (
42
+ // DEVIATION: Using __DEV__
43
+ __DEV__ &&
44
+ !didWarnUncachedGetSnapshot &&
45
+ // DEVIATION: Not using Object.is because we know our snapshots will never
46
+ // be exotic primitive values like NaN, which is !== itself.
47
+ value !== getSnapshot()
48
+ ) {
49
+ didWarnUncachedGetSnapshot = true;
50
+ // DEVIATION: Using invariant.error instead of console.error directly.
51
+ invariant.error(34);
52
+ }
53
+ // Because updates are synchronous, we don't queue them. Instead we force a
54
+ // re-render whenever the subscribed state changes by updating an some
55
+ // arbitrary useState hook. Then, during render, we call getSnapshot to read
56
+ // the current value.
57
+ //
58
+ // Because we don't actually use the state returned by the useState hook, we
59
+ // can save a bit of memory by storing other stuff in that slot.
60
+ //
61
+ // To implement the early bailout, we need to track some things on a mutable
62
+ // object. Usually, we would put that in a useRef hook, but we can stash it in
63
+ // our useState hook instead.
64
+ //
65
+ // To force a re-render, we call forceUpdate({inst}). That works because the
66
+ // new object always fails an equality check.
67
+ const [{ inst }, forceUpdate] = React.useState({
68
+ inst: { value, getSnapshot },
69
+ });
70
+ // Track the latest getSnapshot function with a ref. This needs to be updated
71
+ // in the layout phase so we can access it during the tearing check that
72
+ // happens on subscribe.
73
+ if (canUseLayoutEffect) {
74
+ // DEVIATION: We avoid calling useLayoutEffect when !canUseLayoutEffect,
75
+ // which may seem like a conditional hook, but this code ends up behaving
76
+ // unconditionally (one way or the other) because canUseLayoutEffect is
77
+ // constant.
78
+ React.useLayoutEffect(() => {
79
+ Object.assign(inst, { value, getSnapshot });
80
+ // Whenever getSnapshot or subscribe changes, we need to check in the
81
+ // commit phase if there was an interleaved mutation. In concurrent mode
82
+ // this can happen all the time, but even in synchronous mode, an earlier
83
+ // effect may have mutated the store.
84
+ if (checkIfSnapshotChanged(inst)) {
85
+ // Force a re-render.
86
+ forceUpdate({ inst });
87
+ }
88
+ // React Hook React.useLayoutEffect has a missing dependency: 'inst'. Either include it or remove the dependency array.
89
+ // eslint-disable-next-line react-hooks/exhaustive-deps
90
+ }, [subscribe, value, getSnapshot]);
91
+ } else {
92
+ Object.assign(inst, { value, getSnapshot });
93
+ }
94
+ React.useEffect(() => {
95
+ // Check for changes right before subscribing. Subsequent changes will be
96
+ // detected in the subscription handler.
97
+ if (checkIfSnapshotChanged(inst)) {
98
+ // Force a re-render.
99
+ forceUpdate({ inst });
100
+ }
101
+ // Subscribe to the store and return a clean-up function.
102
+ return subscribe(function handleStoreChange() {
103
+ // TODO: Because there is no cross-renderer API for batching updates, it's
104
+ // up to the consumer of this library to wrap their subscription event
105
+ // with unstable_batchedUpdates. Should we try to detect when this isn't
106
+ // the case and print a warning in development?
107
+ // The store changed. Check if the snapshot changed since the last time we
108
+ // read from the store.
109
+ if (checkIfSnapshotChanged(inst)) {
110
+ // Force a re-render.
111
+ forceUpdate({ inst });
112
+ }
113
+ });
114
+ // React Hook React.useEffect has a missing dependency: 'inst'. Either include it or remove the dependency array.
115
+ // eslint-disable-next-line react-hooks/exhaustive-deps
116
+ }, [subscribe]);
117
+ return value;
118
+ });
119
+ function checkIfSnapshotChanged({ value, getSnapshot }) {
120
+ try {
121
+ return value !== getSnapshot();
122
+ } catch {
123
+ return true;
124
+ }
125
+ }
@@ -0,0 +1,68 @@
1
+ export type { ApolloContextValue } from './context/ApolloContext.js';
2
+ export { getApolloContext } from './context/ApolloContext.js';
3
+ export { ApolloProvider } from './context/ApolloProvider.js';
4
+ export { useApolloClient } from './hooks/useApolloClient.js';
5
+ export { useLazyQuery } from './hooks/useLazyQuery.js';
6
+ export { useMutation } from './hooks/useMutation.js';
7
+ export { useQuery } from './hooks/useQuery.js';
8
+ export { useSubscription } from './hooks/useSubscription.js';
9
+ export { useReactiveVar } from './hooks/useReactiveVar.js';
10
+ export { useFragment } from './hooks/useFragment.js';
11
+ export { useSuspenseQuery } from './hooks/useSuspenseQuery.js';
12
+ export { useBackgroundQuery } from './hooks/useBackgroundQuery.js';
13
+ export { useSuspenseFragment } from './hooks/useSuspenseFragment.js';
14
+ export { useLoadableQuery } from './hooks/useLoadableQuery.js';
15
+ export { useQueryRefHandlers } from './hooks/useQueryRefHandlers.js';
16
+ export { useReadQuery } from './hooks/useReadQuery.js';
17
+ export { skipToken } from './hooks/constants.js';
18
+ export type { SkipToken } from './hooks/constants.js';
19
+ export type {
20
+ PreloadQueryFetchPolicy,
21
+ PreloadQueryFunction,
22
+ PreloadQueryOptions,
23
+ } from './query-preloader/createQueryPreloader.js';
24
+ export { createQueryPreloader } from './query-preloader/createQueryPreloader.js';
25
+ export type { PreloadedQueryRef, QueryRef } from '@octanejs/apollo-client/react/internal';
26
+ export type {
27
+ BackgroundQueryHookFetchPolicy,
28
+ BackgroundQueryHookOptions,
29
+ LazyQueryExecFunction,
30
+ LazyQueryHookExecOptions,
31
+ LazyQueryHookOptions,
32
+ LazyQueryResult,
33
+ LazyQueryResultTuple,
34
+ LoadableQueryFetchPolicy,
35
+ LoadableQueryHookOptions,
36
+ LoadQueryFunction,
37
+ MutationFunctionOptions,
38
+ MutationHookOptions,
39
+ MutationResult,
40
+ MutationTuple,
41
+ OnDataOptions,
42
+ OnSubscriptionDataOptions,
43
+ QueryHookOptions,
44
+ QueryResult,
45
+ SubscriptionHookOptions,
46
+ SubscriptionResult,
47
+ SuspenseQueryHookFetchPolicy,
48
+ SuspenseQueryHookOptions,
49
+ UseBackgroundQueryResult,
50
+ UseFragmentOptions,
51
+ UseFragmentResult,
52
+ UseLoadableQueryResult,
53
+ UseQueryRefHandlersResult,
54
+ UseReadQueryResult,
55
+ UseSuspenseFragmentOptions,
56
+ UseSuspenseFragmentResult,
57
+ UseSuspenseQueryResult,
58
+ } from './types/deprecated.js';
59
+ import type { HookWrappers } from './hooks/internal/wrapHook.js';
60
+ /**
61
+ * @internal
62
+ *
63
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
64
+ */
65
+ export declare namespace InternalTypes {
66
+ export type { HookWrappers };
67
+ }
68
+ export declare const reactCompilerVersion: string;
@@ -0,0 +1,18 @@
1
+ export { getApolloContext } from './context/ApolloContext.js';
2
+ export { ApolloProvider } from './context/ApolloProvider.js';
3
+ export { useApolloClient } from './hooks/useApolloClient.js';
4
+ export { useLazyQuery } from './hooks/useLazyQuery.js';
5
+ export { useMutation } from './hooks/useMutation.js';
6
+ export { useQuery } from './hooks/useQuery.js';
7
+ export { useSubscription } from './hooks/useSubscription.js';
8
+ export { useReactiveVar } from './hooks/useReactiveVar.js';
9
+ export { useFragment } from './hooks/useFragment.js';
10
+ export { useSuspenseQuery } from './hooks/useSuspenseQuery.js';
11
+ export { useBackgroundQuery } from './hooks/useBackgroundQuery.js';
12
+ export { useSuspenseFragment } from './hooks/useSuspenseFragment.js';
13
+ export { useLoadableQuery } from './hooks/useLoadableQuery.js';
14
+ export { useQueryRefHandlers } from './hooks/useQueryRefHandlers.js';
15
+ export { useReadQuery } from './hooks/useReadQuery.js';
16
+ export { skipToken } from './hooks/constants.js';
17
+ export { createQueryPreloader } from './query-preloader/createQueryPreloader.js';
18
+ export const reactCompilerVersion = 'uncompiled';
@@ -0,0 +1,41 @@
1
+ import type { ApolloClient, OperationVariables } from '@apollo/client';
2
+ import type { MaybeMasked } from '@apollo/client/masking';
3
+ import type { DecoratedPromise } from '@apollo/client/utilities/internal';
4
+ import type { FragmentKey } from './types.js';
5
+ type FragmentRefPromise<TData> = DecoratedPromise<TData>;
6
+ type Listener<TData> = (promise: FragmentRefPromise<TData>) => void;
7
+ interface FragmentReferenceOptions {
8
+ autoDisposeTimeoutMs?: number;
9
+ onDispose?: () => void;
10
+ }
11
+ export declare class FragmentReference<
12
+ TData = unknown,
13
+ TVariables extends OperationVariables = OperationVariables,
14
+ > {
15
+ readonly observable: ApolloClient.ObservableFragment<TData | null>;
16
+ readonly key: FragmentKey;
17
+ promise: FragmentRefPromise<MaybeMasked<TData>>;
18
+ private resolve;
19
+ private reject;
20
+ private subscription;
21
+ private listeners;
22
+ private autoDisposeTimeoutId?;
23
+ private references;
24
+ constructor(
25
+ client: ApolloClient,
26
+ watchFragmentOptions: ApolloClient.WatchFragmentOptions<TData, TVariables> & {
27
+ from: string | null | Array<string | null>;
28
+ },
29
+ options: FragmentReferenceOptions,
30
+ );
31
+ listen(listener: Listener<MaybeMasked<TData>>): () => void;
32
+ retain(): () => void;
33
+ private dispose;
34
+ private onDispose;
35
+ private subscribeToFragment;
36
+ private handleNext;
37
+ private handleError;
38
+ private deliver;
39
+ private createPendingPromise;
40
+ }
41
+ export {};
@@ -0,0 +1,117 @@
1
+ import { equal } from '@wry/equality';
2
+ import { createFulfilledPromise, decoratePromise } from '@apollo/client/utilities/internal';
3
+ export class FragmentReference {
4
+ observable;
5
+ key = {};
6
+ promise;
7
+ resolve;
8
+ reject;
9
+ subscription;
10
+ listeners = new Set();
11
+ autoDisposeTimeoutId;
12
+ references = 0;
13
+ constructor(client, watchFragmentOptions, options) {
14
+ this.dispose = this.dispose.bind(this);
15
+ this.handleNext = this.handleNext.bind(this);
16
+ this.handleError = this.handleError.bind(this);
17
+ this.observable = client.watchFragment(watchFragmentOptions);
18
+ if (options.onDispose) {
19
+ this.onDispose = options.onDispose;
20
+ }
21
+ const result = this.observable.getCurrentResult();
22
+ // Start a timer that will automatically dispose of the query if the
23
+ // suspended resource does not use this fragmentRef in the given time. This
24
+ // helps prevent memory leaks when a component has unmounted before the
25
+ // query has finished loading.
26
+ const startDisposeTimer = () => {
27
+ if (!this.references) {
28
+ this.autoDisposeTimeoutId = setTimeout(
29
+ this.dispose,
30
+ options.autoDisposeTimeoutMs ?? 30_000,
31
+ );
32
+ }
33
+ };
34
+ this.promise = result.complete
35
+ ? createFulfilledPromise(result.data)
36
+ : this.createPendingPromise();
37
+ this.subscribeToFragment();
38
+ this.promise.then(startDisposeTimer, startDisposeTimer);
39
+ }
40
+ listen(listener) {
41
+ this.listeners.add(listener);
42
+ return () => {
43
+ this.listeners.delete(listener);
44
+ };
45
+ }
46
+ retain() {
47
+ this.references++;
48
+ clearTimeout(this.autoDisposeTimeoutId);
49
+ let disposed = false;
50
+ return () => {
51
+ if (disposed) {
52
+ return;
53
+ }
54
+ disposed = true;
55
+ this.references--;
56
+ setTimeout(() => {
57
+ if (!this.references) {
58
+ this.dispose();
59
+ }
60
+ });
61
+ };
62
+ }
63
+ dispose() {
64
+ this.subscription.unsubscribe();
65
+ }
66
+ onDispose() {
67
+ // noop. overridable by options
68
+ }
69
+ subscribeToFragment() {
70
+ this.subscription = this.observable.subscribe(
71
+ this.handleNext.bind(this),
72
+ this.handleError.bind(this),
73
+ );
74
+ // call `onDispose` when the subscription is finalized, either because it is
75
+ // unsubscribed as a consequence of a `dispose` call or because the
76
+ // ObservableQuery completes because of a `ApolloClient.stop()` call.
77
+ this.subscription.add(this.onDispose);
78
+ }
79
+ handleNext(result) {
80
+ switch (this.promise.status) {
81
+ case 'pending': {
82
+ if (result.complete) {
83
+ return this.resolve?.(result.data);
84
+ }
85
+ this.deliver(this.promise);
86
+ break;
87
+ }
88
+ case 'fulfilled': {
89
+ // This can occur when we already have a result written to the cache and
90
+ // we subscribe for the first time. We create a fulfilled promise in the
91
+ // constructor with a value that is the same as the first emitted value
92
+ // so we want to skip delivering it.
93
+ if (equal(this.promise.value, result.data)) {
94
+ return;
95
+ }
96
+ this.promise = result.complete
97
+ ? createFulfilledPromise(result.data)
98
+ : this.createPendingPromise();
99
+ this.deliver(this.promise);
100
+ }
101
+ }
102
+ }
103
+ handleError(error) {
104
+ this.reject?.(error);
105
+ }
106
+ deliver(promise) {
107
+ this.listeners.forEach((listener) => listener(promise));
108
+ }
109
+ createPendingPromise() {
110
+ return decoratePromise(
111
+ new Promise((resolve, reject) => {
112
+ this.resolve = resolve;
113
+ this.reject = reject;
114
+ }),
115
+ );
116
+ }
117
+ }
@@ -0,0 +1,180 @@
1
+ import type {
2
+ DocumentTypeDecoration,
3
+ ResultOf,
4
+ VariablesOf,
5
+ } from '@graphql-typed-document-node/core';
6
+ import type { ApolloClient, DataState, ObservableQuery, OperationVariables } from '@apollo/client';
7
+ import type { MaybeMasked } from '@apollo/client/masking';
8
+ import type { DecoratedPromise } from '@apollo/client/utilities/internal';
9
+ import type { QueryKey } from './types.js';
10
+ type QueryRefPromise<TData, TStates extends DataState<TData>['dataState']> = DecoratedPromise<
11
+ ObservableQuery.Result<MaybeMasked<TData>, TStates>
12
+ >;
13
+ type Listener<TData, TStates extends DataState<TData>['dataState']> = (
14
+ promise: QueryRefPromise<TData, TStates>,
15
+ ) => void;
16
+ declare const QUERY_REFERENCE_SYMBOL: unique symbol;
17
+ declare const PROMISE_SYMBOL: unique symbol;
18
+ declare const QUERY_REF_BRAND: unique symbol;
19
+ declare const PRELOADED_QUERY_REF_BRAND: unique symbol;
20
+ /**
21
+ * A `QueryReference` is an opaque object returned by `useBackgroundQuery`.
22
+ * A child component reading the `QueryReference` via `useReadQuery` will
23
+ * suspend until the promise resolves.
24
+ */
25
+ export interface QueryRef<
26
+ TData = unknown,
27
+ TVariables extends OperationVariables = OperationVariables,
28
+ TStates extends DataState<TData>['dataState'] = 'complete' | 'streaming',
29
+ > {
30
+ /**
31
+ * @internal
32
+ *
33
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
34
+ */
35
+ [QUERY_REF_BRAND]?(variables: TVariables): {
36
+ data: TData;
37
+ states: TStates;
38
+ };
39
+ }
40
+ export declare namespace QueryRef {
41
+ type ForQuery<
42
+ Document extends DocumentTypeDecoration<any, any>,
43
+ TStates extends DataState<ResultOf<Document>>['dataState'] = 'complete' | 'streaming',
44
+ > = QueryRef<ResultOf<Document>, VariablesOf<Document>, TStates>;
45
+ }
46
+ /**
47
+ * @internal
48
+ * For usage in internal helpers only.
49
+ *
50
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
51
+ */
52
+ interface WrappedQueryRef<
53
+ TData = unknown,
54
+ TVariables extends OperationVariables = OperationVariables,
55
+ TStates extends DataState<TData>['dataState'] = 'complete' | 'streaming',
56
+ > extends QueryRef<TData, TVariables, TStates> {
57
+ /**
58
+ * @internal
59
+ *
60
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
61
+ */
62
+ readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData, TStates>;
63
+ /**
64
+ * @internal
65
+ *
66
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
67
+ */
68
+ [PROMISE_SYMBOL]: QueryRefPromise<TData, TStates>;
69
+ }
70
+ /**
71
+ * A `QueryReference` is an opaque object returned by `useBackgroundQuery`.
72
+ * A child component reading the `QueryReference` via `useReadQuery` will
73
+ * suspend until the promise resolves.
74
+ */
75
+ export interface PreloadedQueryRef<
76
+ TData = unknown,
77
+ TVariables extends OperationVariables = OperationVariables,
78
+ TStates extends DataState<TData>['dataState'] = 'complete' | 'streaming',
79
+ > extends QueryRef<TData, TVariables, TStates> {
80
+ /**
81
+ * @internal
82
+ *
83
+ * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
84
+ */
85
+ [PRELOADED_QUERY_REF_BRAND]: typeof PRELOADED_QUERY_REF_BRAND;
86
+ }
87
+ interface InternalQueryReferenceOptions {
88
+ onDispose?: () => void;
89
+ autoDisposeTimeoutMs?: number;
90
+ }
91
+ export declare function wrapQueryRef<
92
+ TData,
93
+ TVariables extends OperationVariables,
94
+ TStates extends DataState<TData>['dataState'],
95
+ >(
96
+ internalQueryRef: InternalQueryReference<TData, TStates>,
97
+ ): WrappedQueryRef<TData, TVariables, TStates>;
98
+ export declare function assertWrappedQueryRef<
99
+ TData,
100
+ TVariables extends OperationVariables,
101
+ TStates extends DataState<TData>['dataState'],
102
+ >(
103
+ queryRef: QueryRef<TData, TVariables, TStates>,
104
+ ): asserts queryRef is WrappedQueryRef<TData, TVariables, TStates>;
105
+ export declare function assertWrappedQueryRef<
106
+ TData,
107
+ TVariables extends OperationVariables,
108
+ TStates extends DataState<TData>['dataState'],
109
+ >(
110
+ queryRef: QueryRef<TData, TVariables, TStates> | undefined | null,
111
+ ): asserts queryRef is WrappedQueryRef<TData, TVariables, TStates> | undefined | null;
112
+ export declare function getWrappedPromise<TData, TStates extends DataState<TData>['dataState']>(
113
+ queryRef: WrappedQueryRef<TData, any, TStates>,
114
+ ): QueryRefPromise<TData, TStates>;
115
+ export declare function unwrapQueryRef<TData, TStates extends DataState<TData>['dataState']>(
116
+ queryRef: WrappedQueryRef<TData, any, TStates>,
117
+ ): InternalQueryReference<TData, TStates>;
118
+ export declare function unwrapQueryRef<TData, TStates extends DataState<TData>['dataState']>(
119
+ queryRef: Partial<WrappedQueryRef<TData, any, TStates>>,
120
+ ): undefined | InternalQueryReference<TData, TStates>;
121
+ export declare function updateWrappedQueryRef<TData, TStates extends DataState<TData>['dataState']>(
122
+ queryRef: WrappedQueryRef<TData, any, TStates>,
123
+ promise: QueryRefPromise<TData, TStates>,
124
+ ): void;
125
+ declare const OBSERVED_CHANGED_OPTIONS: readonly [
126
+ 'context',
127
+ 'errorPolicy',
128
+ 'fetchPolicy',
129
+ 'refetchOn',
130
+ 'refetchWritePolicy',
131
+ 'returnPartialData',
132
+ ];
133
+ type ObservedOptions = Pick<
134
+ ApolloClient.WatchQueryOptions,
135
+ (typeof OBSERVED_CHANGED_OPTIONS)[number]
136
+ >;
137
+ export declare class InternalQueryReference<
138
+ TData = unknown,
139
+ TStates extends DataState<TData>['dataState'] = DataState<TData>['dataState'],
140
+ > {
141
+ result: ObservableQuery.Result<MaybeMasked<TData>, TStates>;
142
+ readonly key: QueryKey;
143
+ readonly observable: ObservableQuery<TData>;
144
+ promise: QueryRefPromise<TData, TStates>;
145
+ private queue;
146
+ private subscription;
147
+ private listeners;
148
+ private autoDisposeTimeoutId?;
149
+ private resolve;
150
+ private reject;
151
+ private references;
152
+ private softReferences;
153
+ constructor(observable: ObservableQuery<TData, any>, options: InternalQueryReferenceOptions);
154
+ get disposed(): boolean;
155
+ get watchQueryOptions(): ObservableQuery.Options<TData, OperationVariables>;
156
+ reinitialize(): void;
157
+ retain(): () => void;
158
+ softRetain(): () => void;
159
+ didChangeOptions(watchQueryOptions: ObservedOptions): boolean;
160
+ applyOptions(watchQueryOptions: ObservedOptions): QueryRefPromise<TData, TStates>;
161
+ listen(listener: Listener<TData, TStates>): () => void;
162
+ refetch(variables: OperationVariables | undefined): Promise<{
163
+ data: TData | undefined;
164
+ error?: import('@apollo/client').ErrorLike;
165
+ }>;
166
+ fetchMore(options: ObservableQuery.FetchMoreOptions<TData, any, any, any>): Promise<{
167
+ data: TData | undefined;
168
+ error?: import('@apollo/client').ErrorLike;
169
+ }>;
170
+ private dispose;
171
+ private onDispose;
172
+ private handleNext;
173
+ private deliver;
174
+ private initiateFetch;
175
+ private subscribeToQuery;
176
+ private setResult;
177
+ private shouldReject;
178
+ private createPendingPromise;
179
+ }
180
+ export {};