@connectedxm/admin 6.1.11 → 6.2.0

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