@connectedxm/admin 6.1.10 → 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.
- package/DEVELOPER_DOCUMENTATION.md +1792 -0
- package/dist/index.cjs +68 -1385
- package/dist/index.d.cts +29 -797
- package/dist/index.d.ts +29 -797
- package/dist/index.js +65 -1311
- package/package.json +1 -1
|
@@ -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
|
+
|