@ver-id/graphql-client 0.8.2

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.
@@ -0,0 +1,627 @@
1
+ # Writing Custom GraphQL Queries
2
+
3
+ This guide explains how to write and execute custom GraphQL queries using the Ver.iD GraphQL client.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Basic Setup](#basic-setup)
9
+ - [Writing Custom Queries](#writing-custom-queries)
10
+ - [Type Generation](#type-generation)
11
+ - [Using TypedDocumentNode](#using-typeddocumentnode)
12
+ - [Query Examples](#query-examples)
13
+ - [Mutation Examples](#mutation-examples)
14
+ - [Best Practices](#best-practices)
15
+ - [Troubleshooting](#troubleshooting)
16
+
17
+ ## Overview
18
+
19
+ While the SDK provides pre-built helper functions like `getAttribute`, `getCredential`, etc., you may need to write custom queries for:
20
+
21
+ - Complex filtering and sorting requirements
22
+ - Custom field selections
23
+ - Combining multiple queries
24
+ - Advanced GraphQL features (fragments, unions, etc.)
25
+ - Mutations and subscriptions
26
+
27
+ ## Basic Setup
28
+
29
+ First, create your GraphQL client:
30
+
31
+ ```typescript
32
+ import { createVeridGraphQLClient } from '@ver-id/graphql-client';
33
+
34
+ const client = createVeridGraphQLClient({
35
+ uri: 'https://api.ver.id/graphql',
36
+ getAccessToken: async () => {
37
+ // Return your OAuth access token
38
+ return 'your-access-token';
39
+ },
40
+ });
41
+ ```
42
+
43
+ ## Writing Custom Queries
44
+
45
+ ### Method 1: Using `gql` Template Tag
46
+
47
+ The simplest way to write custom queries is using Apollo's `gql` tag:
48
+
49
+ ```typescript
50
+ import { gql } from '@apollo/client';
51
+
52
+ const MY_QUERY = gql`
53
+ query GetAttributesByCredential($credentialId: UUID!) {
54
+ findManyAttributes(
55
+ input: { filtering: [{ field: CREDENTIAL_UUID, type: EQUAL, value: $credentialId }] }
56
+ ) {
57
+ edges {
58
+ node {
59
+ uuid
60
+ name
61
+ description
62
+ dataType
63
+ }
64
+ }
65
+ }
66
+ }
67
+ `;
68
+
69
+ // Execute the query
70
+ const response = await client.query({
71
+ query: MY_QUERY,
72
+ variables: {
73
+ credentialId: 'credential-uuid-here',
74
+ },
75
+ });
76
+
77
+ console.log(response.data.findManyAttributes.edges);
78
+ ```
79
+
80
+ ### Method 2: Using GraphQL Code Generator (Recommended)
81
+
82
+ For better type safety, use GraphQL Code Generator to generate TypedDocumentNode:
83
+
84
+ 1. **Create a `.graphql` file** in `src/operations/query/`:
85
+
86
+ ```graphql
87
+ # src/operations/query/my-custom-query.graphql
88
+
89
+ query GetAttributesByCredential($credentialId: UUID!) {
90
+ findManyAttributes(
91
+ input: { filtering: [{ field: CREDENTIAL_UUID, type: EQUAL, value: $credentialId }] }
92
+ ) {
93
+ edges {
94
+ node {
95
+ uuid
96
+ name
97
+ description
98
+ dataType
99
+ locale {
100
+ edges {
101
+ node {
102
+ uuid
103
+ locale
104
+ name
105
+ description
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ 2. **Run the code generator**:
116
+
117
+ ```bash
118
+ npm run codegen
119
+ ```
120
+
121
+ 3. **Use the generated types**:
122
+
123
+ ```typescript
124
+ import { GetAttributesByCredentialDocument } from './operations/query/my-custom-query';
125
+
126
+ const response = await client.query({
127
+ query: GetAttributesByCredentialDocument,
128
+ variables: {
129
+ credentialId: 'credential-uuid-here',
130
+ },
131
+ });
132
+
133
+ // Fully typed response!
134
+ const attributes = response.data.findManyAttributes.edges.map((edge) => edge.node);
135
+ ```
136
+
137
+ ## Type Generation
138
+
139
+ The GraphQL Code Generator is configured with the `near-operation-file` preset, which means:
140
+
141
+ - Types are generated next to your `.graphql` files
142
+ - Each query gets its own TypedDocumentNode
143
+ - Full TypeScript inference for variables and responses
144
+ - Auto-completion in your IDE
145
+
146
+ ### Configuration
147
+
148
+ The codegen is configured in `codegen.ts`:
149
+
150
+ ```typescript
151
+ {
152
+ schema: 'https://api.ver.id/graphql',
153
+ documents: 'src/operations/**/*.graphql',
154
+ generates: {
155
+ // Generated types appear next to your .graphql files
156
+ 'src/operations/': {
157
+ preset: 'near-operation-file',
158
+ presetConfig: {
159
+ extension: '.ts',
160
+ baseTypesPath: '../../types/schema-types',
161
+ },
162
+ plugins: [
163
+ 'typescript-operations',
164
+ 'typed-document-node',
165
+ ],
166
+ },
167
+ },
168
+ }
169
+ ```
170
+
171
+ ## Using TypedDocumentNode
172
+
173
+ TypedDocumentNode provides full type safety for your queries:
174
+
175
+ ```typescript
176
+ import { TypedDocumentNode } from '@apollo/client';
177
+ import { GetAttributesByCredentialDocument } from './operations/query/my-custom-query';
178
+
179
+ async function fetchAttributesByCredential(client: ApolloClient, credentialId: string) {
180
+ const { data, error } = await client.query({
181
+ query: GetAttributesByCredentialDocument,
182
+ variables: { credentialId }, // TypeScript knows what variables are required!
183
+ });
184
+
185
+ if (error) {
186
+ throw new Error(`Query failed: ${error.message}`);
187
+ }
188
+
189
+ // data is fully typed!
190
+ return data.findManyAttributes.edges.map((edge) => ({
191
+ id: edge.node.uuid,
192
+ name: edge.node.name,
193
+ description: edge.node.description,
194
+ }));
195
+ }
196
+ ```
197
+
198
+ ## Query Examples
199
+
200
+ ### Example 1: Filtering Attributes
201
+
202
+ ```graphql
203
+ query FilterAttributes($dataType: String!, $locale: String!) {
204
+ findManyAttributes(
205
+ input: {
206
+ filtering: [{ field: DATA_TYPE, type: EQUAL, value: $dataType }]
207
+ localeFiltering: [{ field: LOCALE, type: EQUAL, value: $locale }]
208
+ }
209
+ ) {
210
+ edges {
211
+ node {
212
+ uuid
213
+ name
214
+ dataType
215
+ locale {
216
+ edges {
217
+ node {
218
+ locale
219
+ name
220
+ description
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+ pageInfo {
227
+ hasNextPage
228
+ hasPreviousPage
229
+ startCursor
230
+ endCursor
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Example 2: Deep Hierarchy Query
237
+
238
+ ```graphql
239
+ query GetAttributeWithFullHierarchy($uuid: UUID!) {
240
+ findAttribute(uuid: $uuid) {
241
+ uuid
242
+ name
243
+ description
244
+ dataType
245
+ locale {
246
+ edges {
247
+ node {
248
+ locale
249
+ name
250
+ description
251
+ }
252
+ }
253
+ }
254
+ credential {
255
+ uuid
256
+ name
257
+ issuer {
258
+ uuid
259
+ name
260
+ scheme {
261
+ uuid
262
+ name
263
+ provider {
264
+ uuid
265
+ name
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Example 3: Pagination
275
+
276
+ ```graphql
277
+ query GetAttributesPaginated($first: Int!, $after: String) {
278
+ findManyAttributes(input: { pagination: { first: $first, after: $after } }) {
279
+ edges {
280
+ cursor
281
+ node {
282
+ uuid
283
+ name
284
+ description
285
+ }
286
+ }
287
+ pageInfo {
288
+ hasNextPage
289
+ endCursor
290
+ }
291
+ }
292
+ }
293
+ ```
294
+
295
+ Usage:
296
+
297
+ ```typescript
298
+ let cursor: string | undefined;
299
+ let hasMore = true;
300
+
301
+ while (hasMore) {
302
+ const response = await client.query({
303
+ query: GetAttributesPaginatedDocument,
304
+ variables: {
305
+ first: 50,
306
+ after: cursor,
307
+ },
308
+ });
309
+
310
+ const { edges, pageInfo } = response.data.findManyAttributes;
311
+
312
+ // Process edges
313
+ edges.forEach((edge) => {
314
+ console.log(edge.node);
315
+ });
316
+
317
+ cursor = pageInfo.endCursor;
318
+ hasMore = pageInfo.hasNextPage;
319
+ }
320
+ ```
321
+
322
+ ## Mutation Examples
323
+
324
+ ### Example 1: Update Attribute
325
+
326
+ ```graphql
327
+ mutation UpdateAttribute($uuid: UUID!, $name: String, $description: String) {
328
+ updateAttribute(uuid: $uuid, input: { name: $name, description: $description }) {
329
+ uuid
330
+ name
331
+ description
332
+ updatedAt
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### Example 2: Create Credential
338
+
339
+ ```graphql
340
+ mutation CreateCredential($input: CreateCredentialInput!) {
341
+ createCredential(input: $input) {
342
+ uuid
343
+ name
344
+ description
345
+ issuer {
346
+ uuid
347
+ name
348
+ }
349
+ }
350
+ }
351
+ ```
352
+
353
+ ## Best Practices
354
+
355
+ ### 1. **Use GraphQL Code Generator**
356
+
357
+ Always use code generation for production code:
358
+
359
+ ```bash
360
+ # Generate types whenever you add/modify queries
361
+ npm run codegen
362
+ ```
363
+
364
+ ### 2. **Keep Queries Focused**
365
+
366
+ Write specific queries for specific use cases instead of fetching everything:
367
+
368
+ ```graphql
369
+ # ❌ Bad: Over-fetching
370
+ query GetEverything {
371
+ findManyAttributes {
372
+ edges {
373
+ node {
374
+ uuid
375
+ name
376
+ description
377
+ # ... 50 more fields
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ # ✅ Good: Fetch only what you need
384
+ query GetAttributeNames {
385
+ findManyAttributes {
386
+ edges {
387
+ node {
388
+ uuid
389
+ name
390
+ }
391
+ }
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### 3. **Handle Errors Properly**
397
+
398
+ ```typescript
399
+ import { GraphQLOperationError } from '@ver-id/graphql-client';
400
+
401
+ try {
402
+ const response = await client.query({ query: MyQuery });
403
+
404
+ if (response.error) {
405
+ throw new GraphQLOperationError(response.error);
406
+ }
407
+
408
+ return response.data;
409
+ } catch (error) {
410
+ if (error instanceof GraphQLOperationError) {
411
+ console.error('GraphQL error:', error.message);
412
+ console.error('Extensions:', error.extensions);
413
+ }
414
+ throw error;
415
+ }
416
+ ```
417
+
418
+ ### 4. **Use Fragments for Reusability**
419
+
420
+ ```graphql
421
+ fragment AttributeFields on Attribute {
422
+ uuid
423
+ name
424
+ description
425
+ dataType
426
+ }
427
+
428
+ query GetAttribute($uuid: UUID!) {
429
+ findAttribute(uuid: $uuid) {
430
+ ...AttributeFields
431
+ locale {
432
+ edges {
433
+ node {
434
+ locale
435
+ name
436
+ }
437
+ }
438
+ }
439
+ }
440
+ }
441
+
442
+ query GetAttributes {
443
+ findManyAttributes {
444
+ edges {
445
+ node {
446
+ ...AttributeFields
447
+ }
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
453
+ ### 5. **Use Variables, Not String Interpolation**
454
+
455
+ ```typescript
456
+ // ❌ Bad: SQL injection-like vulnerability
457
+ const query = gql`
458
+ query {
459
+ findAttribute(uuid: "${userInput}") {
460
+ name
461
+ }
462
+ }
463
+ `;
464
+
465
+ // ✅ Good: Safe parameterized query
466
+ const query = gql`
467
+ query GetAttribute($uuid: UUID!) {
468
+ findAttribute(uuid: $uuid) {
469
+ name
470
+ }
471
+ }
472
+ `;
473
+
474
+ await client.query({
475
+ query,
476
+ variables: { uuid: userInput },
477
+ });
478
+ ```
479
+
480
+ ### 6. **Leverage Apollo Client Caching**
481
+
482
+ ```typescript
483
+ // First query fetches from network
484
+ const response1 = await client.query({
485
+ query: GetAttributeDocument,
486
+ variables: { uuid: 'some-uuid' },
487
+ });
488
+
489
+ // Second query returns from cache (unless cache-first policy changed)
490
+ const response2 = await client.query({
491
+ query: GetAttributeDocument,
492
+ variables: { uuid: 'some-uuid' },
493
+ fetchPolicy: 'cache-first', // default
494
+ });
495
+
496
+ // Force network fetch
497
+ const response3 = await client.query({
498
+ query: GetAttributeDocument,
499
+ variables: { uuid: 'some-uuid' },
500
+ fetchPolicy: 'network-only',
501
+ });
502
+ ```
503
+
504
+ ## Troubleshooting
505
+
506
+ ### Types Not Generated
507
+
508
+ If types aren't being generated after running `npm run codegen`:
509
+
510
+ 1. Check your `.graphql` file is in the correct location (`src/operations/**/*.graphql`)
511
+ 2. Verify your query syntax is valid
512
+ 3. Make sure the schema URL is accessible
513
+ 4. Check for TypeScript errors in the codegen output
514
+
515
+ ### TypeScript Errors
516
+
517
+ If you get type errors when using generated types:
518
+
519
+ ```typescript
520
+ // Make sure you import from the generated file
521
+ import { MyQueryDocument } from './operations/query/my-query';
522
+
523
+ // Not from the schema types
524
+ // ❌ import { MyQuery } from './contrib/graphql/schema-types';
525
+ ```
526
+
527
+ ### Query Not Working
528
+
529
+ If your query executes but returns unexpected results:
530
+
531
+ 1. Test the query in GraphQL Playground/GraphiQL first
532
+ 2. Check the network tab for the actual request/response
533
+ 3. Verify your authentication token is valid
534
+ 4. Check for GraphQL errors in the response:
535
+
536
+ ```typescript
537
+ const response = await client.query({ query: MyQuery });
538
+
539
+ if (response.errors) {
540
+ console.error('GraphQL errors:', response.errors);
541
+ }
542
+ ```
543
+
544
+ ### Cache Issues
545
+
546
+ If you're seeing stale data:
547
+
548
+ ```typescript
549
+ // Clear specific cache entry
550
+ client.cache.evict({
551
+ id: client.cache.identify({ __typename: 'Attribute', uuid: 'some-uuid' }),
552
+ });
553
+
554
+ // Or clear entire cache
555
+ await client.clearStore();
556
+
557
+ // Or refetch query
558
+ await client.refetchQueries({
559
+ include: [MyQueryDocument],
560
+ });
561
+ ```
562
+
563
+ ## Advanced Topics
564
+
565
+ ### Using with React
566
+
567
+ ```typescript
568
+ import { useQuery } from '@apollo/client';
569
+ import { GetAttributesDocument } from './operations/query/get-attributes';
570
+
571
+ function AttributeList() {
572
+ const { data, loading, error } = useQuery(GetAttributesDocument, {
573
+ variables: { first: 10 },
574
+ });
575
+
576
+ if (loading) return <div>Loading...</div>;
577
+ if (error) return <div>Error: {error.message}</div>;
578
+
579
+ return (
580
+ <ul>
581
+ {data.findManyAttributes.edges.map(edge => (
582
+ <li key={edge.node.uuid}>{edge.node.name}</li>
583
+ ))}
584
+ </ul>
585
+ );
586
+ }
587
+ ```
588
+
589
+ ### Batch Queries
590
+
591
+ ```typescript
592
+ const [attributes, credentials] = await Promise.all([
593
+ client.query({ query: GetAttributesDocument }),
594
+ client.query({ query: GetCredentialsDocument }),
595
+ ]);
596
+ ```
597
+
598
+ ### Optimistic Updates
599
+
600
+ ```typescript
601
+ await client.mutate({
602
+ mutation: UpdateAttributeDocument,
603
+ variables: { uuid: 'some-uuid', name: 'New Name' },
604
+ optimisticResponse: {
605
+ updateAttribute: {
606
+ __typename: 'Attribute',
607
+ uuid: 'some-uuid',
608
+ name: 'New Name',
609
+ updatedAt: new Date().toISOString(),
610
+ },
611
+ },
612
+ });
613
+ ```
614
+
615
+ ## Additional Resources
616
+
617
+ - [Apollo Client Documentation](https://www.apollographql.com/docs/react/)
618
+ - [GraphQL Code Generator Documentation](https://the-guild.dev/graphql/codegen)
619
+ - [Ver.iD API Documentation](https://docs.ver.id)
620
+ - [Main README](./README.md)
621
+
622
+ ## Support
623
+
624
+ For questions and issues:
625
+
626
+ - GitHub Issues: [ver-id/verid-sdk-js](https://github.com/ver-id/verid-sdk-js/issues)
627
+ - Documentation: [Ver.iD Docs](https://docs.ver.id)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ver.iD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.