react-fate 0.1.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +810 -129
  2. package/docs/api/functions/FateClient.md +23 -0
  3. package/docs/api/functions/clientRoot.md +27 -0
  4. package/docs/api/functions/createClient.md +21 -0
  5. package/docs/api/functions/createHTTPTransport.md +51 -0
  6. package/docs/api/functions/createTRPCTransport.md +46 -0
  7. package/docs/api/functions/mutation.md +32 -0
  8. package/docs/api/functions/useFateClient.md +17 -0
  9. package/docs/api/functions/useListView.md +28 -0
  10. package/docs/api/functions/useLiveListView.md +28 -0
  11. package/docs/api/functions/useLiveView.md +38 -0
  12. package/docs/api/functions/useRequest.md +38 -0
  13. package/docs/api/functions/useView.md +37 -0
  14. package/docs/api/functions/view.md +26 -0
  15. package/docs/api/index.md +35 -0
  16. package/docs/api/type-aliases/ConnectionRef.md +13 -0
  17. package/docs/api/type-aliases/InferFateAPI.md +11 -0
  18. package/docs/api/type-aliases/ViewRef.md +13 -0
  19. package/docs/api/variables/toEntityId.md +21 -0
  20. package/docs/guide/actions.md +263 -0
  21. package/docs/guide/core-concepts.md +22 -0
  22. package/docs/guide/getting-started.md +57 -0
  23. package/docs/guide/list-views.md +86 -0
  24. package/docs/guide/live-views.md +245 -0
  25. package/docs/guide/requests.md +130 -0
  26. package/docs/guide/server-integration.md +630 -0
  27. package/docs/guide/views.md +278 -0
  28. package/docs/guide/void-integration.md +167 -0
  29. package/docs/index.md +4 -0
  30. package/lib/cli.d.mts +1 -1
  31. package/lib/cli.mjs +1 -2
  32. package/lib/client.d.mts +7 -0
  33. package/lib/client.mjs +2 -0
  34. package/lib/index.d.mts +41 -17
  35. package/lib/index.mjs +96 -46
  36. package/lib/vite.d.mts +12 -0
  37. package/lib/vite.mjs +8 -0
  38. package/package.json +17 -6
package/lib/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { ConnectionTag, clientRoot, createClient, createTRPCTransport, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
2
- import { createContext, use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
1
+ import { ConnectionTag, clientRoot, createClient, createHTTPTransport, createTRPCTransport, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
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";
5
5
  //#region src/context.tsx
6
6
  const FateContext = createContext(null);
7
7
  /**
@@ -21,7 +21,6 @@ function useFateClient() {
21
21
  if (!context) throw new Error(`react-fate: '<FateClient client={fate}>' is missing.`);
22
22
  return context;
23
23
  }
24
-
25
24
  //#endregion
26
25
  //#region src/useView.tsx
27
26
  const nullSnapshot = {
@@ -31,7 +30,7 @@ const nullSnapshot = {
31
30
  },
32
31
  value: null
33
32
  };
34
- function useView(view$1, ref) {
33
+ function useView(view, ref) {
35
34
  const client = useFateClient();
36
35
  const snapshotRef = useRef(null);
37
36
  const getSnapshot = useCallback(() => {
@@ -39,12 +38,12 @@ function useView(view$1, ref) {
39
38
  snapshotRef.current = null;
40
39
  return nullSnapshot;
41
40
  }
42
- const snapshot$1 = client.readView(view$1, ref);
43
- snapshotRef.current = snapshot$1.status === "fulfilled" ? snapshot$1.value : null;
44
- return snapshot$1;
41
+ const snapshot = client.readView(view, ref);
42
+ snapshotRef.current = snapshot.status === "fulfilled" ? snapshot.value : null;
43
+ return snapshot;
45
44
  }, [
46
45
  client,
47
- view$1,
46
+ view,
48
47
  ref
49
48
  ]);
50
49
  const snapshot = use(useDeferredValue(useSyncExternalStore(useCallback((onStoreChange) => {
@@ -80,49 +79,48 @@ function useView(view$1, ref) {
80
79
  }, [client.store, ref]), getSnapshot, getSnapshot)));
81
80
  return snapshot ? snapshot.data : null;
82
81
  }
83
-
84
82
  //#endregion
85
- //#region src/useRequest.tsx
86
- /**
87
- * Declares the data a screen needs and kicks off fetching, suspending while the
88
- * request resolves.
89
- *
90
- * @example
91
- * const { posts } = useRequest({ posts: { list: PostView } });
92
- */
93
- function useRequest(request, options) {
83
+ //#region src/useLiveView.tsx
84
+ function useLiveView(view, ref) {
94
85
  const client = useFateClient();
95
- const promise = client.request(request, options);
96
- const mode = options?.mode ?? "cache-first";
97
- useEffect(() => {
98
- if (mode === "network-only" || mode === "stale-while-revalidate") return () => {
99
- client.releaseRequest(request, mode);
100
- };
101
- }, [
86
+ const liveId = ref?.id;
87
+ const liveType = ref?.__typename;
88
+ const subscribeLiveView = useEffectEvent(() => {
89
+ if (ref === null) return;
90
+ client.assertLiveViewSupport();
91
+ return client.subscribeLiveView(view, ref);
92
+ });
93
+ useEffect(() => subscribeLiveView(), [
102
94
  client,
103
- mode,
104
- request
95
+ view,
96
+ liveId,
97
+ liveType
105
98
  ]);
106
- return use(useDeferredValue(promise));
99
+ return useView(view, ref);
107
100
  }
108
-
109
101
  //#endregion
110
- //#region src/useListView.tsx
111
- const getNodeView = (view$1) => {
112
- const maybeView = view$1?.items?.node;
113
- if (maybeView) {
102
+ //#region src/listView.ts
103
+ const getNodeView = (view) => {
104
+ const maybeView = view.items?.node;
105
+ if (maybeView && typeof maybeView === "object") {
114
106
  for (const key of Object.keys(maybeView)) if (isViewTag(key)) return maybeView;
115
107
  }
116
- return view$1;
108
+ return view;
117
109
  };
110
+ const getConnectionMetadata = (connection) => connection && typeof connection === "object" ? connection[ConnectionTag] : null;
111
+ const useListViewInfo = (selection, connection) => ({
112
+ metadata: getConnectionMetadata(connection),
113
+ nodeView: useMemo(() => getNodeView(selection), [selection])
114
+ });
115
+ //#endregion
116
+ //#region src/useListView.tsx
118
117
  /**
119
118
  * Subscribes to a connection field, returning the current items and pagination
120
119
  * helpers to load the next or previous page.
121
120
  */
122
121
  function useListView(selection, connection) {
123
122
  const client = useFateClient();
124
- const nodeView = useMemo(() => getNodeView(selection), [selection]);
125
- const metadata = connection && typeof connection === "object" ? connection[ConnectionTag] : null;
123
+ const { metadata, nodeView } = useListViewInfo(selection, connection);
126
124
  const subscribe = useCallback((onStoreChange) => metadata ? client.store.subscribeList(metadata.key, onStoreChange) : () => {}, [client, metadata]);
127
125
  const getSnapshot = useCallback(() => metadata ? client.store.getListState(metadata.key) : void 0, [client, metadata]);
128
126
  const listState = useDeferredValue(useSyncExternalStore(subscribe, getSnapshot, getSnapshot));
@@ -133,8 +131,8 @@ function useListView(selection, connection) {
133
131
  const previousCursor = pagination?.previousCursor;
134
132
  return [
135
133
  useMemo(() => {
136
- if (metadata?.root && listState) return listState.ids.map((id, index) => ({
137
- cursor: listState.cursors?.[index],
134
+ if (metadata && listState) return getListEntries(listState).map(({ cursor, id }) => ({
135
+ cursor,
138
136
  node: client.rootListRef(id, nodeView)
139
137
  }));
140
138
  return connection?.items;
@@ -142,18 +140,20 @@ function useListView(selection, connection) {
142
140
  client,
143
141
  connection?.items,
144
142
  listState,
145
- metadata?.root,
143
+ metadata,
146
144
  nodeView
147
145
  ]),
148
146
  useMemo(() => {
149
147
  if (!metadata || !hasNext || !nextCursor) return null;
150
148
  return async () => {
151
- const { before, first, last, ...values } = metadata.args || {};
149
+ const { before: _before, first, last, ...values } = metadata.args || {};
152
150
  const nextPageSize = first ?? last;
153
151
  await client.loadConnection(nodeView, metadata, {
154
152
  ...values,
155
153
  after: nextCursor,
156
- ...nextPageSize !== void 0 ? { first: nextPageSize } : null
154
+ before: void 0,
155
+ ...nextPageSize !== void 0 ? { first: nextPageSize } : null,
156
+ last: void 0
157
157
  }, { direction: "forward" });
158
158
  };
159
159
  }, [
@@ -166,10 +166,14 @@ function useListView(selection, connection) {
166
166
  useMemo(() => {
167
167
  if (!metadata || !hasPrevious || !previousCursor) return null;
168
168
  return async () => {
169
- const { after, ...values } = metadata.args || {};
169
+ const { after: _after, first, last, ...values } = metadata.args || {};
170
+ const previousPageSize = last ?? first;
170
171
  await client.loadConnection(nodeView, metadata, {
171
172
  ...values,
172
- before: previousCursor
173
+ after: void 0,
174
+ before: previousCursor,
175
+ first: void 0,
176
+ ...previousPageSize !== void 0 ? { last: previousPageSize } : null
173
177
  }, { direction: "backward" });
174
178
  };
175
179
  }, [
@@ -181,6 +185,52 @@ function useListView(selection, connection) {
181
185
  ])
182
186
  ];
183
187
  }
184
-
185
188
  //#endregion
186
- export { FateClient, clientRoot, createClient, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useRequest, useView, view };
189
+ //#region src/useLiveListView.tsx
190
+ /**
191
+ * Subscribes to a connection field, returning live-updating items and pagination
192
+ * helpers to load the next or previous page.
193
+ */
194
+ function useLiveListView(selection, connection) {
195
+ const client = useFateClient();
196
+ const { metadata, nodeView } = useListViewInfo(selection, connection);
197
+ const subscribeLiveListView = useEffectEvent(() => {
198
+ if (!metadata) return;
199
+ client.assertLiveConnectionSupport();
200
+ return client.subscribeLiveListView(nodeView, metadata);
201
+ });
202
+ useEffect(() => subscribeLiveListView(), [
203
+ client,
204
+ metadata?.key,
205
+ nodeView
206
+ ]);
207
+ return useListView(selection, connection);
208
+ }
209
+ //#endregion
210
+ //#region src/useRequest.tsx
211
+ /**
212
+ * Declares the data a screen needs and kicks off fetching, suspending while the
213
+ * request resolves.
214
+ *
215
+ * @example
216
+ * const { posts } = useRequest({ posts: { list: PostView } });
217
+ */
218
+ function useRequest(request, options) {
219
+ const client = useFateClient();
220
+ const mode = options?.mode ?? "cache-first";
221
+ const { promise, requestKey } = client.prepareRequestForRender(request, options);
222
+ useEffect(() => {
223
+ const retained = client.retainRequestKey(requestKey, mode);
224
+ return () => {
225
+ retained.dispose();
226
+ if (mode === "network-only" || mode === "stale-while-revalidate") client.releaseRequestKey(requestKey, mode);
227
+ };
228
+ }, [
229
+ client,
230
+ mode,
231
+ requestKey
232
+ ]);
233
+ return use(useDeferredValue(promise));
234
+ }
235
+ //#endregion
236
+ export { FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
package/lib/vite.d.mts ADDED
@@ -0,0 +1,12 @@
1
+ import { FateViteTransport, fate as fate$1 } from "@nkzw/fate/vite";
2
+
3
+ //#region src/vite.d.ts
4
+ type FateVitePluginOptions = {
5
+ generatedFile?: false | string;
6
+ module: string;
7
+ transport?: FateViteTransport;
8
+ tsconfigFile?: false | string;
9
+ };
10
+ declare const fate: (options: FateVitePluginOptions) => ReturnType<typeof fate$1>;
11
+ //#endregion
12
+ export { FateVitePluginOptions, fate };
package/lib/vite.mjs ADDED
@@ -0,0 +1,8 @@
1
+ import { fate as fate$1 } from "@nkzw/fate/vite";
2
+ //#region src/vite.ts
3
+ const fate = (options) => fate$1({
4
+ ...options,
5
+ clientModule: "react-fate"
6
+ });
7
+ //#endregion
8
+ export { fate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-fate",
3
- "version": "0.1.3",
3
+ "version": "1.0.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",
@@ -16,6 +16,7 @@
16
16
  "fate": "./lib/cli.mjs"
17
17
  },
18
18
  "files": [
19
+ "docs",
19
20
  "lib"
20
21
  ],
21
22
  "type": "module",
@@ -26,22 +27,32 @@
26
27
  "types": "./lib/index.d.mts",
27
28
  "@nkzw/source": "./src/index.tsx",
28
29
  "default": "./lib/index.mjs"
30
+ },
31
+ "./client": {
32
+ "types": "./lib/client.d.mts",
33
+ "@nkzw/source": "./src/client.ts",
34
+ "default": "./lib/client.mjs"
35
+ },
36
+ "./vite": {
37
+ "types": "./lib/vite.d.mts",
38
+ "@nkzw/source": "./src/vite.ts",
39
+ "default": "./lib/vite.mjs"
29
40
  }
30
41
  },
31
42
  "dependencies": {
32
- "@nkzw/fate": "^0.1.3"
43
+ "@nkzw/fate": "^1.0.0"
33
44
  },
34
45
  "devDependencies": {
35
- "@types/react": "^19.2.9",
46
+ "@types/react": "^19.2.14",
36
47
  "@types/react-dom": "^19.2.3",
37
- "react": "^19.2.3",
38
- "react-dom": "^19.2.3"
48
+ "react": "^19.2.6",
49
+ "react-dom": "^19.2.6"
39
50
  },
40
51
  "peerDependencies": {
41
52
  "react": "^19.2.0",
42
53
  "react-dom": "^19.2.0"
43
54
  },
44
55
  "scripts": {
45
- "build": "vite lib -d lib --target=node24 src/index.tsx src/cli.ts"
56
+ "build": "vp pack -d lib --target=node24 src/index.tsx src/client.ts src/cli.ts src/vite.ts"
46
57
  }
47
58
  }