@connectedxm/admin 6.1.11 → 6.3.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.
@@ -0,0 +1,1856 @@
1
+ # Internal Developer Documentation
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [Repository Overview](#1-repository-overview)
6
+ 2. [Core Files and Their Roles](#2-core-files-and-their-roles)
7
+ 3. [Query Patterns](#3-query-patterns)
8
+ 4. [Mutation Patterns](#4-mutation-patterns)
9
+ 5. [Directory Organization](#5-directory-organization)
10
+ 6. [Code Patterns and Conventions](#6-code-patterns-and-conventions)
11
+ 7. [Common Utilities](#7-common-utilities)
12
+ 8. [Examples and Best Practices](#8-examples-and-best-practices)
13
+ 9. [Testing Patterns](#9-testing-patterns)
14
+ 10. [Build and Distribution](#10-build-and-distribution)
15
+
16
+ ---
17
+
18
+ ## 1. Repository Overview
19
+
20
+ ### Purpose and Scope
21
+
22
+ The `@connectedxm/admin` SDK is a TypeScript/JavaScript library that provides a type-safe interface for interacting with the ConnectedXM Admin API. It is designed to be used in React applications and provides both direct function calls and React Query hooks for data fetching and mutations.
23
+
24
+ ### Technology Stack
25
+
26
+ - **TypeScript**: Full type safety throughout the codebase
27
+ - **React Query (TanStack Query)**: Data fetching, caching, and synchronization
28
+ - **Axios**: HTTP client for API requests
29
+ - **React**: Framework for React hooks and context providers
30
+ - **Immer**: Immutable state updates (used internally)
31
+
32
+ ### High-Level Architecture
33
+
34
+ The SDK follows a layered architecture:
35
+
36
+ ```
37
+ ┌─────────────────────────────────────┐
38
+ │ React Components (Consumer) │
39
+ │ Uses hooks: useGet*, useCreate* │
40
+ └──────────────┬──────────────────────┘
41
+
42
+ ┌──────────────▼──────────────────────┐
43
+ │ React Query Hooks Layer │
44
+ │ useConnectedSingleQuery │
45
+ │ useConnectedInfiniteQuery │
46
+ │ useConnectedMutation │
47
+ └──────────────┬──────────────────────┘
48
+
49
+ ┌──────────────▼──────────────────────┐
50
+ │ API Functions Layer │
51
+ │ GetAccount, CreateAccount, etc. │
52
+ └──────────────┬──────────────────────┘
53
+
54
+ ┌──────────────▼──────────────────────┐
55
+ │ AdminAPI (Axios Instance) │
56
+ │ GetAdminAPI() │
57
+ └──────────────┬──────────────────────┘
58
+
59
+ ┌──────────────▼──────────────────────┐
60
+ │ ConnectedXM Admin API │
61
+ └─────────────────────────────────────┘
62
+ ```
63
+
64
+ ### Project Structure Overview
65
+
66
+ ```
67
+ src/
68
+ ├── AdminAPI.ts # Core API client factory
69
+ ├── ConnectedXMProvider.tsx # React context provider
70
+ ├── interfaces.ts # All TypeScript interfaces and enums
71
+ ├── params.ts # Input parameter interfaces for mutations
72
+ ├── index.ts # Main entry point (exports everything)
73
+ ├── queries/ # Query hooks organized by domain
74
+ │ ├── useConnectedSingleQuery.ts
75
+ │ ├── useConnectedInfiniteQuery.ts
76
+ │ ├── useConnectedCursorQuery.ts
77
+ │ └── [domain]/ # accounts/, events/, etc.
78
+ ├── mutations/ # Mutation hooks organized by domain
79
+ │ ├── useConnectedMutation.ts
80
+ │ └── [domain]/ # account/, event/, etc.
81
+ ├── hooks/ # Core React hooks
82
+ │ └── useConnectedXM.ts # Context hook
83
+ └── utilities/ # Helper functions
84
+ ├── CacheIndividualQueries.ts
85
+ ├── MergeInfinitePages.ts
86
+ └── ...
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 2. Core Files and Their Roles
92
+
93
+ ### 2.1 `src/interfaces.ts`
94
+
95
+ **Purpose**: Centralized TypeScript interfaces and enums that define all API response types, domain models, and constants used throughout the SDK.
96
+
97
+ **Key Contents**:
98
+
99
+ - `ConnectedXMResponse<T>` - Wrapper interface for all API responses
100
+ - Domain models: `Account`, `Event`, `Group`, `Channel`, etc.
101
+ - Enums: `EventType`, `AccountAccess`, `PassTypeVisibility`, etc.
102
+ - Type definitions for nested resources
103
+
104
+ **Pattern**: All API response types extend `ConnectedXMResponse<TData>`:
105
+
106
+ ```typescript
107
+ export interface ConnectedXMResponse<TData> {
108
+ status: string;
109
+ message: string;
110
+ count?: number;
111
+ data: TData;
112
+ cursor?: string | number | null;
113
+ }
114
+ ```
115
+
116
+ **Example Usage**:
117
+
118
+ ```typescript
119
+ // Response type for a single account
120
+ ConnectedXMResponse<Account>;
121
+
122
+ // Response type for a list of accounts
123
+ ConnectedXMResponse<Account[]>;
124
+
125
+ // Response type with cursor for pagination
126
+ ConnectedXMResponse<Account[]>; // cursor field included
127
+ ```
128
+
129
+ **Conventions**:
130
+
131
+ - All domain models are PascalCase (e.g., `Account`, `EventSession`)
132
+ - Enums use PascalCase for the enum name and SCREAMING_SNAKE_CASE for values
133
+ - Nested interfaces follow the pattern `ParentChild` (e.g., `EventSession`, `AccountAddress`)
134
+
135
+ ### 2.2 `src/params.ts`
136
+
137
+ **Purpose**: Input parameter interfaces for all mutations. These define the shape of data sent to the API when creating or updating resources.
138
+
139
+ **Pattern**: All mutation inputs follow naming conventions:
140
+
141
+ - `*CreateInputs` - For creating new resources (e.g., `AccountCreateInputs`)
142
+ - `*UpdateInputs` - For updating existing resources (e.g., `AccountUpdateInputs`)
143
+ - `*TranslationUpdateInputs` - For updating translated fields (e.g., `EventTranslationUpdateInputs`)
144
+
145
+ **Example**:
146
+
147
+ ```typescript
148
+ export interface AccountCreateInputs {
149
+ email: string;
150
+ username?: string | null;
151
+ featured?: boolean;
152
+ firstName?: string | null;
153
+ lastName?: string | null;
154
+ // ... more fields
155
+ }
156
+
157
+ export interface AccountUpdateInputs {
158
+ accountAccess?: keyof typeof AccountAccess | null;
159
+ featured?: boolean;
160
+ firstName?: string | null;
161
+ // ... more fields (all optional for updates)
162
+ }
163
+ ```
164
+
165
+ **Key Patterns**:
166
+
167
+ - Create inputs typically have required fields (e.g., `email: string`)
168
+ - Update inputs make most fields optional (e.g., `firstName?: string | null`)
169
+ - Fields can be `null` to explicitly clear values
170
+ - Enum values use `keyof typeof EnumName` for type safety
171
+ - Some fields accept both `string` and `number` (e.g., `price: number | string`)
172
+
173
+ **Relationship to API**: These interfaces mirror the backend validation schemas. When the API schema changes, these interfaces must be updated accordingly.
174
+
175
+ ### 2.3 `src/AdminAPI.ts`
176
+
177
+ **Purpose**: Core API client factory that creates configured Axios instances for making API requests.
178
+
179
+ **Key Components**:
180
+
181
+ #### `AdminApiParams` Interface
182
+
183
+ ```typescript
184
+ export interface AdminApiParams {
185
+ apiUrl:
186
+ | "https://admin-api.connected.dev"
187
+ | "https://staging-admin-api.connected.dev"
188
+ | "http://localhost:4001";
189
+ organizationId: string;
190
+ getToken?: () => Promise<string | undefined> | string | undefined;
191
+ apiKey?: string;
192
+ getExecuteAs?: () => Promise<string | undefined> | string | undefined;
193
+ }
194
+ ```
195
+
196
+ **Parameters**:
197
+
198
+ - `apiUrl`: The base URL for the API (production, staging, or local)
199
+ - `organizationId`: Required organization identifier
200
+ - `getToken`: Optional function to retrieve authentication token
201
+ - `apiKey`: Optional API key for server-side usage
202
+ - `getExecuteAs`: Optional function for impersonation (admin feature)
203
+
204
+ #### `GetAdminAPI()` Function
205
+
206
+ Creates a configured Axios instance with proper headers:
207
+
208
+ ```typescript
209
+ export const GetAdminAPI = async (
210
+ params: AdminApiParams
211
+ ): Promise<AxiosInstance> => {
212
+ const token = !!params.getToken && (await params.getToken());
213
+ const executeAs = params.getExecuteAs
214
+ ? await params.getExecuteAs()
215
+ : undefined;
216
+
217
+ return axios.create({
218
+ baseURL: params.apiUrl,
219
+ headers: {
220
+ organization: params.organizationId,
221
+ authorization: token,
222
+ "api-key": params.apiKey,
223
+ executeAs: executeAs,
224
+ },
225
+ });
226
+ };
227
+ ```
228
+
229
+ **Usage**: All query and mutation functions call `GetAdminAPI()` to get a configured client before making requests.
230
+
231
+ ### 2.4 `src/ConnectedXMProvider.tsx`
232
+
233
+ **Purpose**: React context provider that supplies SDK configuration and error handling callbacks to all hooks.
234
+
235
+ **Required Props**:
236
+
237
+ ```typescript
238
+ interface ConnectedXMProviderProps {
239
+ queryClient: QueryClient; // React Query client instance
240
+ organizationId: string; // Organization ID
241
+ apiUrl: "https://admin-api.connected.dev" | ...;
242
+ getToken: () => Promise<string | undefined>;
243
+ children: React.ReactNode;
244
+ }
245
+ ```
246
+
247
+ **Optional Callbacks**:
248
+
249
+ ```typescript
250
+ onNotAuthorized?: (
251
+ error: AxiosError<ConnectedXMResponse<any>>,
252
+ key: QueryKey,
253
+ shouldRedirect: boolean
254
+ ) => void;
255
+
256
+ onModuleForbidden?: (
257
+ error: AxiosError<ConnectedXMResponse<any>>,
258
+ key: QueryKey,
259
+ shouldRedirect: boolean
260
+ ) => void;
261
+
262
+ onNotFound?: (
263
+ error: AxiosError<ConnectedXMResponse<any>>,
264
+ key: QueryKey,
265
+ shouldRedirect: boolean
266
+ ) => void;
267
+
268
+ onMutationError?: (
269
+ error: AxiosError<ConnectedXMResponse<null>>,
270
+ variables: Omit<MutationParams, "queryClient" | "adminApiParams">,
271
+ context: unknown
272
+ ) => void;
273
+ ```
274
+
275
+ **SSR Considerations**: The provider handles server-side rendering by initially rendering with SSR flag, then updating after mount to prevent hydration mismatches.
276
+
277
+ **Usage Example**:
278
+
279
+ ```tsx
280
+ <QueryClientProvider client={queryClient}>
281
+ <ConnectedXMProvider
282
+ queryClient={queryClient}
283
+ organizationId={ORGANIZATION_ID}
284
+ apiUrl="https://admin-api.connected.dev"
285
+ getToken={getToken}
286
+ onNotAuthorized={(error) => {
287
+ // Handle 401 errors
288
+ router.push("/login");
289
+ }}
290
+ onModuleForbidden={(error) => {
291
+ // Handle 403/460/461 errors
292
+ showError("You do not have permission");
293
+ }}
294
+ >
295
+ {children}
296
+ </ConnectedXMProvider>
297
+ </QueryClientProvider>
298
+ ```
299
+
300
+ ---
301
+
302
+ ## 3. Query Patterns
303
+
304
+ ### 3.1 Query Types
305
+
306
+ The SDK provides three types of queries, each optimized for different use cases:
307
+
308
+ #### Single Queries (`useConnectedSingleQuery`)
309
+
310
+ **Pattern**: Fetch a single resource by ID or identifier.
311
+
312
+ **File**: `src/queries/useConnectedSingleQuery.ts`
313
+
314
+ **Use Case**: When you need one specific resource (e.g., a single account, event, or group).
315
+
316
+ **Example**:
317
+
318
+ ```typescript
319
+ const { data: account, isLoading } = useGetAccount(accountId);
320
+ ```
321
+
322
+ **Characteristics**:
323
+
324
+ - Returns a single resource
325
+ - Automatically handles 404 errors
326
+ - 60-second stale time
327
+ - Retries up to 3 times on network errors
328
+
329
+ #### Infinite Queries (`useConnectedInfiniteQuery`)
330
+
331
+ **Pattern**: Paginated lists using page numbers (1, 2, 3, ...).
332
+
333
+ **File**: `src/queries/useConnectedInfiniteQuery.ts`
334
+
335
+ **Use Case**: When fetching paginated lists where pages are numbered sequentially.
336
+
337
+ **Example**:
338
+
339
+ ```typescript
340
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetAccounts(
341
+ verified,
342
+ online,
343
+ {
344
+ pageSize: 25,
345
+ search: "john",
346
+ }
347
+ );
348
+ ```
349
+
350
+ **Characteristics**:
351
+
352
+ - Uses `pageParam` (number) for pagination
353
+ - Default page size: 25
354
+ - Automatically determines next page based on response length
355
+ - Supports search and ordering
356
+ - Includes search term in query key
357
+
358
+ **Pagination Logic**:
359
+
360
+ ```typescript
361
+ const getNextPageParam = (lastPage, allPages) => {
362
+ if (lastPage.data.length === params.pageSize) {
363
+ return allPages.length + 1;
364
+ }
365
+ return undefined; // No more pages
366
+ };
367
+ ```
368
+
369
+ #### Cursor Queries (`useConnectedCursorQuery`)
370
+
371
+ **Pattern**: Paginated lists using cursor-based pagination.
372
+
373
+ **File**: `src/queries/useConnectedCursorQuery.ts`
374
+
375
+ **Use Case**: When the API uses cursor-based pagination (more efficient for large datasets).
376
+
377
+ **Example**:
378
+
379
+ ```typescript
380
+ const { data, fetchNextPage, hasNextPage } = useGetSomeCursorBasedList({
381
+ pageSize: 50,
382
+ });
383
+ ```
384
+
385
+ **Characteristics**:
386
+
387
+ - Uses `cursor` (string | number | null) for pagination
388
+ - Initial cursor is `null`
389
+ - Next cursor comes from `response.cursor`
390
+ - Better for large datasets and real-time data
391
+
392
+ **Pagination Logic**:
393
+
394
+ ```typescript
395
+ const getNextPageParam = (lastPage) => {
396
+ if (lastPage.cursor) {
397
+ return lastPage.cursor;
398
+ }
399
+ return null; // No more pages
400
+ };
401
+ ```
402
+
403
+ ### 3.2 Query File Structure
404
+
405
+ Each query file follows a consistent structure with four main components:
406
+
407
+ #### 1. Query Key Function
408
+
409
+ Generates the React Query cache key for this query.
410
+
411
+ ```typescript
412
+ /**
413
+ * @category Keys
414
+ * @group Accounts
415
+ */
416
+ export const ACCOUNT_QUERY_KEY = (accountId: string) => [
417
+ ...ACCOUNTS_QUERY_KEY(),
418
+ accountId,
419
+ ];
420
+ ```
421
+
422
+ **Conventions**:
423
+
424
+ - Named: `*_QUERY_KEY`
425
+ - Returns an array (React Query key format)
426
+ - Can accept parameters for filtering/variants
427
+ - Nested keys spread parent keys
428
+
429
+ #### 2. Query Setter Function
430
+
431
+ Helper function to manually update the cache.
432
+
433
+ ```typescript
434
+ /**
435
+ * @category Setters
436
+ * @group Accounts
437
+ */
438
+ export const SET_ACCOUNT_QUERY_DATA = (
439
+ client: QueryClient,
440
+ keyParams: Parameters<typeof ACCOUNT_QUERY_KEY>,
441
+ response: Awaited<ReturnType<typeof GetAccount>>
442
+ ) => {
443
+ client.setQueryData(ACCOUNT_QUERY_KEY(...keyParams), response);
444
+ };
445
+ ```
446
+
447
+ **Conventions**:
448
+
449
+ - Named: `SET_*_QUERY_DATA`
450
+ - Takes QueryClient, key parameters, and response data
451
+ - Used in mutations to optimistically update cache
452
+
453
+ #### 3. Query Function
454
+
455
+ The actual API call function (can be used standalone).
456
+
457
+ ```typescript
458
+ /**
459
+ * @category Queries
460
+ * @group Accounts
461
+ */
462
+ export const GetAccount = async ({
463
+ accountId = "",
464
+ adminApiParams,
465
+ }: GetAccountProps): Promise<ConnectedXMResponse<Account>> => {
466
+ const adminApi = await GetAdminAPI(adminApiParams);
467
+ const { data } = await adminApi.get(`/accounts/${accountId}`);
468
+ return data;
469
+ };
470
+ ```
471
+
472
+ **Conventions**:
473
+
474
+ - Named: `Get*` (PascalCase)
475
+ - Accepts `adminApiParams` (and other params)
476
+ - Returns `Promise<ConnectedXMResponse<T>>`
477
+ - Can be used outside React (direct function calls)
478
+
479
+ #### 4. React Hook
480
+
481
+ React Query hook wrapper for use in components.
482
+
483
+ ```typescript
484
+ /**
485
+ * @category Hooks
486
+ * @group Accounts
487
+ */
488
+ export const useGetAccount = (
489
+ accountId: string = "",
490
+ options: SingleQueryOptions<ReturnType<typeof GetAccount>> = {}
491
+ ) => {
492
+ return useConnectedSingleQuery<ReturnType<typeof GetAccount>>(
493
+ ACCOUNT_QUERY_KEY(accountId),
494
+ (params: SingleQueryParams) =>
495
+ GetAccount({ accountId: accountId || "unknown", ...params }),
496
+ {
497
+ ...options,
498
+ enabled: !!accountId && (options?.enabled ?? true),
499
+ }
500
+ );
501
+ };
502
+ ```
503
+
504
+ **Conventions**:
505
+
506
+ - Named: `useGet*` (camelCase with "use" prefix)
507
+ - Wraps the query function with `useConnectedSingleQuery` or similar
508
+ - Accepts options that extend base query options
509
+ - Handles conditional enabling (e.g., only run if ID exists)
510
+
511
+ ### 3.3 Query Key Conventions
512
+
513
+ Query keys follow a hierarchical structure that enables efficient cache management.
514
+
515
+ #### Structure Pattern
516
+
517
+ ```typescript
518
+ ["RESOURCE_TYPE", ...filters, ...identifiers];
519
+ ```
520
+
521
+ #### Examples
522
+
523
+ **Base List Query**:
524
+
525
+ ```typescript
526
+ ACCOUNTS_QUERY_KEY();
527
+ // Returns: ["ACCOUNTS"]
528
+ ```
529
+
530
+ **Filtered List Query**:
531
+
532
+ ```typescript
533
+ ACCOUNTS_QUERY_KEY(true, false); // verified=true, online=false
534
+ // Returns: ["ACCOUNTS", "VERIFIED", "OFFLINE"]
535
+ ```
536
+
537
+ **Single Resource Query**:
538
+
539
+ ```typescript
540
+ ACCOUNT_QUERY_KEY("account-123");
541
+ // Returns: ["ACCOUNTS", "account-123"]
542
+ // Note: Spreads parent key for hierarchy
543
+ ```
544
+
545
+ **Nested Resource Query**:
546
+
547
+ ```typescript
548
+ EVENT_SESSION_QUERY_KEY("event-123", "session-456");
549
+ // Returns: ["EVENTS", "event-123", "SESSIONS", "session-456"]
550
+ ```
551
+
552
+ #### Key Design Principles
553
+
554
+ 1. **Hierarchical Inheritance**: Child keys include parent keys
555
+
556
+ ```typescript
557
+ export const ACCOUNT_QUERY_KEY = (accountId: string) => [
558
+ ...ACCOUNTS_QUERY_KEY(), // Includes parent
559
+ accountId,
560
+ ];
561
+ ```
562
+
563
+ 2. **Filter Representation**: Filters are included as string constants
564
+
565
+ ```typescript
566
+ if (typeof verified !== "undefined")
567
+ keys.push(verified ? "VERIFIED" : "UNVERIFIED");
568
+ ```
569
+
570
+ 3. **Identifier Last**: Resource IDs come after filters
571
+
572
+ ```typescript
573
+ ["EVENTS", "PAST", "FEATURED", "event-123"];
574
+ ```
575
+
576
+ 4. **Consistent Naming**: Use SCREAMING_SNAKE_CASE for constants
577
+
578
+ #### Benefits of This Structure
579
+
580
+ - **Automatic Invalidation**: Invalidating `["ACCOUNTS"]` invalidates all account queries
581
+ - **Granular Updates**: Can update specific queries without affecting others
582
+ - **Type Safety**: Query keys are typed and autocompleted
583
+ - **Debugging**: Clear hierarchy makes cache inspection easier
584
+
585
+ ---
586
+
587
+ ## 4. Mutation Patterns
588
+
589
+ ### 4.1 Mutation Structure
590
+
591
+ Each mutation file follows a consistent three-part structure:
592
+
593
+ #### 1. Params Interface
594
+
595
+ Defines the parameters for the mutation function.
596
+
597
+ ```typescript
598
+ /**
599
+ * @category Params
600
+ * @group Account
601
+ */
602
+ export interface CreateAccountParams extends MutationParams {
603
+ account: AccountCreateInputs;
604
+ }
605
+ ```
606
+
607
+ **Conventions**:
608
+
609
+ - Extends `MutationParams` (includes `adminApiParams` and `queryClient`)
610
+ - Named: `*Params` (e.g., `CreateAccountParams`, `UpdateAccountParams`)
611
+ - Includes domain-specific parameters
612
+
613
+ #### 2. Mutation Function
614
+
615
+ The actual API call function (can be used standalone).
616
+
617
+ ```typescript
618
+ /**
619
+ * @category Methods
620
+ * @group Account
621
+ */
622
+ export const CreateAccount = async ({
623
+ account,
624
+ adminApiParams,
625
+ queryClient,
626
+ }: CreateAccountParams): Promise<ConnectedXMResponse<Account>> => {
627
+ const connectedXM = await GetAdminAPI(adminApiParams);
628
+ const { data } = await connectedXM.post<ConnectedXMResponse<Account>>(
629
+ `/accounts`,
630
+ account
631
+ );
632
+
633
+ if (queryClient && data.status === "ok") {
634
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
635
+ SET_ACCOUNT_QUERY_DATA(queryClient, [data?.data.id], data);
636
+ }
637
+
638
+ return data;
639
+ };
640
+ ```
641
+
642
+ **Conventions**:
643
+
644
+ - Named: `Create*`, `Update*`, `Delete*` (PascalCase)
645
+ - Accepts params including `adminApiParams` and `queryClient`
646
+ - Returns `Promise<ConnectedXMResponse<T>>`
647
+ - Handles cache updates on success
648
+
649
+ #### 3. React Hook
650
+
651
+ React Query mutation hook wrapper.
652
+
653
+ ```typescript
654
+ /**
655
+ * @category Mutations
656
+ * @group Account
657
+ */
658
+ export const useCreateAccount = (
659
+ options: Omit<
660
+ ConnectedXMMutationOptions<
661
+ Awaited<ReturnType<typeof CreateAccount>>,
662
+ Omit<CreateAccountParams, "queryClient" | "adminApiParams">
663
+ >,
664
+ "mutationFn"
665
+ > = {}
666
+ ) => {
667
+ return useConnectedMutation<
668
+ CreateAccountParams,
669
+ Awaited<ReturnType<typeof CreateAccount>>
670
+ >(CreateAccount, options);
671
+ };
672
+ ```
673
+
674
+ **Conventions**:
675
+
676
+ - Named: `useCreate*`, `useUpdate*`, `useDelete*`
677
+ - Wraps mutation function with `useConnectedMutation`
678
+ - Options exclude `queryClient` and `adminApiParams` (injected automatically)
679
+ - Can accept React Query mutation options (onSuccess, onError, etc.)
680
+
681
+ ### 4.2 Cache Invalidation Patterns
682
+
683
+ Mutations must update the React Query cache to keep the UI in sync. There are two main strategies:
684
+
685
+ #### Strategy 1: Invalidate and Refetch
686
+
687
+ Invalidate query keys to trigger automatic refetching.
688
+
689
+ ```typescript
690
+ if (queryClient && data.status === "ok") {
691
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
692
+ }
693
+ ```
694
+
695
+ **When to Use**:
696
+
697
+ - After creating new resources (adds to list)
698
+ - After deleting resources (removes from list)
699
+ - When you want fresh data from the server
700
+
701
+ **Benefits**:
702
+
703
+ - Ensures data consistency
704
+ - Handles edge cases automatically
705
+ - Simple to implement
706
+
707
+ **Drawbacks**:
708
+
709
+ - Causes network request
710
+ - May cause loading states
711
+
712
+ #### Strategy 2: Optimistic Updates
713
+
714
+ Directly update the cache with known data.
715
+
716
+ ```typescript
717
+ if (queryClient && data.status === "ok") {
718
+ // Invalidate list to show new item
719
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
720
+
721
+ // Optimistically update single item cache
722
+ SET_ACCOUNT_QUERY_DATA(queryClient, [data?.data.id], data);
723
+ }
724
+ ```
725
+
726
+ **When to Use**:
727
+
728
+ - After updating existing resources
729
+ - When you have the complete updated data
730
+ - To provide instant UI feedback
731
+
732
+ **Benefits**:
733
+
734
+ - Instant UI updates
735
+ - Better user experience
736
+ - Reduces unnecessary requests
737
+
738
+ **Drawbacks**:
739
+
740
+ - Must ensure data consistency
741
+ - More complex implementation
742
+
743
+ #### Common Patterns
744
+
745
+ **Create Pattern**:
746
+
747
+ ```typescript
748
+ // 1. Invalidate list (to show new item)
749
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
750
+
751
+ // 2. Set individual item cache (for immediate access)
752
+ SET_ACCOUNT_QUERY_DATA(queryClient, [data?.data.id], data);
753
+ ```
754
+
755
+ **Update Pattern**:
756
+
757
+ ```typescript
758
+ // 1. Invalidate list (in case item appears in filtered views)
759
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
760
+
761
+ // 2. Update individual item cache
762
+ SET_ACCOUNT_QUERY_DATA(queryClient, [accountId], data);
763
+ ```
764
+
765
+ **Delete Pattern**:
766
+
767
+ ```typescript
768
+ // 1. Invalidate list (to remove item)
769
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
770
+
771
+ // 2. Remove individual item cache
772
+ queryClient.removeQueries({ queryKey: ACCOUNT_QUERY_KEY(accountId) });
773
+ ```
774
+
775
+ **Complex Invalidation**:
776
+
777
+ ```typescript
778
+ // Invalidate multiple related queries
779
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
780
+ queryClient.invalidateQueries({ queryKey: EVENT_ATTENDEES_QUERY_KEY(eventId) });
781
+ queryClient.invalidateQueries({ queryKey: ["REPORTS"] });
782
+ ```
783
+
784
+ ### 4.3 Mutation File Organization
785
+
786
+ Mutations are organized by domain, similar to queries:
787
+
788
+ ```
789
+ mutations/
790
+ ├── useConnectedMutation.ts # Base mutation hook
791
+ ├── account/
792
+ │ ├── index.ts # Exports all account mutations
793
+ │ ├── useCreateAccount.ts
794
+ │ ├── useUpdateAccount.ts
795
+ │ ├── useDeleteAccount.ts
796
+ │ └── ...
797
+ ├── event/
798
+ │ ├── index.ts
799
+ │ ├── useCreateEvent.ts
800
+ │ └── ...
801
+ └── ...
802
+ ```
803
+
804
+ **Translation Mutations**: Some resources have translatable fields. These follow a special pattern:
805
+
806
+ ```typescript
807
+ // In params.ts
808
+ export interface EventTranslationUpdateInputs {
809
+ name?: string | null;
810
+ shortDescription?: string | null;
811
+ longDescription?: string | null;
812
+ }
813
+
814
+ // In mutations/event/translations/
815
+ export const useUpdateEventTranslation = ...
816
+ ```
817
+
818
+ **Naming Convention**: `*TranslationUpdateInputs` and `useUpdate*Translation`
819
+
820
+ ---
821
+
822
+ ## 5. Directory Organization
823
+
824
+ ### 5.1 `src/queries/`
825
+
826
+ Queries are organized by domain (resource type), with base utilities in the root.
827
+
828
+ #### Structure
829
+
830
+ ```
831
+ queries/
832
+ ├── useConnectedSingleQuery.ts # Base single query hook
833
+ ├── useConnectedInfiniteQuery.ts # Base infinite query hook
834
+ ├── useConnectedCursorQuery.ts # Base cursor query hook
835
+ ├── index.ts # Exports all queries
836
+ ├── accounts/
837
+ │ ├── index.ts # Exports account queries
838
+ │ ├── useGetAccount.ts
839
+ │ ├── useGetAccounts.ts
840
+ │ ├── useGetAccountEvents.ts
841
+ │ └── ...
842
+ ├── events/
843
+ │ ├── index.ts
844
+ │ ├── useGetEvent.ts
845
+ │ ├── useGetEvents.ts
846
+ │ └── ...
847
+ └── ...
848
+ ```
849
+
850
+ #### Domain Organization
851
+
852
+ Each domain folder contains:
853
+
854
+ - **Multiple query files**: One per endpoint/resource
855
+ - **index.ts**: Barrel export file that re-exports all queries from the domain
856
+
857
+ **Example `index.ts`**:
858
+
859
+ ```typescript
860
+ // queries/accounts/index.ts
861
+ export * from "./useGetAccount";
862
+ export * from "./useGetAccounts";
863
+ export * from "./useGetAccountEvents";
864
+ // ... etc
865
+ ```
866
+
867
+ #### Base Utilities
868
+
869
+ The root `queries/` directory contains reusable query hooks:
870
+
871
+ - `useConnectedSingleQuery.ts` - Wrapper for single resource queries
872
+ - `useConnectedInfiniteQuery.ts` - Wrapper for paginated list queries
873
+ - `useConnectedCursorQuery.ts` - Wrapper for cursor-based queries
874
+
875
+ These provide:
876
+
877
+ - Consistent error handling
878
+ - Automatic retry logic
879
+ - Standardized query options
880
+ - Integration with `ConnectedXMProvider`
881
+
882
+ ### 5.2 `src/mutations/`
883
+
884
+ Mutations follow the same domain-based organization as queries.
885
+
886
+ #### Structure
887
+
888
+ ```
889
+ mutations/
890
+ ├── useConnectedMutation.ts # Base mutation hook
891
+ ├── index.ts # Exports all mutations
892
+ ├── account/
893
+ │ ├── index.ts # Exports account mutations
894
+ │ ├── useCreateAccount.ts
895
+ │ ├── useUpdateAccount.ts
896
+ │ ├── useDeleteAccount.ts
897
+ │ └── ...
898
+ ├── event/
899
+ │ ├── index.ts
900
+ │ ├── useCreateEvent.ts
901
+ │ ├── translations/ # Translation mutations
902
+ │ │ ├── useUpdateEventTranslation.ts
903
+ │ │ └── ...
904
+ │ └── ...
905
+ └── ...
906
+ ```
907
+
908
+ #### Domain Organization
909
+
910
+ Similar to queries:
911
+
912
+ - **Multiple mutation files**: One per operation (Create, Update, Delete, etc.)
913
+ - **index.ts**: Barrel export file
914
+ - **translations/**: Subfolder for translation-specific mutations (when applicable)
915
+
916
+ #### Base Utility
917
+
918
+ `useConnectedMutation.ts` provides:
919
+
920
+ - Automatic `adminApiParams` injection
921
+ - Error handling integration
922
+ - QueryClient access
923
+ - Standardized mutation options
924
+
925
+ ### 5.3 `src/utilities/`
926
+
927
+ Utility functions used across queries and mutations.
928
+
929
+ #### Available Utilities
930
+
931
+ **Cache Management**:
932
+
933
+ - `CacheIndividualQueries.ts` - Caches individual items from list responses
934
+ - Used in queries to populate single-item caches from list responses
935
+
936
+ **Data Transformation**:
937
+
938
+ - `TransformPrice.ts` - Formats price values
939
+ - `GetImageVariant.ts` - Generates image URLs with variants
940
+ - `CalculateDuration.ts` - Calculates time durations
941
+
942
+ **Query Helpers**:
943
+
944
+ - `MergeInfinitePages.ts` - Flattens infinite query pages into single array
945
+ - `AppendInfiniteQuery.ts` - Appends new page to infinite query cache
946
+
947
+ **Type Utilities**:
948
+
949
+ - `IsUUID.ts` - Validates UUID format
950
+ - `GetErrorMessage.ts` - Extracts error messages from responses
951
+
952
+ #### Usage Pattern
953
+
954
+ Utilities are imported from the main index:
955
+
956
+ ```typescript
957
+ import { MergeInfinitePages, CacheIndividualQueries } from "@connectedxm/admin";
958
+ ```
959
+
960
+ ---
961
+
962
+ ## 6. Code Patterns and Conventions
963
+
964
+ ### 6.1 Error Handling
965
+
966
+ The SDK implements standardized error handling across all queries and mutations.
967
+
968
+ #### HTTP Status Code Handling
969
+
970
+ All query hooks handle these status codes consistently:
971
+
972
+ **401 - Unauthorized**:
973
+
974
+ ```typescript
975
+ if (error.response?.status === 401) {
976
+ if (onNotAuthorized) onNotAuthorized(error, queryKeys, shouldRedirect);
977
+ return false; // Don't retry
978
+ }
979
+ ```
980
+
981
+ - Triggers `onNotAuthorized` callback
982
+ - Typically indicates expired token
983
+ - No automatic retry
984
+
985
+ **403/460/461 - Forbidden**:
986
+
987
+ ```typescript
988
+ if (
989
+ error.response?.status === 403 ||
990
+ error.response?.status === 460 ||
991
+ error.response?.status === 461
992
+ ) {
993
+ if (onModuleForbidden) onModuleForbidden(error, queryKeys, shouldRedirect);
994
+ return false; // Don't retry
995
+ }
996
+ ```
997
+
998
+ - Triggers `onModuleForbidden` callback
999
+ - Indicates user lacks permission
1000
+ - No automatic retry
1001
+
1002
+ **404 - Not Found**:
1003
+
1004
+ ```typescript
1005
+ if (error.response?.status === 404) {
1006
+ if (onNotFound) onNotFound(error, queryKeys, shouldRedirect);
1007
+ return false; // Don't retry
1008
+ }
1009
+ ```
1010
+
1011
+ - Triggers `onNotFound` callback
1012
+ - Resource doesn't exist
1013
+ - No automatic retry
1014
+
1015
+ **Other Errors**:
1016
+
1017
+ ```typescript
1018
+ // Default retry logic
1019
+ if (failureCount < 3) return true;
1020
+ return false;
1021
+ ```
1022
+
1023
+ - Retries up to 3 times
1024
+ - For network errors, timeouts, etc.
1025
+
1026
+ #### Retry Configuration
1027
+
1028
+ - **Stale Time**: 60 seconds (data considered fresh for 1 minute)
1029
+ - **Max Retries**: 3 attempts for network errors
1030
+ - **No Retry**: For 4xx status codes (client errors)
1031
+
1032
+ #### Error Callbacks
1033
+
1034
+ All error callbacks receive:
1035
+
1036
+ 1. **Error object**: Axios error with response data
1037
+ 2. **Query key**: The React Query key that failed
1038
+ 3. **Should redirect flag**: Whether redirect should occur
1039
+
1040
+ ### 6.2 Type Safety
1041
+
1042
+ The SDK is fully typed with TypeScript for maximum type safety.
1043
+
1044
+ #### Generic Types
1045
+
1046
+ Functions use generics for reusability:
1047
+
1048
+ ```typescript
1049
+ export const useConnectedSingleQuery = <TQueryData = unknown>(
1050
+ queryKeys: QueryKey,
1051
+ queryFn: (params: SingleQueryParams) => TQueryData,
1052
+ options: SingleQueryOptions<TQueryData> = {}
1053
+ )
1054
+ ```
1055
+
1056
+ #### Response Wrapper
1057
+
1058
+ All API responses use `ConnectedXMResponse<T>`:
1059
+
1060
+ ```typescript
1061
+ export interface ConnectedXMResponse<TData> {
1062
+ status: string;
1063
+ message: string;
1064
+ count?: number;
1065
+ data: TData;
1066
+ cursor?: string | number | null;
1067
+ }
1068
+ ```
1069
+
1070
+ #### Type Inference
1071
+
1072
+ React Query hooks infer types from query functions:
1073
+
1074
+ ```typescript
1075
+ const { data } = useGetAccount(accountId);
1076
+ // data is automatically typed as ConnectedXMResponse<Account> | undefined
1077
+ ```
1078
+
1079
+ #### Enum Types
1080
+
1081
+ Enums are used with `keyof typeof` for type safety:
1082
+
1083
+ ```typescript
1084
+ export interface AccountUpdateInputs {
1085
+ accountAccess?: keyof typeof AccountAccess | null;
1086
+ // TypeScript ensures only valid enum values
1087
+ }
1088
+ ```
1089
+
1090
+ ### 6.3 JSDoc Categories
1091
+
1092
+ All exported functions use JSDoc with category tags for documentation generation.
1093
+
1094
+ #### Categories
1095
+
1096
+ - `@category Keys` - Query key functions (`*_QUERY_KEY`)
1097
+ - `@category Setters` - Cache setter functions (`SET_*_QUERY_DATA`)
1098
+ - `@category Queries` - Query functions (`Get*`)
1099
+ - `@category Hooks` - React hooks (`useGet*`, `useCreate*`)
1100
+ - `@category Mutations` - Mutation hooks (`useCreate*`, etc.)
1101
+ - `@category Methods` - Mutation functions (`Create*`, `Update*`, `Delete*`)
1102
+ - `@category Params` - Parameter interfaces (`*Params`)
1103
+
1104
+ #### Groups
1105
+
1106
+ - `@group Accounts` - Account-related functions
1107
+ - `@group Events` - Event-related functions
1108
+ - `@group Organization` - Organization-related functions
1109
+ - etc.
1110
+
1111
+ #### Example
1112
+
1113
+ ```typescript
1114
+ /**
1115
+ * @category Keys
1116
+ * @group Accounts
1117
+ */
1118
+ export const ACCOUNT_QUERY_KEY = (accountId: string) => [...];
1119
+
1120
+ /**
1121
+ * @category Hooks
1122
+ * @group Accounts
1123
+ */
1124
+ export const useGetAccount = (accountId: string) => {...};
1125
+ ```
1126
+
1127
+ ### 6.4 Naming Conventions
1128
+
1129
+ Consistent naming makes the codebase predictable and maintainable.
1130
+
1131
+ #### Query Functions
1132
+
1133
+ - **Hook**: `useGet*` (e.g., `useGetAccount`, `useGetAccounts`)
1134
+ - **Function**: `Get*` (e.g., `GetAccount`, `GetAccounts`)
1135
+ - **Key**: `*_QUERY_KEY` (e.g., `ACCOUNT_QUERY_KEY`, `ACCOUNTS_QUERY_KEY`)
1136
+ - **Setter**: `SET_*_QUERY_DATA` (e.g., `SET_ACCOUNT_QUERY_DATA`)
1137
+
1138
+ #### Mutation Functions
1139
+
1140
+ - **Hook**: `useCreate*`, `useUpdate*`, `useDelete*`
1141
+ - **Function**: `Create*`, `Update*`, `Delete*`
1142
+ - **Params**: `*Params` (e.g., `CreateAccountParams`)
1143
+
1144
+ #### File Names
1145
+
1146
+ - **Queries**: `useGet*.ts` (e.g., `useGetAccount.ts`)
1147
+ - **Mutations**: `useCreate*.ts`, `useUpdate*.ts`, `useDelete*.ts`
1148
+ - **Match the hook name**
1149
+
1150
+ #### Variables
1151
+
1152
+ - **camelCase** for variables and functions
1153
+ - **PascalCase** for types, interfaces, and classes
1154
+ - **SCREAMING_SNAKE_CASE** for constants
1155
+
1156
+ ---
1157
+
1158
+ ## 7. Common Utilities
1159
+
1160
+ ### 7.1 Cache Management
1161
+
1162
+ #### `CacheIndividualQueries`
1163
+
1164
+ Caches individual items from a list response into their respective single-item query caches.
1165
+
1166
+ **Purpose**: When fetching a list, also populate individual item caches for instant access.
1167
+
1168
+ **Signature**:
1169
+
1170
+ ```typescript
1171
+ export const CacheIndividualQueries = <TData extends ItemWithId>(
1172
+ page: ConnectedXMResponse<TData[]>,
1173
+ queryClient: QueryClient,
1174
+ queryKeyFn: (id: string) => QueryKey,
1175
+ itemMap?: (item: TData) => TData
1176
+ ) => void
1177
+ ```
1178
+
1179
+ **Usage Example**:
1180
+
1181
+ ```typescript
1182
+ const { data } = await adminApi.get("/accounts");
1183
+ CacheIndividualQueries(data, queryClient, (id) => ACCOUNT_QUERY_KEY(id));
1184
+ ```
1185
+
1186
+ **Features**:
1187
+
1188
+ - Caches by `id`
1189
+ - Also caches by `slug`, `username`, `name`, `code`, `alternateId` if available
1190
+ - Sets cache timestamp to 1 minute ago (allows refetch if needed)
1191
+ - Optional `itemMap` for data transformation
1192
+
1193
+ **When to Use**:
1194
+
1195
+ - In list queries to populate individual caches
1196
+ - After fetching paginated lists
1197
+ - To enable instant navigation to detail pages
1198
+
1199
+ #### `SET_*_QUERY_DATA`
1200
+
1201
+ Direct cache update helpers for each resource type.
1202
+
1203
+ **Purpose**: Update cache with known data (optimistic updates).
1204
+
1205
+ **Pattern**:
1206
+
1207
+ ```typescript
1208
+ SET_ACCOUNT_QUERY_DATA(queryClient, [accountId], response);
1209
+ ```
1210
+
1211
+ **Usage**:
1212
+
1213
+ - After mutations to update cache immediately
1214
+ - For optimistic UI updates
1215
+ - When you have complete data
1216
+
1217
+ ### 7.2 Data Transformation
1218
+
1219
+ #### `MergeInfinitePages`
1220
+
1221
+ Flattens infinite query pages into a single array.
1222
+
1223
+ **Signature**:
1224
+
1225
+ ```typescript
1226
+ export function MergeInfinitePages<TData>(
1227
+ data: InfiniteData<ConnectedXMResponse<TData[]>>
1228
+ ): TData[];
1229
+ ```
1230
+
1231
+ **Usage Example**:
1232
+
1233
+ ```typescript
1234
+ const { data } = useGetAccounts();
1235
+ const allAccounts = MergeInfinitePages(data);
1236
+ // Returns: Account[] (flattened from all pages)
1237
+ ```
1238
+
1239
+ **When to Use**:
1240
+
1241
+ - Displaying all items from infinite query
1242
+ - Filtering/searching across all pages
1243
+ - Calculating totals across pages
1244
+
1245
+ #### `TransformPrice`
1246
+
1247
+ Formats price values for display.
1248
+
1249
+ **Usage**: Price formatting and currency conversion.
1250
+
1251
+ #### `GetImageVariant`
1252
+
1253
+ Generates image URLs with size variants.
1254
+
1255
+ **Usage**: Responsive image loading.
1256
+
1257
+ #### `CalculateDuration`
1258
+
1259
+ Calculates time durations between dates.
1260
+
1261
+ **Usage**: Event duration, session length, etc.
1262
+
1263
+ ### 7.3 Type Utilities
1264
+
1265
+ #### `IsUUID`
1266
+
1267
+ Validates if a string is a valid UUID.
1268
+
1269
+ **Usage**: Type checking before API calls.
1270
+
1271
+ #### `GetErrorMessage`
1272
+
1273
+ Extracts user-friendly error messages from API responses.
1274
+
1275
+ **Usage**: Error display in UI.
1276
+
1277
+ ---
1278
+
1279
+ ## 8. Examples and Best Practices
1280
+
1281
+ ### 8.1 Creating a New Query
1282
+
1283
+ Follow these steps to create a new query:
1284
+
1285
+ #### Step 1: Create the Query File
1286
+
1287
+ Create `src/queries/[domain]/useGet[Resource].ts`
1288
+
1289
+ #### Step 2: Define Query Key
1290
+
1291
+ ```typescript
1292
+ /**
1293
+ * @category Keys
1294
+ * @group [Domain]
1295
+ */
1296
+ export const [RESOURCE]_QUERY_KEY = (id: string) => [
1297
+ ...[PARENT]_QUERY_KEY(), // If nested
1298
+ id,
1299
+ ];
1300
+ ```
1301
+
1302
+ #### Step 3: Define Setter Function
1303
+
1304
+ ```typescript
1305
+ /**
1306
+ * @category Setters
1307
+ * @group [Domain]
1308
+ */
1309
+ export const SET_[RESOURCE]_QUERY_DATA = (
1310
+ client: QueryClient,
1311
+ keyParams: Parameters<typeof [RESOURCE]_QUERY_KEY>,
1312
+ response: Awaited<ReturnType<typeof Get[Resource]>>
1313
+ ) => {
1314
+ client.setQueryData([RESOURCE]_QUERY_KEY(...keyParams), response);
1315
+ };
1316
+ ```
1317
+
1318
+ #### Step 4: Define Query Function
1319
+
1320
+ ```typescript
1321
+ /**
1322
+ * @category Queries
1323
+ * @group [Domain]
1324
+ */
1325
+ export const Get[Resource] = async ({
1326
+ id,
1327
+ adminApiParams,
1328
+ }: Get[Resource]Props): Promise<ConnectedXMResponse<[Resource]>> => {
1329
+ const adminApi = await GetAdminAPI(adminApiParams);
1330
+ const { data } = await adminApi.get(`/[endpoint]/${id}`);
1331
+ return data;
1332
+ };
1333
+ ```
1334
+
1335
+ #### Step 5: Define React Hook
1336
+
1337
+ ```typescript
1338
+ /**
1339
+ * @category Hooks
1340
+ * @group [Domain]
1341
+ */
1342
+ export const useGet[Resource] = (
1343
+ id: string = "",
1344
+ options: SingleQueryOptions<ReturnType<typeof Get[Resource]>> = {}
1345
+ ) => {
1346
+ return useConnectedSingleQuery<ReturnType<typeof Get[Resource]>>(
1347
+ [RESOURCE]_QUERY_KEY(id),
1348
+ (params: SingleQueryParams) =>
1349
+ Get[Resource]({ id: id || "unknown", ...params }),
1350
+ {
1351
+ ...options,
1352
+ enabled: !!id && (options?.enabled ?? true),
1353
+ }
1354
+ );
1355
+ };
1356
+ ```
1357
+
1358
+ #### Step 6: Export from Index
1359
+
1360
+ Add to `src/queries/[domain]/index.ts`:
1361
+
1362
+ ```typescript
1363
+ export * from "./useGet[Resource]";
1364
+ ```
1365
+
1366
+ #### Complete Example: `useGetGroup`
1367
+
1368
+ ```typescript
1369
+ import { GetAdminAPI } from "@src/AdminAPI";
1370
+ import {
1371
+ SingleQueryOptions,
1372
+ SingleQueryParams,
1373
+ useConnectedSingleQuery,
1374
+ } from "../useConnectedSingleQuery";
1375
+ import { ConnectedXMResponse } from "@src/interfaces";
1376
+ import { Group } from "@src/interfaces";
1377
+ import { QueryClient } from "@tanstack/react-query";
1378
+ import { GROUPS_QUERY_KEY } from "./useGetGroups";
1379
+
1380
+ /**
1381
+ * @category Keys
1382
+ * @group Groups
1383
+ */
1384
+ export const GROUP_QUERY_KEY = (groupId: string) => [
1385
+ ...GROUPS_QUERY_KEY(),
1386
+ groupId,
1387
+ ];
1388
+
1389
+ /**
1390
+ * @category Setters
1391
+ * @group Groups
1392
+ */
1393
+ export const SET_GROUP_QUERY_DATA = (
1394
+ client: QueryClient,
1395
+ keyParams: Parameters<typeof GROUP_QUERY_KEY>,
1396
+ response: Awaited<ReturnType<typeof GetGroup>>
1397
+ ) => {
1398
+ client.setQueryData(GROUP_QUERY_KEY(...keyParams), response);
1399
+ };
1400
+
1401
+ interface GetGroupProps extends SingleQueryParams {
1402
+ groupId: string;
1403
+ }
1404
+
1405
+ /**
1406
+ * @category Queries
1407
+ * @group Groups
1408
+ */
1409
+ export const GetGroup = async ({
1410
+ groupId = "",
1411
+ adminApiParams,
1412
+ }: GetGroupProps): Promise<ConnectedXMResponse<Group>> => {
1413
+ const adminApi = await GetAdminAPI(adminApiParams);
1414
+ const { data } = await adminApi.get(`/groups/${groupId}`);
1415
+ return data;
1416
+ };
1417
+
1418
+ /**
1419
+ * @category Hooks
1420
+ * @group Groups
1421
+ */
1422
+ export const useGetGroup = (
1423
+ groupId: string = "",
1424
+ options: SingleQueryOptions<ReturnType<typeof GetGroup>> = {}
1425
+ ) => {
1426
+ return useConnectedSingleQuery<ReturnType<typeof GetGroup>>(
1427
+ GROUP_QUERY_KEY(groupId),
1428
+ (params: SingleQueryParams) =>
1429
+ GetGroup({ groupId: groupId || "unknown", ...params }),
1430
+ {
1431
+ ...options,
1432
+ enabled: !!groupId && (options?.enabled ?? true),
1433
+ }
1434
+ );
1435
+ };
1436
+ ```
1437
+
1438
+ ### 8.2 Creating a New Mutation
1439
+
1440
+ Follow these steps to create a new mutation:
1441
+
1442
+ #### Step 1: Create the Mutation File
1443
+
1444
+ Create `src/mutations/[domain]/useCreate[Resource].ts`
1445
+
1446
+ #### Step 2: Define Params Interface
1447
+
1448
+ ```typescript
1449
+ /**
1450
+ * @category Params
1451
+ * @group [Domain]
1452
+ */
1453
+ export interface Create[Resource]Params extends MutationParams {
1454
+ [resource]: [Resource]CreateInputs;
1455
+ }
1456
+ ```
1457
+
1458
+ #### Step 3: Define Mutation Function
1459
+
1460
+ ```typescript
1461
+ /**
1462
+ * @category Methods
1463
+ * @group [Domain]
1464
+ */
1465
+ export const Create[Resource] = async ({
1466
+ [resource],
1467
+ adminApiParams,
1468
+ queryClient,
1469
+ }: Create[Resource]Params): Promise<ConnectedXMResponse<[Resource]>> => {
1470
+ const connectedXM = await GetAdminAPI(adminApiParams);
1471
+ const { data } = await connectedXM.post<ConnectedXMResponse<[Resource]>>(
1472
+ `/[endpoint]`,
1473
+ [resource]
1474
+ );
1475
+
1476
+ if (queryClient && data.status === "ok") {
1477
+ queryClient.invalidateQueries({ queryKey: [RESOURCES]_QUERY_KEY() });
1478
+ SET_[RESOURCE]_QUERY_DATA(queryClient, [data?.data.id], data);
1479
+ }
1480
+
1481
+ return data;
1482
+ };
1483
+ ```
1484
+
1485
+ #### Step 4: Define React Hook
1486
+
1487
+ ```typescript
1488
+ /**
1489
+ * @category Mutations
1490
+ * @group [Domain]
1491
+ */
1492
+ export const useCreate[Resource] = (
1493
+ options: Omit<
1494
+ ConnectedXMMutationOptions<
1495
+ Awaited<ReturnType<typeof Create[Resource]>>,
1496
+ Omit<Create[Resource]Params, "queryClient" | "adminApiParams">
1497
+ >,
1498
+ "mutationFn"
1499
+ > = {}
1500
+ ) => {
1501
+ return useConnectedMutation<
1502
+ Create[Resource]Params,
1503
+ Awaited<ReturnType<typeof Create[Resource]>>
1504
+ >(Create[Resource], options);
1505
+ };
1506
+ ```
1507
+
1508
+ #### Step 5: Export from Index
1509
+
1510
+ Add to `src/mutations/[domain]/index.ts`:
1511
+
1512
+ ```typescript
1513
+ export * from "./useCreate[Resource]";
1514
+ ```
1515
+
1516
+ #### Complete Example: `useCreateGroup`
1517
+
1518
+ ```typescript
1519
+ import { Group, ConnectedXMResponse } from "@src/interfaces";
1520
+ import {
1521
+ ConnectedXMMutationOptions,
1522
+ MutationParams,
1523
+ useConnectedMutation,
1524
+ } from "../useConnectedMutation";
1525
+ import { GROUPS_QUERY_KEY, SET_GROUP_QUERY_DATA } from "@src/queries";
1526
+ import { GetAdminAPI } from "@src/AdminAPI";
1527
+ import { GroupCreateInputs } from "@src/params";
1528
+
1529
+ /**
1530
+ * @category Params
1531
+ * @group Group
1532
+ */
1533
+ export interface CreateGroupParams extends MutationParams {
1534
+ group: GroupCreateInputs;
1535
+ }
1536
+
1537
+ /**
1538
+ * @category Methods
1539
+ * @group Group
1540
+ */
1541
+ export const CreateGroup = async ({
1542
+ group,
1543
+ adminApiParams,
1544
+ queryClient,
1545
+ }: CreateGroupParams): Promise<ConnectedXMResponse<Group>> => {
1546
+ const connectedXM = await GetAdminAPI(adminApiParams);
1547
+ const { data } = await connectedXM.post<ConnectedXMResponse<Group>>(
1548
+ `/groups`,
1549
+ group
1550
+ );
1551
+ if (queryClient && data.status === "ok") {
1552
+ queryClient.invalidateQueries({ queryKey: GROUPS_QUERY_KEY() });
1553
+ SET_GROUP_QUERY_DATA(queryClient, [data?.data.id], data);
1554
+ }
1555
+ return data;
1556
+ };
1557
+
1558
+ /**
1559
+ * @category Mutations
1560
+ * @group Group
1561
+ */
1562
+ export const useCreateGroup = (
1563
+ options: Omit<
1564
+ ConnectedXMMutationOptions<
1565
+ Awaited<ReturnType<typeof CreateGroup>>,
1566
+ Omit<CreateGroupParams, "queryClient" | "adminApiParams">
1567
+ >,
1568
+ "mutationFn"
1569
+ > = {}
1570
+ ) => {
1571
+ return useConnectedMutation<
1572
+ CreateGroupParams,
1573
+ Awaited<ReturnType<typeof CreateGroup>>
1574
+ >(CreateGroup, options);
1575
+ };
1576
+ ```
1577
+
1578
+ ### 8.3 Query Key Design
1579
+
1580
+ #### Guidelines
1581
+
1582
+ 1. **Start with Resource Type**: `["ACCOUNTS"]`, `["EVENTS"]`
1583
+ 2. **Add Filters**: `["ACCOUNTS", "VERIFIED"]`
1584
+ 3. **Add Identifiers Last**: `["ACCOUNTS", "account-123"]`
1585
+ 4. **Inherit Parent Keys**: Child keys should spread parent keys
1586
+
1587
+ #### Good Examples
1588
+
1589
+ ```typescript
1590
+ // Simple list
1591
+ ACCOUNTS_QUERY_KEY();
1592
+ // ["ACCOUNTS"]
1593
+
1594
+ // Filtered list
1595
+ ACCOUNTS_QUERY_KEY(true, false);
1596
+ // ["ACCOUNTS", "VERIFIED", "OFFLINE"]
1597
+
1598
+ // Single item (inherits parent)
1599
+ ACCOUNT_QUERY_KEY("123");
1600
+ // ["ACCOUNTS", "123"]
1601
+
1602
+ // Nested resource
1603
+ EVENT_SESSION_QUERY_KEY("event-123", "session-456");
1604
+ // ["EVENTS", "event-123", "SESSIONS", "session-456"]
1605
+ ```
1606
+
1607
+ #### Bad Examples
1608
+
1609
+ ```typescript
1610
+ // ❌ Don't use IDs in base list keys
1611
+ ACCOUNTS_QUERY_KEY("account-123"); // Wrong!
1612
+
1613
+ // ❌ Don't forget to inherit parent
1614
+ ACCOUNT_QUERY_KEY("123");
1615
+ // Should be: [...ACCOUNTS_QUERY_KEY(), "123"]
1616
+
1617
+ // ❌ Don't use inconsistent naming
1618
+ accounts_query_key(); // Should be ACCOUNTS_QUERY_KEY
1619
+ ```
1620
+
1621
+ ### 8.4 Cache Management
1622
+
1623
+ #### When to Invalidate
1624
+
1625
+ **Always Invalidate**:
1626
+
1627
+ - After creating new resources
1628
+ - After deleting resources
1629
+ - When data might be stale
1630
+
1631
+ **Example**:
1632
+
1633
+ ```typescript
1634
+ // After create
1635
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
1636
+ ```
1637
+
1638
+ #### When to Update Directly
1639
+
1640
+ **Update Directly**:
1641
+
1642
+ - After updating existing resources (you have the new data)
1643
+ - For optimistic updates
1644
+ - When you want instant UI feedback
1645
+
1646
+ **Example**:
1647
+
1648
+ ```typescript
1649
+ // After update
1650
+ SET_ACCOUNT_QUERY_DATA(queryClient, [accountId], updatedData);
1651
+ ```
1652
+
1653
+ #### Best Practices
1654
+
1655
+ 1. **Combine Both**: Invalidate lists, update individual items
1656
+
1657
+ ```typescript
1658
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
1659
+ SET_ACCOUNT_QUERY_DATA(queryClient, [accountId], data);
1660
+ ```
1661
+
1662
+ 2. **Invalidate Related Queries**: If an account update affects events, invalidate both
1663
+
1664
+ ```typescript
1665
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
1666
+ queryClient.invalidateQueries({ queryKey: EVENTS_QUERY_KEY() });
1667
+ ```
1668
+
1669
+ 3. **Use Hierarchical Keys**: Invalidating parent invalidates children
1670
+
1671
+ ```typescript
1672
+ // This invalidates all account queries
1673
+ queryClient.invalidateQueries({ queryKey: ACCOUNTS_QUERY_KEY() });
1674
+ ```
1675
+
1676
+ 4. **Check Success**: Only update cache on successful mutations
1677
+ ```typescript
1678
+ if (queryClient && data.status === "ok") {
1679
+ // Update cache
1680
+ }
1681
+ ```
1682
+
1683
+ ---
1684
+
1685
+ ## 9. Testing Patterns
1686
+
1687
+ ### 9.1 Query Testing
1688
+
1689
+ #### Mocking AdminAPI
1690
+
1691
+ ```typescript
1692
+ import { vi } from "vitest";
1693
+ import { GetAdminAPI } from "@src/AdminAPI";
1694
+
1695
+ vi.mock("@src/AdminAPI", () => ({
1696
+ GetAdminAPI: vi.fn(),
1697
+ }));
1698
+ ```
1699
+
1700
+ #### Testing Query Hooks
1701
+
1702
+ ```typescript
1703
+ import { renderHook, waitFor } from "@testing-library/react";
1704
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1705
+ import { useGetAccount } from "@src/queries";
1706
+
1707
+ test("fetches account data", async () => {
1708
+ const queryClient = new QueryClient();
1709
+ const wrapper = ({ children }) => (
1710
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
1711
+ );
1712
+
1713
+ const { result } = renderHook(() => useGetAccount("account-123"), {
1714
+ wrapper,
1715
+ });
1716
+
1717
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
1718
+ expect(result.current.data?.data.id).toBe("account-123");
1719
+ });
1720
+ ```
1721
+
1722
+ ### 9.2 Mutation Testing
1723
+
1724
+ #### Testing Mutation Hooks
1725
+
1726
+ ```typescript
1727
+ import { renderHook, waitFor } from "@testing-library/react";
1728
+ import { useCreateAccount } from "@src/mutations";
1729
+
1730
+ test("creates account and updates cache", async () => {
1731
+ const { result } = renderHook(() => useCreateAccount());
1732
+
1733
+ result.current.mutate({
1734
+ account: { email: "test@example.com" },
1735
+ });
1736
+
1737
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
1738
+ });
1739
+ ```
1740
+
1741
+ ### 9.3 Mock Patterns
1742
+
1743
+ #### Mock API Responses
1744
+
1745
+ ```typescript
1746
+ const mockAccountResponse = {
1747
+ status: "ok",
1748
+ message: "Success",
1749
+ data: {
1750
+ id: "account-123",
1751
+ email: "test@example.com",
1752
+ // ... other fields
1753
+ },
1754
+ };
1755
+
1756
+ (GetAdminAPI as any).mockResolvedValue({
1757
+ get: vi.fn().mockResolvedValue({ data: mockAccountResponse }),
1758
+ });
1759
+ ```
1760
+
1761
+ ---
1762
+
1763
+ ## 10. Build and Distribution
1764
+
1765
+ ### 10.1 Build Process
1766
+
1767
+ The SDK uses `tsup` for building:
1768
+
1769
+ ```json
1770
+ {
1771
+ "scripts": {
1772
+ "build": "tsup src/index.ts --format cjs,esm --dts"
1773
+ }
1774
+ }
1775
+ ```
1776
+
1777
+ **Output**:
1778
+
1779
+ - CommonJS: `dist/index.cjs`
1780
+ - ES Modules: `dist/index.js`
1781
+ - Type Definitions: `dist/index.d.ts`
1782
+
1783
+ ### 10.2 Export Generation
1784
+
1785
+ The `exports` script generates barrel exports:
1786
+
1787
+ ```bash
1788
+ npm run exports
1789
+ ```
1790
+
1791
+ This ensures all queries and mutations are properly exported from the main `index.ts`.
1792
+
1793
+ ### 10.3 Package Structure
1794
+
1795
+ ```
1796
+ dist/
1797
+ ├── index.js # ES Module build
1798
+ ├── index.cjs # CommonJS build
1799
+ ├── index.d.ts # TypeScript definitions
1800
+ └── index.d.cts # CommonJS TypeScript definitions
1801
+ ```
1802
+
1803
+ ### 10.4 Versioning
1804
+
1805
+ - Follows semantic versioning (MAJOR.MINOR.PATCH)
1806
+ - Current version in `package.json`
1807
+ - Beta versions: `6.1.11-beta.1`
1808
+
1809
+ ### 10.5 Publishing
1810
+
1811
+ 1. Update version in `package.json`
1812
+ 2. Run `npm run release` (lint + build)
1813
+ 3. Test locally with `npm run local` (creates `.tgz` file)
1814
+ 4. Publish to npm registry
1815
+
1816
+ ---
1817
+
1818
+ ## Additional Resources
1819
+
1820
+ ### Key Files Reference
1821
+
1822
+ - `src/interfaces.ts` - All TypeScript interfaces and enums
1823
+ - `src/params.ts` - Input parameter types for mutations
1824
+ - `src/queries/accounts/useGetAccount.ts` - Single query example
1825
+ - `src/queries/accounts/useGetAccounts.ts` - Infinite query example
1826
+ - `src/mutations/account/useCreateAccount.ts` - Mutation example
1827
+ - `src/mutations/account/useUpdateAccount.ts` - Update mutation example
1828
+ - `src/AdminAPI.ts` - API client setup
1829
+ - `src/ConnectedXMProvider.tsx` - Provider setup
1830
+ - `src/utilities/CacheIndividualQueries.ts` - Cache utility example
1831
+
1832
+ ### Common Patterns Quick Reference
1833
+
1834
+ **Query Pattern**:
1835
+
1836
+ ```typescript
1837
+ QUERY_KEY → SETTER → QUERY_FUNCTION → REACT_HOOK
1838
+ ```
1839
+
1840
+ **Mutation Pattern**:
1841
+
1842
+ ```typescript
1843
+ PARAMS_INTERFACE → MUTATION_FUNCTION → REACT_HOOK
1844
+ ```
1845
+
1846
+ **Cache Update Pattern**:
1847
+
1848
+ ```typescript
1849
+ invalidateQueries() + SET_ * _QUERY_DATA();
1850
+ ```
1851
+
1852
+ ---
1853
+
1854
+ ## Questions or Issues?
1855
+
1856
+ For questions about this SDK or to report issues, please contact the ConnectedXM development team or refer to the main repository documentation.