@zeroin.earth/appwrite-graphql 22.4.1 → 23.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +572 -47
- package/dist/index.cjs +173 -255
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7919 -8877
- package/dist/index.d.ts +7919 -8877
- package/dist/index.js +173 -255
- package/dist/index.js.map +1 -1
- package/package.json +56 -27
- package/react-native/index.cjs +173 -255
- package/react-native/index.d.cts +7919 -8877
package/README.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Appwrite GraphQL
|
|
2
|
+
  
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
Appwrite is an open source, BaaS in the same vein as Supabase and Firebase, but geared more toward self-hosting.
|
|
5
|
+
|
|
6
|
+
This is a fully featured GraphQL library built with [@tanstack/react-query](https://github.com/TanStack/query) on top of the Appwrite web SDK and is fully typed. Think of this library as the abstract wrapper you would have made yourself, but we already did it for you.
|
|
7
|
+
|
|
8
|
+
## Getting Started
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Basic Usage](#usage)
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Dual build for both React and React Native
|
|
15
|
+
- Full Appwrite SDK v23 parity using React hooks
|
|
16
|
+
- [Optimistic Mutations](#optimistic-mutations)
|
|
17
|
+
- Documents only
|
|
18
|
+
- [Query Caching](#query-caching)
|
|
19
|
+
- [QueryKey Builder](#querykey-builder)
|
|
20
|
+
- [Offline-first Support](#offline-first-support)
|
|
21
|
+
- [Built-in Offline Persisters](#built-in-offline-persisters) (localStorage, AsyncStorage)
|
|
22
|
+
- [Custom Offline Persister Support](#custom-offline-persister-support)
|
|
23
|
+
- [Conflict Resolution](#conflict-resolution)
|
|
24
|
+
- [SSR Support](#ssr-support)
|
|
25
|
+
- [Field Selection](#field-selection)
|
|
26
|
+
- [Suspense Queries](#suspense-queries)
|
|
27
|
+
- Documents
|
|
28
|
+
- Collections
|
|
29
|
+
- [Pagination Hooks](#pagination-hooks)
|
|
30
|
+
- Standard Pagination
|
|
31
|
+
- Infinite Scroll
|
|
32
|
+
- [Appwrite QueryBuilder](#appwrite-querybuilder)
|
|
33
|
+
- [React Query Devtools Support](#react-query-devtools-support)
|
|
4
34
|
|
|
5
35
|
## Installation
|
|
6
36
|
|
|
@@ -10,39 +40,555 @@ npm install --save @zeroin.earth/appwrite-graphql
|
|
|
10
40
|
bun add @zeroin.earth/appwrite-graphql
|
|
11
41
|
```
|
|
12
42
|
|
|
43
|
+
### Peer Dependencies
|
|
44
|
+
|
|
45
|
+
- `react` - `^19.1.0`
|
|
46
|
+
- `appwrite` - `^23.0.0`
|
|
47
|
+
- `@tanstack/react-query` - `^5.70.0`
|
|
48
|
+
|
|
49
|
+
**React Native:**
|
|
50
|
+
|
|
51
|
+
- `@react-native-async-storage/async-storage`
|
|
52
|
+
- `@react-native-community/netinfo`
|
|
53
|
+
- `react-native-appwrite`
|
|
54
|
+
|
|
13
55
|
## Usage
|
|
14
56
|
|
|
15
|
-
###
|
|
16
|
-
|
|
57
|
+
### Provider
|
|
58
|
+
|
|
59
|
+
The library is designed to use a single wrapper, `<AppwriteProvider>`. There are multiple ways you can configure the wrapper based on your app's needs:
|
|
60
|
+
|
|
61
|
+
1. Basic (no offline-first support) - React
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import {
|
|
65
|
+
AppwriteProvider,
|
|
66
|
+
createAppwriteClient
|
|
67
|
+
} from '@zeroin.earth/appwrite-graphql'
|
|
17
68
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
EXPO_PUBLIC_APPWRITE_URL=
|
|
69
|
+
const client = createAppwriteClient({
|
|
70
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
71
|
+
projectId: 'my-project',
|
|
72
|
+
})
|
|
23
73
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
74
|
+
function App() {
|
|
75
|
+
return (
|
|
76
|
+
<AppwriteProvider client={client}>
|
|
77
|
+
{/* your app */}
|
|
78
|
+
</AppwriteProvider>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
28
81
|
```
|
|
29
82
|
|
|
30
|
-
|
|
31
|
-
|
|
83
|
+
2. Offline-first - React
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import {
|
|
87
|
+
AppwriteProvider,
|
|
88
|
+
createOfflineClient,
|
|
89
|
+
webNetworkAdapter,
|
|
90
|
+
} from '@zeroin.earth/appwrite-graphql'
|
|
91
|
+
|
|
92
|
+
const { appwrite, queryClient, persister } = createOfflineClient({
|
|
93
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
94
|
+
projectId: 'my-project',
|
|
95
|
+
storage: localStorage, // or any AsyncStorage-compatible interface
|
|
96
|
+
networkAdapter: webNetworkAdapter(),
|
|
97
|
+
})
|
|
32
98
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
99
|
+
function App() {
|
|
100
|
+
return (
|
|
101
|
+
<AppwriteProvider
|
|
102
|
+
client={appwrite}
|
|
103
|
+
queryClient={queryClient}
|
|
104
|
+
persister={persister}
|
|
105
|
+
onCacheRestored={() => console.log('Cache restored mutations replayed')}
|
|
106
|
+
>
|
|
107
|
+
{/* your app */}
|
|
108
|
+
</AppwriteProvider>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
37
111
|
```
|
|
38
112
|
|
|
39
|
-
|
|
113
|
+
3. Offline-first - React Native
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
117
|
+
import {
|
|
118
|
+
AppwriteProvider,
|
|
119
|
+
createOfflineClient,
|
|
120
|
+
} from '@zeroin.earth/appwrite-graphql'
|
|
121
|
+
|
|
122
|
+
import {
|
|
123
|
+
reactNativeNetworkAdapter
|
|
124
|
+
} from '@zeroin.earth/appwrite-graphql/react-native'
|
|
125
|
+
|
|
126
|
+
const { appwrite, queryClient, persister } = createOfflineClient({
|
|
127
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
128
|
+
projectId: 'my-project',
|
|
129
|
+
storage: AsyncStorage,
|
|
130
|
+
networkAdapter: reactNativeNetworkAdapter(),
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
function App() {
|
|
134
|
+
return (
|
|
135
|
+
<AppwriteProvider
|
|
136
|
+
client={appwrite}
|
|
137
|
+
queryClient={queryClient}
|
|
138
|
+
persister={persister}
|
|
139
|
+
>
|
|
140
|
+
{/* your app */}
|
|
141
|
+
</AppwriteProvider>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
4. Offline-first - React with custom persister
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import {
|
|
150
|
+
AppwriteProvider,
|
|
151
|
+
createOfflineClient,
|
|
152
|
+
webNetworkAdapter,
|
|
153
|
+
type Persister,
|
|
154
|
+
} from '@zeroin.earth/appwrite-graphql'
|
|
155
|
+
|
|
156
|
+
const myPersister: Persister = {
|
|
157
|
+
persistClient: async (client) => { /* write to your storage */ },
|
|
158
|
+
restoreClient: async () => { /* read from your storage */ },
|
|
159
|
+
removeClient: async () => { /* clear your storage */ },
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { appwrite, queryClient, persister } = createOfflineClient({
|
|
163
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
164
|
+
projectId: 'my-project',
|
|
165
|
+
persister: myPersister,
|
|
166
|
+
networkAdapter: webNetworkAdapter(),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
function App() {
|
|
170
|
+
return (
|
|
171
|
+
<AppwriteProvider
|
|
172
|
+
client={appwrite}
|
|
173
|
+
queryClient={queryClient}
|
|
174
|
+
persister={persister}
|
|
175
|
+
>
|
|
176
|
+
{/* your app */}
|
|
177
|
+
</AppwriteProvider>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
5. Offline - Imperative / non-React
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import {
|
|
186
|
+
createOfflineClient,
|
|
187
|
+
webNetworkAdapter,
|
|
188
|
+
} from '@zeroin.earth/appwrite-graphql'
|
|
189
|
+
|
|
190
|
+
const client = createOfflineClient({
|
|
191
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
192
|
+
projectId: 'my-project',
|
|
193
|
+
storage: localStorage,
|
|
194
|
+
networkAdapter: webNetworkAdapter(),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Start persistence — restores cache from storage, subscribes to
|
|
198
|
+
// future changes, and replays paused mutations once restored.
|
|
199
|
+
const { unsubscribe, restored } = client.startPersistence()
|
|
200
|
+
|
|
201
|
+
await restored
|
|
202
|
+
console.log('Cache restored, paused mutations replayed')
|
|
203
|
+
|
|
204
|
+
// Use client.queryClient and client.appwrite directly
|
|
205
|
+
// ...
|
|
206
|
+
// Cleanup when done
|
|
207
|
+
|
|
208
|
+
unsubscribe()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Optimistic Mutations
|
|
212
|
+
|
|
213
|
+
For this first iteration, the project provides optimistic mutations for document-related mutation hooks: `useUpdateDocument`, `useUpsertDocument`, `useIncrementAttribute`, `useDecrementAttribute`, and `useDeleteDocument`. Optimistic Mutations can easily be added to other hooks. We wanted to make sure the most important ones were covered first.
|
|
214
|
+
|
|
215
|
+
Optimistic mutations allow us to update the query cache while the mutation is in-flight, giving us the illusion of immediate updates without waiting for a server response. If the server update fails for any reason, the optimistic update is reverted to prevent incorrect data from being displayed.
|
|
216
|
+
|
|
217
|
+
## Query Caching
|
|
218
|
+
|
|
219
|
+
All queries are assigned a unique queryKey and provide the developer with access to the underlying `staleTime` property.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
type Person = {
|
|
223
|
+
name: string
|
|
224
|
+
age: number
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const person = useDocument<Person>(
|
|
228
|
+
{
|
|
229
|
+
databaseId: 'db1',
|
|
230
|
+
collectionId: 'col1',
|
|
231
|
+
documentId: 'doc1',
|
|
232
|
+
fields: ['name', 'age'],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
staleTime: 1000 * 60, // 1 minute
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### QueryKey Builder
|
|
241
|
+
|
|
242
|
+
During development, we started getting annoyed with keeping our query keys straight, so we built a factory you can use to perform manual cache eviction. We tried to keep the pattern as close to Appwrite's `Channels` as possible.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { Keys } from "@zeroin.earth/appwrite-graphql";
|
|
246
|
+
|
|
247
|
+
const queryKey = Keys.database(databaseId)
|
|
248
|
+
.collection(collectionId)
|
|
249
|
+
.document(documentId)
|
|
250
|
+
.key()
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Offline-first Support
|
|
254
|
+
|
|
255
|
+
We wanted to give developers the freedom to build projects that didn't require continual internet connectivity. Using React Query's `offlineFirst` network modes, we built in a way for mutations to queue up and replay in order once the device reconnects to the internet. We then built an offline client to wrap it all together.
|
|
256
|
+
|
|
257
|
+
### Built-in Offline Persisters
|
|
258
|
+
|
|
259
|
+
With mutations queuing up, we needed to persist them in the event of an online connection being days away, rather than just a temporary outage. Enter the persisters. The library comes with two out-of-the-box options: localStorage and AsyncStorage, depending on what you're building.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
const { appwrite, queryClient, persister } = createOfflineClient({
|
|
263
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
264
|
+
projectId: 'my-project',
|
|
265
|
+
storage: localStorage, // or any AsyncStorage-compatible interface
|
|
266
|
+
networkAdapter: webNetworkAdapter(),
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The above example will serialize the mutations to localStorage after a set `throttleTime` elapses (defaults to 1000ms). Once the `networkAdpater` detects the device is online, the serialized mutations will instantly start replaying in order.
|
|
271
|
+
|
|
272
|
+
### Custom Offline Persister Support
|
|
273
|
+
|
|
274
|
+
Sometimes you want to bring your own persister, or just don't want to use localStorage or AsyncStorage. For this, you can build your own.
|
|
275
|
+
|
|
276
|
+
### Conflict Resolution
|
|
277
|
+
|
|
278
|
+
While in offline-first mode, there will be times when an update can happen on the server without your device knowing about it, and the device will push its own mutation once it comes back online. To handle this, we have built in 3 conflict resolution paths and allowed the developer to bring their own if needed.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
createOfflineClient({
|
|
282
|
+
endpoint: 'https://cloud.appwrite.io/v1',
|
|
283
|
+
projectId: 'my-project',
|
|
284
|
+
storage: localStorage,
|
|
285
|
+
networkAdapter: webNetworkAdapter(),
|
|
286
|
+
conflictStrategy: 'last-write-wins'
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**last-write-wins**: This is the default behavior, where whatever is in the most recent mutation is what is applied to the database, regardless of when and where it came from. This is also the default behavior of Appwrite.
|
|
291
|
+
|
|
292
|
+
**server-wins**: If the record was changed on the server and differs from the version cached locally on the device, the replayed mutation is dropped, preserving the server's version.
|
|
293
|
+
|
|
294
|
+
**merge-shallow**: A remote copy is pulled from the server, changes between the remote and local copies are identified, and the changes are merged into a final copy, giving precedence to fields that were updated on the server if both copies changed the same field.
|
|
295
|
+
|
|
296
|
+
**custom**: A custom resolver function can be supplied with the following type:
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
conflictStrategy: ((context: ConflictContext) => Record<string, string | number | boolean | null> | 'abort')
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- `abort` signals to drop the replaying mutation and change nothing.
|
|
303
|
+
|
|
304
|
+
## SSR Support
|
|
305
|
+
|
|
306
|
+
We have exposed 3 of the most used queries Appwrite surfaces to be used in SSR preFetchQuery calls. This allows you to prefetch a page's content server-side:
|
|
307
|
+
|
|
308
|
+
- `getAccountQuery`
|
|
309
|
+
- `getDocumentQuery`
|
|
310
|
+
- `getCollectionQuery`
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import * as React from "react";
|
|
314
|
+
import {
|
|
315
|
+
dehydrate,
|
|
316
|
+
HydrationBoundary,
|
|
317
|
+
QueryClient,
|
|
318
|
+
} from "@tanstack/react-query";
|
|
319
|
+
|
|
320
|
+
import { createAppwriteClient, useCollection } from "./";
|
|
321
|
+
import { getCollectionQuery } from "./";
|
|
322
|
+
|
|
323
|
+
type PostType = {
|
|
324
|
+
title: string;
|
|
325
|
+
image: string;
|
|
326
|
+
description: string;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// This could also be getServerSideProps
|
|
330
|
+
export async function getStaticProps() {
|
|
331
|
+
const appwriteClient = createAppwriteClient({
|
|
332
|
+
endpoint: "https://example.com/v1",
|
|
333
|
+
projectId: "project-id",
|
|
334
|
+
});
|
|
40
335
|
|
|
41
|
-
|
|
336
|
+
const queryClient = new QueryClient();
|
|
337
|
+
|
|
338
|
+
// Perform the prefetching of the collection query on the server.
|
|
339
|
+
await queryClient.prefetchQuery(
|
|
340
|
+
getCollectionQuery<PostType>(appwriteClient, {
|
|
341
|
+
databaseId: "db1",
|
|
342
|
+
collectionId: "col1",
|
|
343
|
+
fields: ["title", "image", "description"],
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Dehydrate the query client state and pass it as a
|
|
348
|
+
// prop to the page component.
|
|
349
|
+
return {
|
|
350
|
+
props: {
|
|
351
|
+
dehydratedState: dehydrate(queryClient),
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function Posts() {
|
|
357
|
+
// Since we prefetched the data on the server, this will use the
|
|
358
|
+
// cached data and not trigger a network request. If the cache is empty,
|
|
359
|
+
// it will fetch the data from the Appwrite server normally.
|
|
360
|
+
const { data } = useCollection<PostType>({
|
|
361
|
+
databaseId: "db1",
|
|
362
|
+
collectionId: "col1",
|
|
363
|
+
fields: ["title", "image", "description"],
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div>
|
|
368
|
+
{data?.documents?.map((post) => (
|
|
369
|
+
<div key={post.$id}>
|
|
370
|
+
<h2>{post.title}</h2>
|
|
371
|
+
<img src={post.image} alt={post.title} />
|
|
372
|
+
<p>{post.description}</p>
|
|
373
|
+
</div>
|
|
374
|
+
))}
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// The dehydrated state from the server is passed to the HydrationBoundary,
|
|
380
|
+
// which allows the client-side React Query to rehydrate and use the prefetched
|
|
381
|
+
// data without making an additional network request.
|
|
382
|
+
export default function PostsRoute({ dehydratedState }) {
|
|
383
|
+
return (
|
|
384
|
+
<HydrationBoundary state={dehydratedState}>
|
|
385
|
+
<Posts />
|
|
386
|
+
</HydrationBoundary>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Field Selection
|
|
392
|
+
|
|
393
|
+
The most used query hooks allow you to specify the fields returned by Appwrite to prevent over-fetching. These fields select out of the `data` property that is returned:
|
|
394
|
+
|
|
395
|
+
- `useDocument`
|
|
396
|
+
- `useCollection`
|
|
397
|
+
- `useCollectionWithPagination`
|
|
398
|
+
- `useInfiniteCollection`
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
type Person = {
|
|
402
|
+
name: string;
|
|
403
|
+
age: number;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const person = useDocument<Person>(
|
|
407
|
+
{
|
|
408
|
+
databaseId: "db1",
|
|
409
|
+
collectionId: "col1",
|
|
410
|
+
documentId: "doc1",
|
|
411
|
+
fields: ["name", "age"],
|
|
412
|
+
},
|
|
413
|
+
);
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Suspense Queries
|
|
417
|
+
|
|
418
|
+
When using a `<Suspense>` boundary within React, you are able to utilize our selection of Suspense hooks. They are using `useSuspenseQuery` on the backside and will work out of the box with React Suspense.
|
|
419
|
+
|
|
420
|
+
- `useSuspenseCreateJWT`
|
|
421
|
+
- `useSuspenseCollection`
|
|
422
|
+
- `useSuspenseCollectionWithPagination`
|
|
423
|
+
- `useSuspenseDocument`
|
|
424
|
+
- `useSuspenseFunction`
|
|
425
|
+
|
|
426
|
+
## Pagination Hooks
|
|
427
|
+
|
|
428
|
+
We have included two pagination hooks out of the box
|
|
429
|
+
|
|
430
|
+
**With Pagination:**
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
import * as React from "react";
|
|
434
|
+
import { q, useCollectionWithPagination } from "./";
|
|
435
|
+
|
|
436
|
+
type Item = {
|
|
437
|
+
_id: string;
|
|
438
|
+
name: string;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
export default function Test() {
|
|
442
|
+
const {
|
|
443
|
+
documents,
|
|
444
|
+
page,
|
|
445
|
+
total,
|
|
446
|
+
nextPage,
|
|
447
|
+
previousPage,
|
|
448
|
+
hasNextPage,
|
|
449
|
+
hasPreviousPage,
|
|
450
|
+
} = useCollectionWithPagination<Item>({
|
|
451
|
+
databaseId: "your-database-id",
|
|
452
|
+
collectionId: "your-collection-id",
|
|
453
|
+
queries: q<Item>()
|
|
454
|
+
.equal("name", ["John", "Jane"])
|
|
455
|
+
.createdBefore(new Date("2024-01-01").toDateString())
|
|
456
|
+
.orderAsc("name")
|
|
457
|
+
.build(),
|
|
458
|
+
limit: 10,
|
|
459
|
+
fields: ["name", "_id"],
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<div>
|
|
464
|
+
<ul>
|
|
465
|
+
{documents.map((item) => (
|
|
466
|
+
<li key={item._id}>{item.name}</li>
|
|
467
|
+
))}
|
|
468
|
+
</ul>
|
|
469
|
+
|
|
470
|
+
<button onClick={previousPage} disabled={!hasPreviousPage}>
|
|
471
|
+
Previous
|
|
472
|
+
</button>
|
|
473
|
+
|
|
474
|
+
<button onClick={nextPage} disabled={!hasNextPage}>
|
|
475
|
+
Next
|
|
476
|
+
</button>
|
|
477
|
+
|
|
478
|
+
<p>Page: {page}</p>
|
|
479
|
+
<p>Total: {total}</p>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Infinite Scroll**:
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
import * as React from "react";
|
|
489
|
+
import { q, useInfiniteCollection } from "./";
|
|
490
|
+
|
|
491
|
+
type Item = {
|
|
492
|
+
_id: string;
|
|
493
|
+
name: string;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export default function Test() {
|
|
497
|
+
const { documents, fetchNextPage, hasNextPage } = useInfiniteCollection<Item>(
|
|
498
|
+
{
|
|
499
|
+
databaseId: "your-database-id",
|
|
500
|
+
collectionId: "your-collection-id",
|
|
501
|
+
queries: q<Item>()
|
|
502
|
+
.equal("name", ["John", "Jane"])
|
|
503
|
+
.createdBefore(new Date("2024-01-01").toDateString())
|
|
504
|
+
.orderAsc("name")
|
|
505
|
+
.build(),
|
|
506
|
+
limit: 25,
|
|
507
|
+
fields: ["name", "_id"],
|
|
508
|
+
},
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
return (
|
|
512
|
+
<div>
|
|
513
|
+
<ul>
|
|
514
|
+
{documents.map((item) => (
|
|
515
|
+
<li key={item._id}>{item.name}</li>
|
|
516
|
+
))}
|
|
517
|
+
</ul>
|
|
518
|
+
|
|
519
|
+
<button onClick={fetchNextPage} disabled={!hasNextPage}>
|
|
520
|
+
Load More...
|
|
521
|
+
</button>
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Appwrite QueryBuilder
|
|
528
|
+
|
|
529
|
+
Appwrite SDK includes a built-in Query factory, but we wanted to make something a little easier for ourselves while developing this library, so we are including what we put together. All `queries` props in all the hooks can take either the built-in Query factory, ours, or both, so you can do what makes the most sense for you.
|
|
530
|
+
|
|
531
|
+
Our QueryBuilder is type safe and exposes all underlying functions from the built-in version 1 for 1.
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
import { q } from "@zeroin.earth/appwrite-graphql";
|
|
535
|
+
|
|
536
|
+
type YourType = {
|
|
537
|
+
name: string;
|
|
538
|
+
favNumber: number;
|
|
539
|
+
favColor: string;
|
|
540
|
+
favFood: string;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
export function Profiles() {
|
|
544
|
+
const { documents, error, isLoading } = useCollection<YourType>({
|
|
545
|
+
databaseId: "your-database-id",
|
|
546
|
+
collectionId: "your-collection-id",
|
|
547
|
+
queries: q<YourType>()
|
|
548
|
+
.or(
|
|
549
|
+
(q) => q.equal("favColor", "blue").greaterThan("favNumber", 18),
|
|
550
|
+
(q) => q.equal("favFood", "pizza").lessThan("favNumber", 10),
|
|
551
|
+
(q) =>
|
|
552
|
+
q.and(
|
|
553
|
+
(q) => q.between("favNumber", 5, 15),
|
|
554
|
+
(q) => q.startsWith("name", "A"),
|
|
555
|
+
),
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
.build(),
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<div>
|
|
563
|
+
{isLoading && <p>Loading...</p>}
|
|
564
|
+
{error?.length > 0 && <p>Error: {error[0].message}</p>}
|
|
565
|
+
{documents && (
|
|
566
|
+
<ul>
|
|
567
|
+
{documents.map((doc) => (
|
|
568
|
+
<li key={doc.$id}>
|
|
569
|
+
Name: {doc.name}, Fav Number: {doc.favNumber}, Fav Color:{" "}
|
|
570
|
+
{doc.favColor}, Fav Food: {doc.favFood}
|
|
571
|
+
</li>
|
|
572
|
+
))}
|
|
573
|
+
</ul>
|
|
574
|
+
)}
|
|
575
|
+
</div>
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## React Query Devtools Support
|
|
581
|
+
|
|
582
|
+
React Query Devtools are bundled and ready to go. For any additional questions and information, please consult [Tanstack Query's](https://tanstack.com/query/latest/docs/framework/react/devtools#install-and-import-the-devtools) website.
|
|
583
|
+
|
|
584
|
+
## Examples
|
|
585
|
+
|
|
586
|
+
```ts
|
|
42
587
|
import { useLogin } from "@zeroin.earth/appwrite-graphql";
|
|
43
588
|
|
|
44
589
|
export function LogIn() {
|
|
45
590
|
const router = useRouter();
|
|
591
|
+
|
|
46
592
|
const { login, oAuthLogin } = useLogin();
|
|
47
593
|
|
|
48
594
|
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
|
@@ -56,14 +602,16 @@ export function LogIn() {
|
|
|
56
602
|
const loginWithGoogle = () => {
|
|
57
603
|
oAuthLogin.mutate({
|
|
58
604
|
provider: "google",
|
|
59
|
-
success:
|
|
60
|
-
failure:
|
|
605
|
+
success: "successUrl",
|
|
606
|
+
failure: "failureUrl",
|
|
61
607
|
});
|
|
62
608
|
};
|
|
63
609
|
}
|
|
64
610
|
```
|
|
65
611
|
|
|
66
|
-
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
```ts
|
|
67
615
|
import { useFunction } from "@zeroin.earth/appwrite-graphql";
|
|
68
616
|
|
|
69
617
|
export function Form() {
|
|
@@ -72,7 +620,7 @@ export function Form() {
|
|
|
72
620
|
const onSubmit: SubmitHandler<Input> = async (data) => {
|
|
73
621
|
executeFunction.mutate(
|
|
74
622
|
{
|
|
75
|
-
functionId:
|
|
623
|
+
functionId: "6gibhbyy6tggdf",
|
|
76
624
|
body: {
|
|
77
625
|
message: {
|
|
78
626
|
...data,
|
|
@@ -88,26 +636,3 @@ export function Form() {
|
|
|
88
636
|
};
|
|
89
637
|
}
|
|
90
638
|
```
|
|
91
|
-
|
|
92
|
-
### Using Fragments
|
|
93
|
-
|
|
94
|
-
```jsx
|
|
95
|
-
import {
|
|
96
|
-
fragments,
|
|
97
|
-
getFragmentData,
|
|
98
|
-
useAccount,
|
|
99
|
-
} from "@zeroin.earth/appwrite-graphql";
|
|
100
|
-
|
|
101
|
-
export function Profile() {
|
|
102
|
-
const { data, isLoading } = useAccount({});
|
|
103
|
-
const account = getFragmentData(fragments.Account_UserFragment, data);
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<div>
|
|
107
|
-
{data && (
|
|
108
|
-
<h2>{`Welcome, ${account?.name ?? "Visitor"}!`}</h2>
|
|
109
|
-
)}
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
```
|