@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
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ TypeScript SDK for building **Fluent Commerce** integrations across Node.js, Den
|
|
|
32
32
|
- [Core Services](#core-services) - API reference
|
|
33
33
|
- [CLI Tools](#cli-tooling) - Command-line utilities
|
|
34
34
|
- [Authentication & Webhooks](#authentication-webhooks) - Security
|
|
35
|
+
- [Security & Compliance](#security--compliance) - Security audits, SOC 2, vulnerability management 🆕
|
|
35
36
|
- [Common Pitfalls](#common-pitfalls-and-fixes) - Troubleshooting
|
|
36
37
|
- [Support](#additional-resources) - Help & resources
|
|
37
38
|
|
|
@@ -146,6 +147,7 @@ Choose the right Fluent Commerce API for your use case:
|
|
|
146
147
|
| **Location setup** (stores, warehouses) | **Event API** | `sendEvent()` | Requires workflow orchestration |
|
|
147
148
|
| **Order creation** (one-time orders) | **GraphQL** | `client.graphql()` with `createOrder` mutation | Triggers Order CREATED event, full control |
|
|
148
149
|
| **Order updates/events** (status changes) | **Event API** | `sendEvent()` | Triggers workflows for order state changes |
|
|
150
|
+
| **Audit/search event history** (trace flows, investigate failures) | **Event API (GET)** | `getEvents()` + `getEventById()` | Read-only event/audit retrieval with filters and pagination |
|
|
149
151
|
| **Customer data** (registration, profiles) | **GraphQL** | `client.graphql()` with mutations | No Rubix workflow support for customers |
|
|
150
152
|
| **Data extraction** (export to S3/SFTP) | **GraphQL** | `client.graphql()` + `ExtractionOrchestrator` | Query with auto-pagination, extract thousands of records |
|
|
151
153
|
| **Single operations** (create one product, update one location) | **GraphQL** | `client.graphql()` | Direct control, immediate feedback |
|
|
@@ -1114,6 +1116,27 @@ console.log(
|
|
|
1114
1116
|
// result.stats = { totalPages: 9, totalRecords: 847, truncated: false }
|
|
1115
1117
|
```
|
|
1116
1118
|
|
|
1119
|
+
**Handling Partial Responses (Errors with Data):**
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
// When GraphQL returns errors but some data is available
|
|
1123
|
+
const result = await orchestrator.extract({
|
|
1124
|
+
query: ORDERS_QUERY,
|
|
1125
|
+
resultPath: 'orders.edges.node',
|
|
1126
|
+
errorHandling: 'partial', // Continue extraction even with errors
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
// Check for partial errors
|
|
1130
|
+
if (result.stats.partialErrors) {
|
|
1131
|
+
console.warn(`Extraction completed with ${result.stats.partialErrors.length} errors`);
|
|
1132
|
+
console.log(`Still extracted ${result.data.length} records`);
|
|
1133
|
+
// Errors are in result.stats.partialErrors
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Edge case: If GraphQL returns errors with null data, result.data will be []
|
|
1137
|
+
// but errors are still available in result.stats.partialErrors
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1117
1140
|
**Alternative: Using FluentClient (For Custom Response Handling)**
|
|
1118
1141
|
|
|
1119
1142
|
```typescript
|
|
@@ -1195,6 +1218,222 @@ console.log('✅ Event sent successfully');
|
|
|
1195
1218
|
- **Event API Templates:** `docs/01-TEMPLATES/versori/workflows/ingestion/event-api/` (8 templates for S3/SFTP with CSV, XML, JSON, Parquet)
|
|
1196
1219
|
- **Batch API Guide:** `docs/02-CORE-GUIDES/ingestion/modules/02-CORE-GUIDES-ingestion-06-batch-api.md`
|
|
1197
1220
|
|
|
1221
|
+
### Event Log API (Search + Audit)
|
|
1222
|
+
|
|
1223
|
+
Query and retrieve events/audit logs from the Fluent Commerce REST Event API. Use these **read-only** methods for troubleshooting workflows, tracing event chains, monitoring orchestration, and auditing batch processing.
|
|
1224
|
+
|
|
1225
|
+
**Methods:**
|
|
1226
|
+
- `getEvents(params)` → `GET /api/v4.1/event` - Search/filter events with flexible query parameters
|
|
1227
|
+
- `getEventById(eventId)` → `GET /api/v4.1/event/{eventId}` - Get a single event by ID
|
|
1228
|
+
|
|
1229
|
+
**Important:** These methods query the Event Log (audit trail). They do NOT trigger workflows — use `sendEvent()` for that.
|
|
1230
|
+
|
|
1231
|
+
#### Basic Usage
|
|
1232
|
+
|
|
1233
|
+
```typescript
|
|
1234
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1235
|
+
|
|
1236
|
+
const client = await createClient({
|
|
1237
|
+
config: { baseUrl, clientId, clientSecret, username, password },
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
// 1) Search for failed orchestration audit events on orders
|
|
1241
|
+
const logs = await client.getEvents({
|
|
1242
|
+
'context.rootEntityType': 'ORDER',
|
|
1243
|
+
eventType: 'ORCHESTRATION_AUDIT',
|
|
1244
|
+
eventStatus: 'FAILED',
|
|
1245
|
+
count: 100,
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
console.log(`Found ${logs.results.length} events (hasMore=${logs.hasMore})`);
|
|
1249
|
+
|
|
1250
|
+
for (const event of logs.results) {
|
|
1251
|
+
console.log(` ${event.name} [${event.eventStatus}] on ${event.context?.entityType}:${event.context?.entityRef}`);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// 2) Get full details for a specific event by ID
|
|
1255
|
+
if (logs.results[0]?.id) {
|
|
1256
|
+
const event = await client.getEventById(logs.results[0].id);
|
|
1257
|
+
console.log(`Event: ${event.name} - Status: ${event.eventStatus}`);
|
|
1258
|
+
console.log(` Entity: ${event.context?.entityType}:${event.context?.entityRef}`);
|
|
1259
|
+
console.log(` Root: ${event.context?.rootEntityType}:${event.context?.rootEntityRef}`);
|
|
1260
|
+
console.log(` Generated: ${event.generatedOn} by ${event.generatedBy}`);
|
|
1261
|
+
|
|
1262
|
+
// Trace parent events (for debugging event chains)
|
|
1263
|
+
if (event.context?.sourceEvents?.length) {
|
|
1264
|
+
for (const parentId of event.context.sourceEvents) {
|
|
1265
|
+
const parent = await client.getEventById(parentId);
|
|
1266
|
+
console.log(` Parent: ${parent.name} (${parent.eventStatus})`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
#### Response Shape
|
|
1273
|
+
|
|
1274
|
+
`getEvents()` returns `FluentEventLogResponse`:
|
|
1275
|
+
|
|
1276
|
+
```typescript
|
|
1277
|
+
{
|
|
1278
|
+
start: 1, // Pagination offset
|
|
1279
|
+
count: 1000, // Results in this page
|
|
1280
|
+
hasMore: false, // More pages available?
|
|
1281
|
+
results: [ // Array of FluentEventLogItem
|
|
1282
|
+
{
|
|
1283
|
+
id: "e2cc5040-...", // UUID
|
|
1284
|
+
name: "BATCH_COMPLETE", // Event/ruleset name (can be null)
|
|
1285
|
+
type: "ORCHESTRATION_AUDIT",
|
|
1286
|
+
accountId: "MYACCOUNT",
|
|
1287
|
+
retailerId: "5", // String in response (not number)
|
|
1288
|
+
category: "BATCH",
|
|
1289
|
+
context: {
|
|
1290
|
+
sourceEvents: ["babc5f37-..."], // Parent event IDs for tracing
|
|
1291
|
+
entityType: "BATCH",
|
|
1292
|
+
entityId: "12",
|
|
1293
|
+
entityRef: "12",
|
|
1294
|
+
rootEntityType: "JOB",
|
|
1295
|
+
rootEntityId: "13",
|
|
1296
|
+
rootEntityRef: "13"
|
|
1297
|
+
},
|
|
1298
|
+
eventStatus: "COMPLETE",
|
|
1299
|
+
attributes: null, // null OR array of { name, value, type }
|
|
1300
|
+
source: null,
|
|
1301
|
+
generatedBy: "Rubix User",
|
|
1302
|
+
generatedOn: "2026-02-05T06:31:56.895+00:00"
|
|
1303
|
+
}
|
|
1304
|
+
]
|
|
1305
|
+
}
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
`getEventById(id)` returns a single `FluentEventLogItem` (same shape as each item in `results`).
|
|
1309
|
+
|
|
1310
|
+
#### Query Parameters
|
|
1311
|
+
|
|
1312
|
+
All parameters are optional. Context filters use flat dot-notation keys:
|
|
1313
|
+
|
|
1314
|
+
| Parameter | Type | Description | Example |
|
|
1315
|
+
|-----------|------|-------------|---------|
|
|
1316
|
+
| `context.rootEntityType` | string | Root entity type | `'ORDER'`, `'JOB'`, `'LOCATION'` |
|
|
1317
|
+
| `context.rootEntityId` | string | Root entity ID | `'12345'` |
|
|
1318
|
+
| `context.rootEntityRef` | string | Root entity reference | `'ORD-12345'` |
|
|
1319
|
+
| `context.entityType` | string | Sub-entity type | `'FULFILMENT'`, `'BATCH'`, `'ARTICLE'` |
|
|
1320
|
+
| `context.entityId` | string | Sub-entity ID | `'67890'` |
|
|
1321
|
+
| `context.entityRef` | string | Sub-entity reference | `'FUL-67890'` |
|
|
1322
|
+
| `eventType` | string | Event type filter | `'ORCHESTRATION_AUDIT'` |
|
|
1323
|
+
| `eventStatus` | string | Status filter | `'FAILED'`, `'COMPLETE'` |
|
|
1324
|
+
| `name` | string | Event/ruleset name | `'BATCH_COMPLETE'` |
|
|
1325
|
+
| `category` | string | Category filter | `'ruleSet'`, `'ACTION'` |
|
|
1326
|
+
| `from` | string | Start date (UTC ISO 8601) | `'2026-02-01T00:00:00.000Z'` |
|
|
1327
|
+
| `to` | string | End date (UTC ISO 8601) | `'2026-02-15T23:59:59.999Z'` |
|
|
1328
|
+
| `retailerId` | string/number | Retailer filter (falls back to client config) | `'5'` |
|
|
1329
|
+
| `start` | number | Pagination offset (default: `0`) | `0` |
|
|
1330
|
+
| `count` | number | Results per page (default: `100`, max: `5000`) | `1000` |
|
|
1331
|
+
|
|
1332
|
+
#### Valid Values Reference
|
|
1333
|
+
|
|
1334
|
+
| Parameter | Valid Values |
|
|
1335
|
+
|-----------|-------------|
|
|
1336
|
+
| **eventType** | `ORCHESTRATION`, `ORCHESTRATION_AUDIT`, `API`, `INTEGRATION`, `SECURITY`, `GENERAL` |
|
|
1337
|
+
| **eventStatus** | `PENDING`, `SCHEDULED`, `NO_MATCH`, `SUCCESS`, `FAILED`, `COMPLETE` |
|
|
1338
|
+
| **category** | `snapshot`, `ruleSet`, `rule`, `ACTION`, `CUSTOM`, `exception`, `ORDER_WORKFLOW`, `BATCH` |
|
|
1339
|
+
| **rootEntityType** | `ORDER`, `LOCATION`, `FULFILMENT_OPTIONS`, `PRODUCT_CATALOGUE`, `INVENTORY_CATALOGUE`, `VIRTUAL_CATALOGUE`, `CONTROL_GROUP`, `RETURN_ORDER`, `BILLING_ACCOUNT`, `JOB` |
|
|
1340
|
+
| **entityType** | `ORDER`, `FULFILMENT`, `ARTICLE`, `CONSIGNMENT`, `LOCATION`, `WAVE`, `FULFILMENT_OPTIONS`, `FULFILMENT_PLAN`, `PRODUCT_CATALOGUE`, `CATEGORY`, `PRODUCT`, `INVENTORY_CATALOGUE`, `INVENTORY_POSITION`, `INVENTORY_QUANTITY`, `VIRTUAL_CATALOGUE`, `VIRTUAL_POSITION`, `CONTROL_GROUP`, `CONTROL`, `RETURN_ORDER`, `RETURN_FULFILMENT`, `BILLING_ACCOUNT`, `CREDIT_MEMO`, `BATCH` |
|
|
1341
|
+
|
|
1342
|
+
#### Common Use Cases
|
|
1343
|
+
|
|
1344
|
+
```typescript
|
|
1345
|
+
// 1. Find failed events in the last 24 hours
|
|
1346
|
+
const failedEvents = await client.getEvents({
|
|
1347
|
+
eventStatus: 'FAILED',
|
|
1348
|
+
from: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
1349
|
+
to: new Date().toISOString(),
|
|
1350
|
+
count: 1000,
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// 2. Track batch job processing (all events for a specific job)
|
|
1354
|
+
const batchEvents = await client.getEvents({
|
|
1355
|
+
'context.rootEntityType': 'JOB',
|
|
1356
|
+
'context.rootEntityId': '13',
|
|
1357
|
+
eventType: 'ORCHESTRATION_AUDIT',
|
|
1358
|
+
count: 500,
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
// 3. Paginate through large result sets
|
|
1362
|
+
let start = 0;
|
|
1363
|
+
const allEvents: any[] = [];
|
|
1364
|
+
let hasMore = true;
|
|
1365
|
+
|
|
1366
|
+
while (hasMore) {
|
|
1367
|
+
const page = await client.getEvents({
|
|
1368
|
+
'context.rootEntityType': 'ORDER',
|
|
1369
|
+
start,
|
|
1370
|
+
count: 1000,
|
|
1371
|
+
});
|
|
1372
|
+
allEvents.push(...page.results);
|
|
1373
|
+
hasMore = page.hasMore;
|
|
1374
|
+
start += page.count;
|
|
1375
|
+
}
|
|
1376
|
+
console.log(`Total events collected: ${allEvents.length}`);
|
|
1377
|
+
|
|
1378
|
+
// 4. Extract performance timing from event attributes
|
|
1379
|
+
const auditEvents = await client.getEvents({
|
|
1380
|
+
eventType: 'ORCHESTRATION_AUDIT',
|
|
1381
|
+
'context.rootEntityType': 'JOB',
|
|
1382
|
+
count: 100,
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
for (const event of auditEvents.results) {
|
|
1386
|
+
if (Array.isArray(event.attributes)) {
|
|
1387
|
+
const startTimer = event.attributes.find(a => a.name === 'startTimer');
|
|
1388
|
+
const stopTimer = event.attributes.find(a => a.name === 'stopTimer');
|
|
1389
|
+
if (startTimer && stopTimer) {
|
|
1390
|
+
const duration = Number(stopTimer.value) - Number(startTimer.value);
|
|
1391
|
+
console.log(`${event.name}: ${duration}ms`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// 5. Find events for a specific order by reference
|
|
1397
|
+
const orderEvents = await client.getEvents({
|
|
1398
|
+
'context.rootEntityType': 'ORDER',
|
|
1399
|
+
'context.rootEntityRef': 'HD_12345',
|
|
1400
|
+
eventType: 'ORCHESTRATION_AUDIT',
|
|
1401
|
+
});
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
#### When to Use Which Method
|
|
1405
|
+
|
|
1406
|
+
| Method | Use When | Returns | Example |
|
|
1407
|
+
|--------|----------|---------|---------|
|
|
1408
|
+
| `sendEvent()` | **Trigger** a workflow or business action | Event creation response | Product upsert, order cancel, location update |
|
|
1409
|
+
| `getEvents()` | **Search/filter** historical events | `{ results[], hasMore, start, count }` | Find failed events, audit batch processing |
|
|
1410
|
+
| `getEventById()` | **Get full details** for one event by ID | Single event object | Drill into context, attributes, trace sourceEvents |
|
|
1411
|
+
|
|
1412
|
+
#### Limitations & Operational Notes
|
|
1413
|
+
|
|
1414
|
+
| Constraint | Value | Notes |
|
|
1415
|
+
|------------|-------|-------|
|
|
1416
|
+
| **Max count per request** | `5000` | Use pagination (`start`/`count`/`hasMore`) for larger result sets |
|
|
1417
|
+
| **Time range (from)** | 4 months back max | API rejects queries older than ~4 months |
|
|
1418
|
+
| **Time range (to)** | 1 month forward max | API rejects future dates beyond ~1 month |
|
|
1419
|
+
| **Default time window** | Past 30 days | Applied when `from` is omitted |
|
|
1420
|
+
| **Date format** | UTC ISO 8601 | `YYYY-MM-DDTHH:mm:ss.SSSZ` |
|
|
1421
|
+
| **retailerId fallback** | Client config | If not in params, uses `client.config.retailerId` |
|
|
1422
|
+
| **attributes field** | Nullable | Can be `null` (not empty array) — always check before iterating |
|
|
1423
|
+
| **name field** | Nullable | Can be `null` for some system events |
|
|
1424
|
+
| **GraphQL** | Not available | Event queries use REST only — no GraphQL `events` root field exists |
|
|
1425
|
+
|
|
1426
|
+
**Key points:**
|
|
1427
|
+
- `getEvents()` and `getEventById()` are **read-only** — they do NOT trigger workflows
|
|
1428
|
+
- Empty `results: []` is valid (not an error) — your filter may simply match zero events
|
|
1429
|
+
- Always use bounded time ranges (`from`/`to`) for predictable performance
|
|
1430
|
+
- Use `eventType: 'ORCHESTRATION_AUDIT'` for workflow execution history
|
|
1431
|
+
- Use `context.sourceEvents` array on each event to trace parent event chains
|
|
1432
|
+
- The `attributes` field is `null` for most events; only rule/ruleset events populate it
|
|
1433
|
+
- `retailerId` in responses is a **string** (e.g., `"5"`) even though you can pass a number in params
|
|
1434
|
+
|
|
1435
|
+
**📚 Detailed Guide:** `docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md`
|
|
1436
|
+
|
|
1198
1437
|
---
|
|
1199
1438
|
|
|
1200
1439
|
### Error Classification
|
|
@@ -1386,6 +1625,110 @@ const validation = await authService.validateApiKey(
|
|
|
1386
1625
|
|
|
1387
1626
|
---
|
|
1388
1627
|
|
|
1628
|
+
## 🚀 Standalone Usage (Outside Versori)
|
|
1629
|
+
|
|
1630
|
+
**Use the SDK in your own TypeScript/Node.js applications** - not just in Versori workflows.
|
|
1631
|
+
|
|
1632
|
+
### Quick Example: Custom REST API Calls
|
|
1633
|
+
|
|
1634
|
+
```typescript
|
|
1635
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1636
|
+
|
|
1637
|
+
// Create client with OAuth2 authentication
|
|
1638
|
+
const client = await createClient({
|
|
1639
|
+
config: {
|
|
1640
|
+
baseUrl: 'https://api.fluentcommerce.com',
|
|
1641
|
+
clientId: 'FLUENT_INTEGRATION',
|
|
1642
|
+
clientSecret: 'your-client-secret',
|
|
1643
|
+
username: 'your-username',
|
|
1644
|
+
password: 'your-password',
|
|
1645
|
+
retailerId: 'your-retailer-id',
|
|
1646
|
+
},
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
// Call any REST endpoint (authentication handled automatically)
|
|
1650
|
+
const response = await client.request('/orchestration/rest/v1/plugin', {
|
|
1651
|
+
method: 'GET',
|
|
1652
|
+
headers: {
|
|
1653
|
+
'Content-Type': 'application/json',
|
|
1654
|
+
},
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
console.log('Status:', response.status);
|
|
1658
|
+
console.log('Data:', response.data);
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
### What's Handled Automatically
|
|
1662
|
+
|
|
1663
|
+
- ✅ **OAuth2 Authentication** - Token fetched and cached automatically
|
|
1664
|
+
- ✅ **Token Refresh** - Uses refresh_token when available, falls back to full auth
|
|
1665
|
+
- ✅ **401 Retry** - Automatically retries on 401 errors with fresh token
|
|
1666
|
+
- ✅ **Error Handling** - Retries 5xx errors with exponential backoff
|
|
1667
|
+
- ✅ **Any REST Endpoint** - Call any Fluent Commerce API endpoint
|
|
1668
|
+
|
|
1669
|
+
### Complete Examples
|
|
1670
|
+
|
|
1671
|
+
- **📄 Full Example:** `examples/standalone-rest-api-call.ts` - Complete working example with GET, POST, query parameters
|
|
1672
|
+
- **📚 Documentation:** `examples/README-standalone-usage.md` - Detailed guide with all options
|
|
1673
|
+
- **✅ Verification:** `examples/VERIFICATION.md` - SDK implementation verification
|
|
1674
|
+
|
|
1675
|
+
### Use Cases
|
|
1676
|
+
|
|
1677
|
+
- Custom admin tools and dashboards
|
|
1678
|
+
- Standalone scripts and automation
|
|
1679
|
+
- Integration with other systems
|
|
1680
|
+
- Testing and development tools
|
|
1681
|
+
- Any TypeScript/Node.js application
|
|
1682
|
+
|
|
1683
|
+
**📚 Learn More:** See `examples/README-standalone-usage.md` for complete documentation
|
|
1684
|
+
|
|
1685
|
+
---
|
|
1686
|
+
|
|
1687
|
+
## Security & Compliance
|
|
1688
|
+
|
|
1689
|
+
The SDK includes comprehensive security audit tools and SOC 2 compliance documentation.
|
|
1690
|
+
|
|
1691
|
+
### Quick Security Check
|
|
1692
|
+
|
|
1693
|
+
```bash
|
|
1694
|
+
# Run comprehensive security audit
|
|
1695
|
+
npm run security:check
|
|
1696
|
+
|
|
1697
|
+
# Fix vulnerabilities safely (with tests)
|
|
1698
|
+
npm run security:fix
|
|
1699
|
+
|
|
1700
|
+
# Advanced scanning with Snyk
|
|
1701
|
+
npm run security:snyk
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
### Security Tools
|
|
1705
|
+
|
|
1706
|
+
- **Security Audit Script** - Automated vulnerability scanning (`security-audit/security-audit.cjs`)
|
|
1707
|
+
- **Safe Fix Script** - Fix vulnerabilities with automatic testing (`security-audit/safe-fix.cjs`)
|
|
1708
|
+
- **Snyk Integration** - Advanced vulnerability scanning with detailed reports
|
|
1709
|
+
- **GitHub Actions** - Automated weekly security scans
|
|
1710
|
+
- **Dependabot** - Automated dependency updates
|
|
1711
|
+
|
|
1712
|
+
### Documentation
|
|
1713
|
+
|
|
1714
|
+
- **[Security Audit Quick Start](security-audit/QUICK-START.md)** - Get started with security checks
|
|
1715
|
+
- **[SOC 2 Compliance Guide](security-audit/SOC2-COMPLIANCE-GUIDE.md)** - Complete SOC 2 compliance requirements
|
|
1716
|
+
- **[Vulnerability Checking Guide](security-audit/VULNERABILITY-CHECKING.md)** - Detailed vulnerability management
|
|
1717
|
+
- **[Safe Fixing Guide](security-audit/SAFE-FIXING-GUIDE.md)** - How to fix vulnerabilities without breaking code
|
|
1718
|
+
- **[Advanced Scanning](security-audit/ADVANCED-SCANNING.md)** - Snyk and advanced security tools
|
|
1719
|
+
- **[What is the Audit?](security-audit/WHAT-IS-THE-AUDIT.md)** - Understanding security audits and risks
|
|
1720
|
+
|
|
1721
|
+
### Current Security Status
|
|
1722
|
+
|
|
1723
|
+
✅ **Production Code:** Secure - All production dependencies are secure
|
|
1724
|
+
⚠️ **Remaining:** 3 moderate vulnerabilities in transitive dependencies (monitoring)
|
|
1725
|
+
✅ **Tests:** All 3420 tests passing
|
|
1726
|
+
✅ **Build:** Successful
|
|
1727
|
+
|
|
1728
|
+
See [security-audit/FIXES-APPLIED.md](security-audit/FIXES-APPLIED.md) for detailed security status.
|
|
1729
|
+
|
|
1730
|
+
---
|
|
1731
|
+
|
|
1389
1732
|
## Common Pitfalls (and fixes)
|
|
1390
1733
|
|
|
1391
1734
|
### ⚠️ Critical Anti-Patterns
|
|
@@ -78,8 +78,9 @@ class FluentClient {
|
|
|
78
78
|
paginationEnabled,
|
|
79
79
|
direction: paginationVars.direction,
|
|
80
80
|
});
|
|
81
|
+
const errorHandling = payload.errorHandling ?? 'throw';
|
|
81
82
|
if (!paginationEnabled) {
|
|
82
|
-
return this.executeSinglePage(payload);
|
|
83
|
+
return this.executeSinglePage(payload, errorHandling);
|
|
83
84
|
}
|
|
84
85
|
let direction = payload.pagination?.direction ?? paginationVars.direction;
|
|
85
86
|
if (paginationVars.direction === 'none' && !payload.pagination?.direction) {
|
|
@@ -99,16 +100,18 @@ class FluentClient {
|
|
|
99
100
|
abortSignal: payload.pagination?.abortSignal,
|
|
100
101
|
onProgress: payload.pagination?.onProgress ?? (() => { }),
|
|
101
102
|
onWarning: payload.pagination?.onWarning ?? ((msg) => this.log('warn', msg)),
|
|
103
|
+
errorHandling,
|
|
102
104
|
};
|
|
103
105
|
this.log('info', '[FluentClient:graphql] Auto-pagination enabled', {
|
|
104
106
|
maxPages: config.maxPages,
|
|
105
107
|
maxRecords: config.maxRecords,
|
|
106
108
|
timeoutMs: config.timeoutMs,
|
|
107
109
|
direction: config.direction,
|
|
110
|
+
errorHandling: config.errorHandling,
|
|
108
111
|
});
|
|
109
112
|
return this.executeWithAutoPagination(payload, paginationVars, config);
|
|
110
113
|
}
|
|
111
|
-
async executeSinglePage(payload) {
|
|
114
|
+
async executeSinglePage(payload, errorHandling = 'throw') {
|
|
112
115
|
const operationName = payload.operationName || 'unnamed';
|
|
113
116
|
try {
|
|
114
117
|
const apiPayload = {
|
|
@@ -125,9 +128,21 @@ class FluentClient {
|
|
|
125
128
|
body: apiPayload,
|
|
126
129
|
});
|
|
127
130
|
if (response.data.errors?.length) {
|
|
131
|
+
if (errorHandling === 'partial') {
|
|
132
|
+
this.log('warn', `[FluentClient:graphql] GraphQL operation "${operationName}" returned partial data with ${response.data.errors.length} error(s)`, {
|
|
133
|
+
operationName,
|
|
134
|
+
errorMessages: response.data.errors.map(e => e.message),
|
|
135
|
+
errorCount: response.data.errors.length,
|
|
136
|
+
hasData: !!response.data.data,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
...response.data,
|
|
140
|
+
hasPartialData: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
128
143
|
this.log('error', `[FluentClient:graphql] GraphQL operation "${operationName}" returned ${response.data.errors.length} error(s)`, {
|
|
129
144
|
operationName,
|
|
130
|
-
|
|
145
|
+
errorMessages: response.data.errors.map(e => e.message),
|
|
131
146
|
errorCount: response.data.errors.length,
|
|
132
147
|
});
|
|
133
148
|
const firstError = response.data.errors[0];
|
|
@@ -152,6 +167,7 @@ class FluentClient {
|
|
|
152
167
|
let lastCursor = null;
|
|
153
168
|
let emptyPageCount = 0;
|
|
154
169
|
const MAX_EMPTY_PAGES = FluentClient.DEFAULT_PAGINATION_CONFIG.maxEmptyPages;
|
|
170
|
+
const accumulatedErrors = [];
|
|
155
171
|
while (true) {
|
|
156
172
|
if (config.abortSignal?.aborted) {
|
|
157
173
|
truncated = true;
|
|
@@ -172,17 +188,29 @@ class FluentClient {
|
|
|
172
188
|
const response = await this.executeSinglePage({
|
|
173
189
|
query: payload.query,
|
|
174
190
|
variables: currentVariables,
|
|
175
|
-
});
|
|
191
|
+
}, 'partial');
|
|
176
192
|
lastExtensions = response.extensions;
|
|
177
193
|
if (response.errors && response.errors.length > 0) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
194
|
+
if (config.errorHandling === 'partial') {
|
|
195
|
+
this.log('warn', `[FluentClient:pagination] Page ${pageNumber + 1} returned partial data with ${response.errors.length} error(s)`, {
|
|
196
|
+
errorMessages: response.errors.map(e => e.message),
|
|
197
|
+
pagesCompleted: pageNumber,
|
|
198
|
+
totalRecordsSoFar: totalRecords,
|
|
199
|
+
errorCount: response.errors.length,
|
|
200
|
+
hasData: !!response.data,
|
|
201
|
+
});
|
|
202
|
+
accumulatedErrors.push(...response.errors);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.log('error', `[FluentClient:pagination] GraphQL errors during pagination (page ${pageNumber + 1}, ${totalRecords} records fetched so far)`, {
|
|
206
|
+
errorMessages: response.errors.map(e => e.message),
|
|
207
|
+
pagesCompleted: pageNumber,
|
|
208
|
+
totalRecordsBeforeError: totalRecords,
|
|
209
|
+
errorCount: response.errors.length,
|
|
210
|
+
});
|
|
211
|
+
const firstError = response.errors[0];
|
|
212
|
+
throw new ingestion_errors_1.GraphQLExecutionError(`GraphQL error during pagination (page ${pageNumber}): ${firstError.message || 'Unknown error'}`, response.errors, payload.query, currentVariables);
|
|
213
|
+
}
|
|
186
214
|
}
|
|
187
215
|
pageNumber++;
|
|
188
216
|
const connection = (0, pagination_helpers_1.extractConnection)(response.data, config.connectionPath);
|
|
@@ -260,14 +288,17 @@ class FluentClient {
|
|
|
260
288
|
await new Promise(resolve => setTimeout(resolve, config.delayMs));
|
|
261
289
|
}
|
|
262
290
|
}
|
|
263
|
-
|
|
291
|
+
const hasPartialErrors = accumulatedErrors.length > 0;
|
|
292
|
+
this.log('info', `[FluentClient:pagination] Pagination complete (${pageNumber} pages, ${totalRecords} records${truncated ? `, truncated: ${truncationReason}` : ''}${hasPartialErrors ? `, ${accumulatedErrors.length} errors` : ''})`, {
|
|
264
293
|
totalPages: pageNumber,
|
|
265
294
|
totalRecords,
|
|
266
295
|
truncated,
|
|
267
296
|
truncationReason,
|
|
268
297
|
duration: Date.now() - startTime,
|
|
298
|
+
hasPartialErrors,
|
|
299
|
+
errorCount: accumulatedErrors.length,
|
|
269
300
|
});
|
|
270
|
-
|
|
301
|
+
const response = {
|
|
271
302
|
data: allData,
|
|
272
303
|
extensions: {
|
|
273
304
|
...(lastExtensions || {}),
|
|
@@ -280,6 +311,11 @@ class FluentClient {
|
|
|
280
311
|
},
|
|
281
312
|
},
|
|
282
313
|
};
|
|
314
|
+
if (hasPartialErrors) {
|
|
315
|
+
response.errors = accumulatedErrors;
|
|
316
|
+
response.hasPartialData = true;
|
|
317
|
+
}
|
|
318
|
+
return response;
|
|
283
319
|
}
|
|
284
320
|
mergeConnectionData(allData, newData, connection, direction) {
|
|
285
321
|
const allConnection = (0, pagination_helpers_1.extractConnection)(allData);
|
|
@@ -477,6 +513,56 @@ class FluentClient {
|
|
|
477
513
|
throw error;
|
|
478
514
|
}
|
|
479
515
|
}
|
|
516
|
+
async getEvents(params = {}) {
|
|
517
|
+
const queryString = this.buildEventQueryString(params);
|
|
518
|
+
const endpoint = queryString ? `/api/v4.1/event?${queryString}` : '/api/v4.1/event';
|
|
519
|
+
this.log('info', '[FluentClient:event] Searching event logs', {
|
|
520
|
+
endpoint,
|
|
521
|
+
filterCount: Object.keys(params).length,
|
|
522
|
+
hasDateRange: !!(params.from || params.to),
|
|
523
|
+
entityType: params['context.entityType'],
|
|
524
|
+
rootEntityType: params['context.rootEntityType'],
|
|
525
|
+
eventStatus: params.eventStatus,
|
|
526
|
+
});
|
|
527
|
+
try {
|
|
528
|
+
const response = await this.request(endpoint, {
|
|
529
|
+
method: 'GET',
|
|
530
|
+
});
|
|
531
|
+
this.log('info', '[FluentClient:event] Event search completed', {
|
|
532
|
+
resultCount: response.data.count,
|
|
533
|
+
hasMore: response.data.hasMore,
|
|
534
|
+
start: response.data.start,
|
|
535
|
+
});
|
|
536
|
+
return response.data;
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
this.log('error', '[FluentClient:event] Failed to search events', error);
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async getEventById(eventId) {
|
|
544
|
+
if (!eventId) {
|
|
545
|
+
throw new types_1.FluentValidationError('eventId is required');
|
|
546
|
+
}
|
|
547
|
+
this.log('info', `[FluentClient:event] Getting event by ID: ${eventId}`, { eventId });
|
|
548
|
+
try {
|
|
549
|
+
const response = await this.request(`/api/v4.1/event/${eventId}`, {
|
|
550
|
+
method: 'GET',
|
|
551
|
+
});
|
|
552
|
+
this.log('info', `[FluentClient:event] Event retrieved: ${response.data.name}`, {
|
|
553
|
+
eventId: response.data.id,
|
|
554
|
+
name: response.data.name,
|
|
555
|
+
type: response.data.type,
|
|
556
|
+
eventStatus: response.data.eventStatus,
|
|
557
|
+
entityType: response.data.context?.entityType,
|
|
558
|
+
});
|
|
559
|
+
return response.data;
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
this.log('error', `[FluentClient:event] Failed to get event ${eventId}`, error);
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
480
566
|
async validateWebhook(payload, signature, rawPayload) {
|
|
481
567
|
this.log('info', `[FluentClient:webhook] Validating webhook for event "${payload.name || 'unknown'}"`, {
|
|
482
568
|
hasSignature: !!signature,
|
|
@@ -556,6 +642,16 @@ class FluentClient {
|
|
|
556
642
|
const result = await this.graphql(payload);
|
|
557
643
|
return result.data;
|
|
558
644
|
}
|
|
645
|
+
buildEventQueryString(params) {
|
|
646
|
+
const searchParams = new URLSearchParams();
|
|
647
|
+
for (const [key, value] of Object.entries(params)) {
|
|
648
|
+
if (value === undefined || value === null || value === '') {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
searchParams.append(key, String(value));
|
|
652
|
+
}
|
|
653
|
+
return searchParams.toString();
|
|
654
|
+
}
|
|
559
655
|
async request(endpoint, config = {}) {
|
|
560
656
|
const isVersoriContext = this.context?.fetch && this.context?.fetch !== globalThis.fetch;
|
|
561
657
|
this.log('debug', 'Request mode detection', {
|
|
@@ -378,7 +378,7 @@ class S3DataSource extends abstract_data_source_1.AbstractDataSource {
|
|
|
378
378
|
return Buffer.from('PAR1\x00\x00\x00\x00PAR1', 'ascii');
|
|
379
379
|
}
|
|
380
380
|
try {
|
|
381
|
-
const parquet = await Promise.resolve().then(() => __importStar(require('parquetjs')));
|
|
381
|
+
const parquet = await Promise.resolve().then(() => __importStar(require('@dsnp/parquetjs')));
|
|
382
382
|
const ParquetWriter = parquet.ParquetWriter;
|
|
383
383
|
const ParquetSchema = parquet.ParquetSchema;
|
|
384
384
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
@@ -361,7 +361,7 @@ class SftpDataSource extends abstract_data_source_1.AbstractDataSource {
|
|
|
361
361
|
return Buffer.from('PAR1\x00\x00\x00\x00PAR1', 'ascii');
|
|
362
362
|
}
|
|
363
363
|
try {
|
|
364
|
-
const parquet = await Promise.resolve().then(() => __importStar(require('parquetjs')));
|
|
364
|
+
const parquet = await Promise.resolve().then(() => __importStar(require('@dsnp/parquetjs')));
|
|
365
365
|
const ParquetWriter = parquet.ParquetWriter;
|
|
366
366
|
const ParquetSchema = parquet.ParquetSchema;
|
|
367
367
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { GraphQLTemplateGenerator } from './services/mapping/query/graphql-templ
|
|
|
34
34
|
export { MappingError, ResolverError } from './errors';
|
|
35
35
|
export type { NodeConfig, NodesConfig, FieldConfig, FieldsConfig, NodesContext, MappingContext, ResolverFunction, ResolverHelpers, ResolverContext, ResolversMap, MapResult, MapWithNodesResult, NodeValidationResult, MappingOptions, } from './services/mapping/types';
|
|
36
36
|
export { CsvDelimiter, FileEncoding, JobStrategy, FileType, BatchAction, EntityType, AwsRegion, ValidationMode, ProcessingStatus, } from './types/enums';
|
|
37
|
-
export type { FluentClientConfig, FluentBatchPayload, FluentInventoryBatchEntity, FluentInventoryBatchRequest, FluentBatchResponse, FluentBatchStatus, FluentJobPayload, FluentJobResponse, FluentJobMetadata, FluentJobStatus, FluentJobResults, FluentEvent, FluentEventMode, CreateEventOptions, GraphQLPayload, GraphQLResponse, PaginationConfig, PageInfo, PaginatedResponse, FluentWebhookPayload, WebhookRequestContext, GraphQLValidationResult, BatchConfiguration, DataSourceConfig, FileMetadata, Logger, StructuredLogger, LogContext, LoggerConfig, PerformanceMetrics, InventoryDataRecord, ParquetDataRecord, FieldMappingConfig, FieldMappingRule, ProcessingMetadata, AttributeValue, } from './types';
|
|
37
|
+
export type { FluentClientConfig, FluentBatchPayload, FluentInventoryBatchEntity, FluentInventoryBatchRequest, FluentBatchResponse, FluentBatchStatus, FluentJobPayload, FluentJobResponse, FluentJobMetadata, FluentJobStatus, FluentJobResults, FluentEvent, FluentEventMode, CreateEventOptions, FluentEventRootEntityType, FluentEventEntityType, FluentEventCategory, FluentEventType, FluentEventStatus, FluentEventQueryParamValue, FluentEventQueryParams, FluentEventLogAttribute, FluentEventLogContext, FluentEventLogItem, FluentEventLogResponse, GraphQLPayload, GraphQLResponse, GraphQLErrorMode, GraphQLError, PaginationConfig, PageInfo, PaginatedResponse, FluentWebhookPayload, WebhookRequestContext, GraphQLValidationResult, BatchConfiguration, DataSourceConfig, FileMetadata, Logger, StructuredLogger, LogContext, LoggerConfig, PerformanceMetrics, InventoryDataRecord, ParquetDataRecord, FieldMappingConfig, FieldMappingRule, ProcessingMetadata, AttributeValue, } from './types';
|
|
38
38
|
export type { VersoriContext, WebhookContext, DirectContext } from './types';
|
|
39
39
|
export { S3SDKTester, S3PresignedTester, S3ComparisonTester, FluentConnectionTester, type TestResult, type FluentTestConfig, type S3TestConfig, } from './testing/index';
|
|
40
40
|
export { S3Service, S3ServiceError, type S3ServiceConfig, type S3Config, type S3Object, type ListOptions, type PutOptions, type CopyOptions, type S3Location, } from './services/s3/index';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FluentClient } from '../../clients/fluent-client';
|
|
2
|
-
import type { Logger } from '../../types';
|
|
2
|
+
import type { Logger, GraphQLErrorMode, GraphQLError } from '../../types';
|
|
3
3
|
export interface ExtractionOptions<T = any> {
|
|
4
4
|
query: string;
|
|
5
5
|
resultPath: string;
|
|
@@ -10,6 +10,8 @@ export interface ExtractionOptions<T = any> {
|
|
|
10
10
|
direction?: 'forward' | 'backward';
|
|
11
11
|
timeout?: number;
|
|
12
12
|
validateItem?: (item: T) => boolean;
|
|
13
|
+
operationName?: string;
|
|
14
|
+
errorHandling?: GraphQLErrorMode;
|
|
13
15
|
}
|
|
14
16
|
export interface ExtractionStats {
|
|
15
17
|
totalRecords: number;
|
|
@@ -20,6 +22,7 @@ export interface ExtractionStats {
|
|
|
20
22
|
validRecords?: number;
|
|
21
23
|
invalidRecords?: number;
|
|
22
24
|
direction?: 'forward' | 'backward';
|
|
25
|
+
partialErrors?: GraphQLError[];
|
|
23
26
|
}
|
|
24
27
|
export interface ExtractionError {
|
|
25
28
|
type: 'graphql' | 'network' | 'validation' | 'parsing';
|