react-fate 1.0.3 → 1.2.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 (44) hide show
  1. package/README.md +1220 -147
  2. package/docs/api/functions/FateClient.md +1 -1
  3. package/docs/api/functions/clientRoot.md +1 -1
  4. package/docs/api/functions/createClient.md +8 -4
  5. package/docs/api/functions/createGraphQLTransport.md +21 -0
  6. package/docs/api/functions/createHTTPTransport.md +1 -1
  7. package/docs/api/functions/createTRPCTransport.md +1 -1
  8. package/docs/api/functions/defer.md +21 -0
  9. package/docs/api/functions/graphqlMutation.md +39 -0
  10. package/docs/api/functions/mutation.md +1 -1
  11. package/docs/api/functions/useFateClient.md +1 -1
  12. package/docs/api/functions/useListView.md +4 -4
  13. package/docs/api/functions/useLiveListView.md +4 -4
  14. package/docs/api/functions/useLiveView.md +52 -11
  15. package/docs/api/functions/useRequest.md +1 -1
  16. package/docs/api/functions/useView.md +85 -11
  17. package/docs/api/functions/view.md +1 -1
  18. package/docs/api/index.md +13 -0
  19. package/docs/api/type-aliases/ConnectionRef.md +2 -2
  20. package/docs/api/type-aliases/Deferred.md +19 -0
  21. package/docs/api/type-aliases/FateDehydratedState.md +11 -0
  22. package/docs/api/type-aliases/GraphQLMutationDefinition.md +19 -0
  23. package/docs/api/type-aliases/GraphQLMutationInput.md +11 -0
  24. package/docs/api/type-aliases/GraphQLMutationMap.md +11 -0
  25. package/docs/api/type-aliases/GraphQLMutationOutput.md +11 -0
  26. package/docs/api/type-aliases/GraphQLTransportOptions.md +119 -0
  27. package/docs/api/type-aliases/HydrateOptions.md +7 -0
  28. package/docs/api/type-aliases/HydrationLimits.md +7 -0
  29. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  30. package/docs/api/type-aliases/Pagination.md +39 -0
  31. package/docs/api/type-aliases/ViewRef.md +1 -1
  32. package/docs/api/variables/toEntityId.md +1 -1
  33. package/docs/guide/actions.md +7 -7
  34. package/docs/guide/deferred-views.md +62 -0
  35. package/docs/guide/getting-started.md +21 -3
  36. package/docs/guide/requests.md +52 -0
  37. package/docs/guide/vue.md +256 -0
  38. package/docs/index.md +1 -0
  39. package/docs/integrations/graphql.md +345 -0
  40. package/docs/{guide/server-integration.md → integrations/server.md} +94 -7
  41. package/docs/{guide/void-integration.md → integrations/void.md} +94 -12
  42. package/lib/index.d.mts +20 -15
  43. package/lib/index.mjs +110 -16
  44. package/package.json +5 -5
package/lib/index.d.mts CHANGED
@@ -1,10 +1,8 @@
1
- import { ConnectionRef, FateClient as FateClient$1, FateMutations, FateRoots, InferFateAPI, Pagination, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createHTTPTransport, createTRPCTransport, mutation, toEntityId, view } from "@nkzw/fate";
1
+ import { ConnectionRef, Deferred, Deferred as Deferred$1, FateClient as FateClient$1, FateDehydratedState, FateMutations, FateRoots, GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, HydrateOptions, HydrationLimits, InferFateAPI, Pagination, Pagination as Pagination$1, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view } from "@nkzw/fate";
2
2
  import { ReactNode } from "react";
3
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
- import * as _$react_fate_client0 from "react-fate/client";
5
3
 
6
4
  //#region src/useRequest.d.ts
7
- type GeneratedFateClient$1 = ReturnType<typeof _$react_fate_client0.createFateClient>;
5
+ type GeneratedFateClient$1 = ReturnType<typeof import('react-fate/client').createFateClient>;
8
6
  type Roots = [GeneratedFateClient$1] extends [never] ? FateRoots : GeneratedFateClient$1 extends FateClient$1<infer R, any> ? R : FateRoots;
9
7
  /**
10
8
  * Declares the data a screen needs and kicks off fetching, suspending while the
@@ -16,7 +14,7 @@ type Roots = [GeneratedFateClient$1] extends [never] ? FateRoots : GeneratedFate
16
14
  declare function useRequest<R extends Request, O extends FateRoots = Roots>(request: R, options?: RequestOptions): RequestResult<O, R>;
17
15
  //#endregion
18
16
  //#region src/context.d.ts
19
- type GeneratedFateClient = ReturnType<typeof _$react_fate_client0.createFateClient>;
17
+ type GeneratedFateClient = ReturnType<typeof import('react-fate/client').createFateClient>;
20
18
  type Mutations = [GeneratedFateClient] extends [never] ? FateMutations : GeneratedFateClient extends FateClient$1<any, infer M> ? M : FateMutations;
21
19
  /**
22
20
  * Provider component that supplies a configured `FateClient` to React hooks.
@@ -27,7 +25,7 @@ declare function FateClient({
27
25
  }: {
28
26
  children: ReactNode;
29
27
  client: FateClient$1<any, any>;
30
- }): _$react_jsx_runtime0.JSX.Element;
28
+ }): import("react").JSX.Element;
31
29
  /**
32
30
  * Returns the nearest `FateClient` from context.
33
31
  */
@@ -45,6 +43,7 @@ type ViewEntityWithTypename$1<V extends View<any, any>> = ViewEntity<V> & {
45
43
  * const post = useLiveView(PostView, postRef);
46
44
  */
47
45
  declare function useLiveView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>;
46
+ declare function useLiveView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>;
48
47
  //#endregion
49
48
  //#region src/listView.d.ts
50
49
  type ConnectionItems<C> = C extends {
@@ -58,14 +57,16 @@ type ConnectionSelection = {
58
57
  };
59
58
  //#endregion
60
59
  //#region src/useLiveListView.d.ts
60
+ type ConnectionValue$1 = {
61
+ items?: ReadonlyArray<any>;
62
+ pagination?: Pagination$1;
63
+ };
64
+ type ResolvedConnection$1<C> = C extends Deferred$1<infer Value> ? Value : NonNullable<C>;
61
65
  /**
62
66
  * Subscribes to a connection field, returning live-updating items and pagination
63
67
  * helpers to load the next or previous page.
64
68
  */
65
- declare function useLiveListView<C extends {
66
- items?: ReadonlyArray<any>;
67
- pagination?: Pagination;
68
- } | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<NonNullable<C>>, LoadMoreFn | null, LoadMoreFn | null];
69
+ declare function useLiveListView<C extends ConnectionValue$1 | Deferred$1<ConnectionValue$1> | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<ResolvedConnection$1<C>>, LoadMoreFn | null, LoadMoreFn | null];
69
70
  //#endregion
70
71
  //#region src/useView.d.ts
71
72
  type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
@@ -78,15 +79,19 @@ type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
78
79
  * const post = useView(PostView, postRef);
79
80
  */
80
81
  declare function useView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>;
82
+ declare function useView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>;
83
+ declare function useView<V extends View<any, any>>(view: V, ref: Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null): ViewData<ViewEntityWithTypename<V>, ViewSelection<V>> | null;
81
84
  //#endregion
82
85
  //#region src/useListView.d.ts
86
+ type ConnectionValue = {
87
+ items?: ReadonlyArray<any>;
88
+ pagination?: Pagination$1;
89
+ };
90
+ type ResolvedConnection<C> = C extends Deferred$1<infer Value> ? Value : NonNullable<C>;
83
91
  /**
84
92
  * Subscribes to a connection field, returning the current items and pagination
85
93
  * helpers to load the next or previous page.
86
94
  */
87
- declare function useListView<C extends {
88
- items?: ReadonlyArray<any>;
89
- pagination?: Pagination;
90
- } | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<NonNullable<C>>, LoadMoreFn | null, LoadMoreFn | null];
95
+ declare function useListView<C extends ConnectionValue | Deferred$1<ConnectionValue> | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<ResolvedConnection<C>>, LoadMoreFn | null, LoadMoreFn | null];
91
96
  //#endregion
92
- export { type ConnectionRef, FateClient, type InferFateAPI, type ViewRef, clientRoot, createClient, createHTTPTransport, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
97
+ export { type ConnectionRef, type Deferred, FateClient, type FateDehydratedState, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type Pagination, type ViewRef, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ConnectionTag, clientRoot, createClient, createHTTPTransport, createTRPCTransport, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
1
+ import { ConnectionTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, isDeferred, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
2
2
  import { createContext, use, useCallback, useDeferredValue, useEffect, useEffectEvent, useMemo, useRef, useSyncExternalStore } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { getListEntries } from "@nkzw/fate/list";
@@ -22,6 +22,16 @@ function useFateClient() {
22
22
  return context;
23
23
  }
24
24
  //#endregion
25
+ //#region src/thenable.ts
26
+ const isFulfilledThenable = (value) => "status" in value && value.status === "fulfilled" && "value" in value;
27
+ const fulfilledThenable = (value) => ({
28
+ status: "fulfilled",
29
+ then(onfulfilled, onrejected) {
30
+ return Promise.resolve(value).then(onfulfilled, onrejected);
31
+ },
32
+ value
33
+ });
34
+ //#endregion
25
35
  //#region src/useView.tsx
26
36
  const nullSnapshot = {
27
37
  status: "fulfilled",
@@ -32,19 +42,99 @@ const nullSnapshot = {
32
42
  };
33
43
  function useView(view, ref) {
34
44
  const client = useFateClient();
45
+ const isDeferredRef = isDeferred(ref);
35
46
  const snapshotRef = useRef(null);
47
+ const mergedSnapshotRef = useRef(null);
48
+ const pendingRef = useRef(null);
49
+ const readViewSnapshot = useCallback((viewRef, coverage = [], cacheKey) => {
50
+ const snapshot = client.readView(view, viewRef);
51
+ const mergeCoverage = (value) => ({
52
+ ...value,
53
+ coverage: coverage.length ? [...coverage, ...value.coverage] : value.coverage
54
+ });
55
+ if (isFulfilledThenable(snapshot)) {
56
+ if (coverage.length) {
57
+ const cached = mergedSnapshotRef.current;
58
+ const resolvedKey = `${viewRef.__typename}:${String(viewRef.id)}`;
59
+ if (cached?.source === snapshot.value && cached.cacheKey === cacheKey && cached.resolvedKey === resolvedKey) {
60
+ snapshotRef.current = cached.thenable.value;
61
+ return cached.thenable;
62
+ }
63
+ const value = mergeCoverage(snapshot.value);
64
+ const thenable = fulfilledThenable(value);
65
+ mergedSnapshotRef.current = {
66
+ cacheKey,
67
+ resolvedKey,
68
+ source: snapshot.value,
69
+ thenable
70
+ };
71
+ snapshotRef.current = value;
72
+ return thenable;
73
+ }
74
+ mergedSnapshotRef.current = null;
75
+ snapshotRef.current = snapshot.value;
76
+ return snapshot;
77
+ }
78
+ mergedSnapshotRef.current = null;
79
+ snapshotRef.current = null;
80
+ return Promise.resolve(snapshot).then((value) => {
81
+ const resolved = mergeCoverage(value);
82
+ snapshotRef.current = resolved;
83
+ return resolved;
84
+ });
85
+ }, [client, view]);
36
86
  const getSnapshot = useCallback(() => {
37
87
  if (ref === null) {
38
88
  snapshotRef.current = null;
39
89
  return nullSnapshot;
40
90
  }
41
- const snapshot = client.readView(view, ref);
42
- snapshotRef.current = snapshot.status === "fulfilled" ? snapshot.value : null;
43
- return snapshot;
91
+ if (!isDeferredRef) {
92
+ pendingRef.current = null;
93
+ return readViewSnapshot(ref);
94
+ }
95
+ const deferred = ref;
96
+ const deferredSnapshot = client.readDeferred(deferred);
97
+ if (isFulfilledThenable(deferredSnapshot)) {
98
+ const resolvedRef = deferredSnapshot.value.data;
99
+ pendingRef.current = null;
100
+ if (resolvedRef === null) {
101
+ snapshotRef.current = {
102
+ coverage: deferredSnapshot.value.coverage,
103
+ data: null
104
+ };
105
+ return fulfilledThenable(snapshotRef.current);
106
+ }
107
+ return readViewSnapshot(client.ref(resolvedRef.__typename, resolvedRef.id, view), deferredSnapshot.value.coverage, deferred);
108
+ }
109
+ if (pendingRef.current?.deferred === ref && pendingRef.current.snapshot === deferredSnapshot) return pendingRef.current.viewSnapshot;
110
+ snapshotRef.current = null;
111
+ const viewSnapshot = Promise.resolve(deferredSnapshot).then((deferredValue) => {
112
+ const resolvedRef = deferredValue.data;
113
+ if (resolvedRef === null) {
114
+ const value = {
115
+ coverage: deferredValue.coverage,
116
+ data: null
117
+ };
118
+ snapshotRef.current = value;
119
+ return value;
120
+ }
121
+ return Promise.resolve(readViewSnapshot(client.ref(resolvedRef.__typename, resolvedRef.id, view), deferredValue.coverage, deferred)).then((value) => {
122
+ snapshotRef.current = value;
123
+ return value;
124
+ });
125
+ });
126
+ pendingRef.current = {
127
+ deferred,
128
+ snapshot: deferredSnapshot,
129
+ viewSnapshot
130
+ };
131
+ return viewSnapshot;
44
132
  }, [
45
133
  client,
46
134
  view,
47
- ref
135
+ ref,
136
+ isDeferredRef,
137
+ readViewSnapshot
48
138
  ]);
49
139
  const snapshot = use(useDeferredValue(useSyncExternalStore(useCallback((onStoreChange) => {
50
140
  if (ref === null) {
@@ -83,12 +173,14 @@ function useView(view, ref) {
83
173
  //#region src/useLiveView.tsx
84
174
  function useLiveView(view, ref) {
85
175
  const client = useFateClient();
86
- const liveId = ref?.id;
87
- const liveType = ref?.__typename;
176
+ const resolvedRef = isDeferred(ref) ? use(client.readDeferred(ref)).data : ref;
177
+ const liveRef = resolvedRef ? client.ref(resolvedRef.__typename, resolvedRef.id, view) : null;
178
+ const liveId = liveRef?.id;
179
+ const liveType = liveRef?.__typename;
88
180
  const subscribeLiveView = useEffectEvent(() => {
89
- if (ref === null) return;
181
+ if (liveRef === null) return;
90
182
  client.assertLiveViewSupport();
91
- return client.subscribeLiveView(view, ref);
183
+ return client.subscribeLiveView(view, liveRef);
92
184
  });
93
185
  useEffect(() => subscribeLiveView(), [
94
186
  client,
@@ -120,11 +212,12 @@ const useListViewInfo = (selection, connection) => ({
120
212
  */
121
213
  function useListView(selection, connection) {
122
214
  const client = useFateClient();
123
- const { metadata, nodeView } = useListViewInfo(selection, connection);
215
+ const resolvedConnection = isDeferred(connection) ? use(client.readDeferred(connection)).data : connection;
216
+ const { metadata, nodeView } = useListViewInfo(selection, resolvedConnection);
124
217
  const subscribe = useCallback((onStoreChange) => metadata ? client.store.subscribeList(metadata.key, onStoreChange) : () => {}, [client, metadata]);
125
218
  const getSnapshot = useCallback(() => metadata ? client.store.getListState(metadata.key) : void 0, [client, metadata]);
126
219
  const listState = useDeferredValue(useSyncExternalStore(subscribe, getSnapshot, getSnapshot));
127
- const pagination = listState?.pagination ?? connection?.pagination;
220
+ const pagination = listState?.pagination ?? resolvedConnection?.pagination;
128
221
  const hasNext = Boolean(pagination?.hasNext);
129
222
  const hasPrevious = Boolean(pagination?.hasPrevious);
130
223
  const nextCursor = pagination?.nextCursor;
@@ -135,10 +228,10 @@ function useListView(selection, connection) {
135
228
  cursor,
136
229
  node: client.rootListRef(id, nodeView)
137
230
  }));
138
- return connection?.items;
231
+ return resolvedConnection?.items;
139
232
  }, [
140
233
  client,
141
- connection?.items,
234
+ resolvedConnection?.items,
142
235
  listState,
143
236
  metadata,
144
237
  nodeView
@@ -193,7 +286,8 @@ function useListView(selection, connection) {
193
286
  */
194
287
  function useLiveListView(selection, connection) {
195
288
  const client = useFateClient();
196
- const { metadata, nodeView } = useListViewInfo(selection, connection);
289
+ const resolvedConnection = isDeferred(connection) ? use(client.readDeferred(connection)).data : connection;
290
+ const { metadata, nodeView } = useListViewInfo(selection, resolvedConnection);
197
291
  const subscribeLiveListView = useEffectEvent(() => {
198
292
  if (!metadata) return;
199
293
  client.assertLiveConnectionSupport();
@@ -204,7 +298,7 @@ function useLiveListView(selection, connection) {
204
298
  metadata?.key,
205
299
  nodeView
206
300
  ]);
207
- return useListView(selection, connection);
301
+ return useListView(selection, resolvedConnection);
208
302
  }
209
303
  //#endregion
210
304
  //#region src/useRequest.tsx
@@ -233,4 +327,4 @@ function useRequest(request, options) {
233
327
  return use(useDeferredValue(promise));
234
328
  }
235
329
  //#endregion
236
- export { FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
330
+ export { FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-fate",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "fate is a modern data client for React.",
5
5
  "homepage": "https://github.com/nkzw-tech/fate",
6
6
  "license": "MIT",
@@ -40,13 +40,13 @@
40
40
  }
41
41
  },
42
42
  "dependencies": {
43
- "@nkzw/fate": "^1.0.3"
43
+ "@nkzw/fate": "^1.2.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/react": "^19.2.14",
46
+ "@types/react": "^19.2.16",
47
47
  "@types/react-dom": "^19.2.3",
48
- "react": "^19.2.6",
49
- "react-dom": "^19.2.6"
48
+ "react": "^19.2.7",
49
+ "react-dom": "^19.2.7"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "^19.2.0",