@fluentcommerce/fc-connect-sdk 0.1.48 → 0.1.52
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/CHANGELOG.md +506 -379
- package/README.md +343 -0
- package/dist/cjs/clients/fluent-client.js +110 -14
- package/dist/cjs/data-sources/s3-data-source.js +1 -1
- package/dist/cjs/data-sources/sftp-data-source.js +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/services/extraction/extraction-orchestrator.d.ts +4 -1
- package/dist/cjs/services/extraction/extraction-orchestrator.js +84 -11
- package/dist/cjs/types/index.d.ts +79 -10
- package/dist/cjs/versori/fluent-versori-client.d.ts +4 -1
- package/dist/cjs/versori/fluent-versori-client.js +131 -13
- package/dist/esm/clients/fluent-client.js +110 -14
- package/dist/esm/data-sources/s3-data-source.js +1 -1
- package/dist/esm/data-sources/sftp-data-source.js +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/services/extraction/extraction-orchestrator.d.ts +4 -1
- package/dist/esm/services/extraction/extraction-orchestrator.js +84 -11
- package/dist/esm/types/index.d.ts +79 -10
- package/dist/esm/versori/fluent-versori-client.d.ts +4 -1
- package/dist/esm/versori/fluent-versori-client.js +131 -13
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/services/extraction/extraction-orchestrator.d.ts +4 -1
- package/dist/types/types/index.d.ts +79 -10
- package/dist/types/versori/fluent-versori-client.d.ts +4 -1
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +478 -18
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +83 -0
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +52 -0
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -0
- package/docs/02-CORE-GUIDES/api-reference/readme.md +1 -1
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +68 -4
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-01-foundations.md +450 -448
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-02-quick-start.md +476 -474
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-03-schema-validation.md +464 -462
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-05-advanced-patterns.md +1366 -1364
- package/docs/readme.md +245 -245
- package/package.json +17 -6
- package/docs/versori-apis/ACTIVATIONS-AND-VARIABLES-GUIDE.md +0 -60
- package/docs/versori-apis/JWT-GENERATION-GUIDE.md +0 -94
- package/docs/versori-apis/QUICK-WORKFLOW.md +0 -293
- package/docs/versori-apis/README.md +0 -73
- package/docs/versori-apis/VERSORI-PLATFORM-ARCHITECTURE.md +0 -880
- package/docs/versori-apis/Versori-Platform-API.postman_collection.json +0 -2925
- package/docs/versori-apis/Versori-Platform-API.postman_environment.example.json +0 -62
- package/docs/versori-apis/Versori-Platform-API.postman_environment.json +0 -178
|
@@ -77,6 +77,58 @@ if (result.errors) {
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
### FluentEventQueryParams
|
|
81
|
+
|
|
82
|
+
Query params for Event Log/Audit search (`GET /api/v4.1/event`).
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface FluentEventQueryParams {
|
|
86
|
+
start?: number;
|
|
87
|
+
count?: number;
|
|
88
|
+
from?: string;
|
|
89
|
+
to?: string;
|
|
90
|
+
name?: string;
|
|
91
|
+
category?: string;
|
|
92
|
+
retailerId?: string | number;
|
|
93
|
+
eventType?: string;
|
|
94
|
+
eventStatus?: string;
|
|
95
|
+
'context.rootEntityType'?: string;
|
|
96
|
+
'context.rootEntityId'?: string | number;
|
|
97
|
+
'context.rootEntityRef'?: string;
|
|
98
|
+
'context.entityType'?: string;
|
|
99
|
+
'context.entityId'?: string | number;
|
|
100
|
+
'context.entityRef'?: string;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### FluentEventLogResponse
|
|
105
|
+
|
|
106
|
+
Response from Event Log search (`client.getEvents()`).
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface FluentEventLogResponse {
|
|
110
|
+
start: number;
|
|
111
|
+
count: number;
|
|
112
|
+
hasMore: boolean;
|
|
113
|
+
results: FluentEventLogItem[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface FluentEventLogItem {
|
|
117
|
+
id: string;
|
|
118
|
+
name: string;
|
|
119
|
+
type?: string;
|
|
120
|
+
accountId?: string;
|
|
121
|
+
retailerId?: string;
|
|
122
|
+
category?: string;
|
|
123
|
+
context?: FluentEventLogContext;
|
|
124
|
+
eventStatus?: string;
|
|
125
|
+
attributes?: FluentEventLogAttribute[] | Record<string, AttributeValue> | null;
|
|
126
|
+
source?: string | null;
|
|
127
|
+
generatedBy?: string;
|
|
128
|
+
generatedOn?: string;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
80
132
|
## Enums
|
|
81
133
|
|
|
82
134
|
### BatchAction
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# GraphQL Partial Response Support
|
|
2
|
+
|
|
3
|
+
> **Version:** 0.1.51+
|
|
4
|
+
> **Status:** Stable
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
By default, the SDK throws a `GraphQLExecutionError` when a GraphQL response contains any errors. This "fail-fast" behavior is intentional for data integration scenarios where partial data could lead to inconsistencies.
|
|
9
|
+
|
|
10
|
+
However, GraphQL allows responses to contain **both** `data` AND `errors` simultaneously (partial responses). This feature allows you to opt-in to receiving partial data when appropriate for your use case.
|
|
11
|
+
|
|
12
|
+
## Default Behavior (Backward Compatible)
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// Default: throws on ANY GraphQL errors
|
|
16
|
+
const result = await client.graphql({
|
|
17
|
+
query: `
|
|
18
|
+
mutation {
|
|
19
|
+
product1: createProduct(input: { ref: "SKU1" }) { id }
|
|
20
|
+
product2: createProduct(input: { ref: "SKU2" }) { id }
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
});
|
|
24
|
+
// If product2 fails, throws GraphQLExecutionError
|
|
25
|
+
// Data from product1 is lost
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Opt-In Partial Response Mode
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Partial mode: returns both data and errors
|
|
32
|
+
const result = await client.graphql({
|
|
33
|
+
query: `
|
|
34
|
+
mutation {
|
|
35
|
+
product1: createProduct(input: { ref: "SKU1" }) { id }
|
|
36
|
+
product2: createProduct(input: { ref: "SKU2" }) { id }
|
|
37
|
+
}
|
|
38
|
+
`,
|
|
39
|
+
errorHandling: 'partial' // <-- Opt-in
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Check for partial success
|
|
43
|
+
if (result.hasPartialData) {
|
|
44
|
+
console.log('Partial success:', {
|
|
45
|
+
data: result.data, // { product1: { id: '123' }, product2: null }
|
|
46
|
+
errors: result.errors, // [{ message: 'SKU2 already exists', path: ['product2'] }]
|
|
47
|
+
errorCount: result.errors?.length,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Process successful items
|
|
51
|
+
if (result.data?.product1) {
|
|
52
|
+
console.log('Product 1 created:', result.data.product1.id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Log failed items for retry or investigation
|
|
56
|
+
for (const error of result.errors || []) {
|
|
57
|
+
console.warn('Failed mutation:', error.path?.[0], error.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### GraphQLPayload Options
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
interface GraphQLPayload {
|
|
68
|
+
query: string;
|
|
69
|
+
variables?: Record<string, any>;
|
|
70
|
+
operationName?: string;
|
|
71
|
+
pagination?: PaginationConfig;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How to handle GraphQL errors in the response.
|
|
75
|
+
* @default 'throw' - Throws GraphQLExecutionError on any errors
|
|
76
|
+
* @since 0.1.51 Set to 'partial' to receive both data and errors
|
|
77
|
+
*/
|
|
78
|
+
errorHandling?: 'throw' | 'partial';
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### GraphQLResponse Extensions
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface GraphQLResponse<T> {
|
|
86
|
+
data?: T;
|
|
87
|
+
errors?: GraphQLError[];
|
|
88
|
+
extensions?: Record<string, unknown>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True when response contains both data AND errors.
|
|
92
|
+
* Only present when errorHandling: 'partial' is used and errors occurred.
|
|
93
|
+
* @since 0.1.51
|
|
94
|
+
*/
|
|
95
|
+
hasPartialData?: boolean;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## With Pagination
|
|
100
|
+
|
|
101
|
+
Partial response mode works with auto-pagination. Errors from all pages are accumulated:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const result = await client.graphql({
|
|
105
|
+
query: PAGINATED_QUERY,
|
|
106
|
+
variables: { first: 100 },
|
|
107
|
+
errorHandling: 'partial'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Errors from all pages are collected
|
|
111
|
+
if (result.hasPartialData) {
|
|
112
|
+
console.log(`Fetched data with ${result.errors?.length} errors across pages`);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## With ExtractionOrchestrator
|
|
117
|
+
|
|
118
|
+
The ExtractionOrchestrator also supports partial responses:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const orchestrator = new ExtractionOrchestrator(client, logger);
|
|
122
|
+
|
|
123
|
+
const result = await orchestrator.extract({
|
|
124
|
+
query: PRODUCTS_QUERY,
|
|
125
|
+
resultPath: 'products.edges.node',
|
|
126
|
+
errorHandling: 'partial', // Continue extraction even with errors
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Check for partial errors in stats
|
|
130
|
+
if (result.stats.partialErrors) {
|
|
131
|
+
logger.warn('Extraction completed with errors', {
|
|
132
|
+
recordsExtracted: result.data.length,
|
|
133
|
+
errorCount: result.stats.partialErrors.length,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Edge Case: Null Data with Errors
|
|
139
|
+
|
|
140
|
+
When GraphQL returns errors with `data: null` or `data: undefined`, the ExtractionOrchestrator handles this gracefully in partial mode:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// GraphQL response: { data: null, errors: [{ message: 'Order item not found' }] }
|
|
144
|
+
const result = await orchestrator.extract({
|
|
145
|
+
query: ORDERS_QUERY,
|
|
146
|
+
resultPath: 'orders.edges.node',
|
|
147
|
+
errorHandling: 'partial',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Returns empty array with errors in stats
|
|
151
|
+
expect(result.data).toEqual([]);
|
|
152
|
+
expect(result.stats.partialErrors).toHaveLength(1);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Behavior:**
|
|
156
|
+
- **Partial mode**: Returns empty array `[]`, accumulates errors in `stats.partialErrors`, stops pagination if no data
|
|
157
|
+
- **Throw mode** (default): Throws `GraphQLExecutionError` immediately (backward compatible)
|
|
158
|
+
|
|
159
|
+
This ensures that partial mode never throws due to null data - it always returns available data (even if empty) along with accumulated errors.
|
|
160
|
+
|
|
161
|
+
## When to Use Partial Responses
|
|
162
|
+
|
|
163
|
+
| Scenario | Recommendation |
|
|
164
|
+
|----------|----------------|
|
|
165
|
+
| Single record operations | Use default `'throw'` |
|
|
166
|
+
| Batch mutations where failures are independent | Consider `'partial'` |
|
|
167
|
+
| Read-only queries where partial data is useful | Consider `'partial'` |
|
|
168
|
+
| Critical data where consistency matters | Use default `'throw'` |
|
|
169
|
+
| Reporting/analytics where some data is better than none | Consider `'partial'` |
|
|
170
|
+
|
|
171
|
+
## Error Classification
|
|
172
|
+
|
|
173
|
+
When using partial mode, you can classify errors for better handling:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { classifyErrors } from '@fluentcommerce/fc-connect-sdk';
|
|
177
|
+
|
|
178
|
+
const result = await client.graphql({
|
|
179
|
+
query: batchMutation,
|
|
180
|
+
errorHandling: 'partial'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (result.hasPartialData && result.errors) {
|
|
184
|
+
const classified = classifyErrors(result.errors);
|
|
185
|
+
|
|
186
|
+
// Handle retryable errors
|
|
187
|
+
if (classified.retryable.length > 0) {
|
|
188
|
+
console.log('Retryable errors:', classified.retryable);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle permanent failures
|
|
192
|
+
if (classified.permanent.length > 0) {
|
|
193
|
+
console.log('Permanent failures:', classified.permanent);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Migration Guide
|
|
199
|
+
|
|
200
|
+
No migration needed - the default behavior remains unchanged. To adopt partial responses:
|
|
201
|
+
|
|
202
|
+
1. Add `errorHandling: 'partial'` to your GraphQL calls
|
|
203
|
+
2. Check `result.hasPartialData` to detect partial success
|
|
204
|
+
3. Handle `result.errors` array for failed operations
|
|
205
|
+
4. Update your logging and monitoring to track partial successes
|
|
206
|
+
|
|
207
|
+
## Best Practices
|
|
208
|
+
|
|
209
|
+
1. **Always check `hasPartialData`**: Don't assume success just because no exception was thrown
|
|
210
|
+
2. **Log partial errors**: Even in partial mode, errors should be logged for investigation
|
|
211
|
+
3. **Consider retry logic**: Partial failures may benefit from retrying failed items
|
|
212
|
+
4. **Monitor partial success rates**: Track how often partial responses occur in your workflows
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
|
|
69
69
|
The main entry point for SDK operations. Learn how to create clients, execute GraphQL operations, send events, and manage batch jobs.
|
|
70
70
|
|
|
71
|
-
**Key APIs:** `createClient()`, `FluentClient`, `graphql(payload)`, `sendEvent()`, `createJob()`, `sendBatch()`
|
|
71
|
+
**Key APIs:** `createClient()`, `FluentClient`, `graphql(payload)`, `sendEvent()`, `getEvents()`, `getEventById()`, `createJob()`, `sendBatch()`
|
|
72
72
|
|
|
73
73
|
[View Module →](./modules/api-reference-01-client-api.md)
|
|
74
74
|
|
|
@@ -290,13 +290,14 @@ interface ExtractionOptions<T = any> {
|
|
|
290
290
|
timeout?: number; // Query timeout ms (default: 60000)
|
|
291
291
|
direction?: 'forward' | 'backward'; // Pagination direction (default: 'forward')
|
|
292
292
|
validateItem?: (item: T) => boolean; // Optional per-item validation
|
|
293
|
+
errorHandling?: 'throw' | 'partial'; // How to handle GraphQL errors (default: 'throw')
|
|
294
|
+
operationName?: string; // Optional operation name for logging
|
|
293
295
|
}
|
|
294
296
|
```
|
|
295
297
|
|
|
296
|
-
**
|
|
297
|
-
- `'forward'` (default)
|
|
298
|
-
- `'
|
|
299
|
-
- Your GraphQL query must match the direction (don't mix `first/after` with `direction: 'backward'`)
|
|
298
|
+
**Notes:**
|
|
299
|
+
- `direction`: `'forward'` (default) uses `first/after` params, requires `pageInfo.hasNextPage`. `'backward'` uses `last/before` params, requires `pageInfo.hasPreviousPage`. Your GraphQL query must match the direction (don't mix `first/after` with `direction: 'backward'`)
|
|
300
|
+
- `errorHandling`: `'throw'` (default) throws `GraphQLExecutionError` on any errors. `'partial'` continues extraction and accumulates errors in `stats.partialErrors`. See [Partial Response Support](../../api-reference/modules/api-reference-12-partial-responses.md) for details.
|
|
300
301
|
|
|
301
302
|
### ExtractionResult
|
|
302
303
|
|
|
@@ -316,6 +317,7 @@ interface ExtractionStats {
|
|
|
316
317
|
validRecords?: number; // Valid records (if validation used)
|
|
317
318
|
invalidRecords?: number; // Invalid records (if validation used)
|
|
318
319
|
direction?: 'forward' | 'backward'; // Pagination direction used
|
|
320
|
+
partialErrors?: GraphQLError[]; // GraphQL errors (only when errorHandling: 'partial')
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
interface ExtractionError {
|
|
@@ -715,6 +717,68 @@ try {
|
|
|
715
717
|
}
|
|
716
718
|
```
|
|
717
719
|
|
|
720
|
+
### Partial Response Mode (errorHandling: 'partial')
|
|
721
|
+
|
|
722
|
+
When GraphQL returns errors but some data is available, you can use partial mode to continue extraction:
|
|
723
|
+
|
|
724
|
+
```typescript
|
|
725
|
+
const result = await orchestrator.extract({
|
|
726
|
+
query: ORDERS_QUERY,
|
|
727
|
+
resultPath: 'orders.edges.node',
|
|
728
|
+
errorHandling: 'partial', // Continue extraction even with errors
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Check for partial errors in stats
|
|
732
|
+
if (result.stats.partialErrors) {
|
|
733
|
+
logger.warn('Extraction completed with partial errors', {
|
|
734
|
+
recordsExtracted: result.data.length,
|
|
735
|
+
errorCount: result.stats.partialErrors.length,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// Process available data
|
|
739
|
+
console.log(`Extracted ${result.data.length} records despite errors`);
|
|
740
|
+
|
|
741
|
+
// Log errors for investigation
|
|
742
|
+
result.stats.partialErrors.forEach(err => {
|
|
743
|
+
logger.warn('GraphQL error during extraction', {
|
|
744
|
+
message: err.message,
|
|
745
|
+
path: err.path,
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**Edge Case: Null Data with Errors**
|
|
752
|
+
|
|
753
|
+
When GraphQL returns errors with `data: null` or `data: undefined`:
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
// GraphQL response: { data: null, errors: [{ message: 'Order item not found' }] }
|
|
757
|
+
const result = await orchestrator.extract({
|
|
758
|
+
query: ORDERS_QUERY,
|
|
759
|
+
resultPath: 'orders.edges.node',
|
|
760
|
+
errorHandling: 'partial',
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Returns empty array with errors in stats
|
|
764
|
+
expect(result.data).toEqual([]);
|
|
765
|
+
expect(result.stats.partialErrors).toHaveLength(1);
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
**Behavior:**
|
|
769
|
+
- **Partial mode**: Returns empty array `[]`, accumulates errors in `stats.partialErrors`, stops pagination if no data
|
|
770
|
+
- **Throw mode** (default): Throws `GraphQLExecutionError` immediately (backward compatible)
|
|
771
|
+
|
|
772
|
+
**When to Use Partial Mode:**
|
|
773
|
+
- ✅ Batch queries where some items may fail independently
|
|
774
|
+
- ✅ Reporting/analytics where partial data is better than none
|
|
775
|
+
- ✅ Non-critical extractions where you want to process available data
|
|
776
|
+
|
|
777
|
+
**When to Use Throw Mode (default):**
|
|
778
|
+
- ✅ Critical data where consistency matters
|
|
779
|
+
- ✅ Single record operations
|
|
780
|
+
- ✅ When you need to fail fast on any errors
|
|
781
|
+
|
|
718
782
|
### Common Error Scenarios
|
|
719
783
|
|
|
720
784
|
| Error | Cause | Solution |
|