npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fluentcommerce/fc-connect-sdk

v0.1.54

Published

Fluent Commerce SDK - Deno & Node.js Compatible

Readme

Fluent Commerce Connect SDK

npm version npm downloads License: MIT TypeScript Node.js Deno Compatible

TypeScript SDK for building Fluent Commerce integrations across Node.js, Deno, and the Versori platform.


Table of Contents


Overview

Build Fluent Commerce integrations faster with a production-ready SDK that handles the hard parts: authentication, data parsing, field mapping, error handling, and multi-runtime compatibility.

What you get:

  • Universal Mapping – Transform CSV, XML, JSON, Parquet with one config
  • Auto-Pagination – Extract thousands of records automatically
  • Multi-Runtime – Same code works in Node.js, Deno, and Versori platform
  • Production Features – Connection pooling, retry logic, state management, webhook validation
  • 23+ Templates – Copy-paste ready workflows for common patterns

The SDK provides composable services – you own the workflow. Mix and match data sources, parsers, mappers, and API clients to move data between Fluent Commerce and external systems.

Read → Parse → Map → Your Logic → Archive

🤔 Quick Decisions:


🎯 Use This SDK If You're Building...

Quick pattern matching for common integration scenarios:

| Scenario | What It Looks Like | |----------|-------------------| | 📦 Inventory Sync | Daily/hourly inventory updates from WMS → Fluent (CSV/Parquet files) | | 🛒 Order Integration | E-commerce orders (SFCC, Shopify) → Fluent via webhooks (XML/JSON) | | 📊 Data Warehouse ETL | Extract Fluent data → Snowflake/BigQuery for analytics (GraphQL queries) | | 🔄 B2B File Exchange | Process EDI/XML files from trading partners via SFTP | | 📱 Admin Tools | Internal dashboards with file upload/processing capabilities | | 🔔 Real-time Events | Webhook processing for inventory updates, order status changes |

Not sure if SDK fits? See When to Use This SDK for detailed decision matrix.


Feature Highlights

Core Services:

  • Universal Mapper – one mapping config for CSV, XML, JSON, Parquet (16 built-in resolvers, custom resolver registry)
  • Extraction Orchestrator – high-level GraphQL extraction with auto-pagination, path-based extraction, validation, statistics
  • GraphQL Error Classification – automatic retry guidance based on Fluent error codes (C/S/T prefix), retryability detection with actionable recommendations (see docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md)
  • Batch Operations – use client.createJob() and client.sendBatch() for batch processing. FluentBatchManager available for Versori workflows (see docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md)
  • State Management – distributed state with KV storage, file deduplication, job caching, distributed locking with stale detection
  • Job Tracker – lifecycle tracking, automatic expiration handling, metadata persistence
  • Preflight Validator – pre-execution validation saves API quota, catches config errors early
  • Webhook Validation Service – cryptographic signature validation (RSA SHA256/SHA1/SHA512)
  • Webhook Authentication Service – Secure API key generation and validation (Deno-compatible)
  • Partial Batch Recovery – graceful handling of partial batch failures with retry logic

Data Layer:

  • S3 Data Source – streaming I/O with presigned URLs (works anywhere, no AWS SDK required), constant memory footprint
  • SFTP Data Source – connection pooling + wait queues, exponential backoff retry, Deno compatibility for AWS Transfer Family
  • Streaming Parsers – CSV, XML, JSON, Parquet with memory-efficient processing, validation hooks
  • Smart Error Handling – 4xx errors fail fast, 5xx exponential backoff, job expiration auto-recreation. Consistent statusCode field across success and error responses

Architecture:

  • Multi-Runtime – single codebase for Node.js ≥18, Deno, Versori platform (auto-detection via universal client factory)
  • Stateless & Parallel-Safe – all services safe for Promise.all(), idempotent operations
  • Layered Design – clear separation: Client → Service → Data → Orchestration
  • Universal Compatibility – consistent API regardless of environment

Supported Runtimes

| Runtime | Support | Notes | | ---------------- | ------- | --------------------------------------- | | Node.js ≥ 18 | ✅ | Native fetch & FormData required | | Deno | ✅ | Import using npm: specifier. SFTP: Only AES-128 ciphers supported (see troubleshooting guide) | | Versori Platform | ✅ | Pass Versori ctx directly to services |

Deno Compatibility Notes:

  • ✅ All ESM imports have proper extensions
  • ✅ All Node.js imports use node: prefix
  • ✅ Buffer support via import { Buffer } from 'node:buffer';
  • ⚠️ SFTP: Limited to aes128-ctr and aes128-cbc ciphers (Deno crypto limitations)
  • 📚 Complete Guide: docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md

When to Use This SDK

| Scenario | When SDK Helps | When to Skip It | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | UI Applications | ✅ Admin tools with file upload/processing✅ Internal dashboards with data export✅ Complex data transformations | ⚠️ Simple CRUD operations⚠️ Real-time UI updates⚠️ Mobile apps (bundle size) | | Event Streaming | ✅ Webhook-based event processing✅ Scheduled batch reactions to events | ⚠️ Real-time streaming⚠️ Sub-second latency requirements | | Simple Scripts | ✅ Need parsing (CSV/XML/JSON)✅ Need field mapping/transformation✅ Need retry/error handling | ⚠️ Single GraphQL query⚠️ No transformation needed⚠️ Prototyping/exploration | | Non-Fluent Systems | ❌ SDK is Fluent Commerce specific | ❌ Use system-specific SDKs |

💡 Key Insight: The SDK shines when you need data transformation, file processing, or multi-step workflows. For simple API calls, direct GraphQL may be lighter.


🔌 Which API Should I Use?

Choose the right Fluent Commerce API for your use case:

| What You're Doing | Use This API | SDK Method | Why | |----------------------|------------------|----------------|---------| | Bulk inventory sync (CSV/Parquet files, 1000+ records) | Batch API | createJob() + sendBatch() | Optimized for high volume, BPP change detection, handles thousands of records efficiently | | Product catalog sync (products, variants, prices) | Event API | sendEvent() | Triggers Rubix workflows, validates business rules, workflow orchestration | | Location setup (stores, warehouses) | Event API | sendEvent() | Requires workflow orchestration | | Order creation (one-time orders) | GraphQL | client.graphql() with createOrder mutation | Triggers Order CREATED event, full control | | Order updates/events (status changes) | Event API | sendEvent() | Triggers workflows for order state changes | | Audit/search event history (trace flows, investigate failures) | Event API (GET) | getEvents() + getEventById() | Read-only event/audit retrieval with filters and pagination | | Customer data (registration, profiles) | GraphQL | client.graphql() with mutations | No Rubix workflow support for customers | | Data extraction (export to S3/SFTP) | GraphQL | client.graphql() + ExtractionOrchestrator | Query with auto-pagination, extract thousands of records | | Single operations (create one product, update one location) | GraphQL | client.graphql() | Direct control, immediate feedback |

Quick Rules

  • Batch API: Only supports INVENTORY + UPSERT (no products, orders, etc.)
  • Event API: For entities that need workflow triggers (products, locations, custom entities)
  • GraphQL: For queries, single mutations, customers, and when you need direct control

📚 Complete Guide: See templates for detailed examples - docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/ (Batch API) and docs/01-TEMPLATES/versori/workflows/ingestion/event-api/ (Event API)


Core Services

| Area | Services & Utilities | Typical Use Case | Notes | | -------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------- | ----------------------------------------- | | Data sources | S3DataSource, SftpDataSource (connection pooling) | Daily inventory files, EDI integrations | Stream-safe reads, automatic retries | | Parsers | CSVParserService, XMLParserService, JSONParserService, ParquetParserService | Price lists, B2B orders, API responses, analytics | Works standalone or with the orchestrator | | Mapping | UniversalMapper, sdkResolvers, custom resolver registry | Field normalization, date/time conversions | One mapping config for every format | | Execution | ExtractionOrchestrator, PreflightValidator, PartialBatchRecovery, JobTracker | Data exports, config validation, error recovery | Build full workflows quickly | | Auth / Clients | createClient, FluentClient, FluentVersoriClient, FluentConnectionTester | Node.js scripts, Versori workflows | Runtime-aware factory, OAuth2 refresh, automatic logging, optional connection validation. See docs/04-REFERENCE/testing/modules/04-REFERENCE-testing-03-fluent-testing.md for connection testing | | Webhooks | FluentClient.validateWebhook, parseWebhookPayload, WebhookAuthService | Secure webhook processing, B2B event handling | RSA signature validation, API keys | | Errors | classifyError, classifyErrors, ErrorClassification | GraphQL error handling, retry decisions | Auto-classifies Fluent error codes | | Logging | createConsoleLogger, toStructuredLogger, generateCorrelationId | Standalone scripts, request tracking | Pure-function log utilities |

📚 Learn More: docs/02-CORE-GUIDES/ - Complete API reference and guides for all services

Note: For batch operations, use client.createJob() and client.sendBatch() directly (shown in examples). FluentBatchManager is available for Versori workflows - see docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md for details.


Install

# npm (recommended)
npm install @fluentcommerce/fc-connect-sdk

# yarn
yarn add @fluentcommerce/fc-connect-sdk

# pnpm
pnpm add @fluentcommerce/fc-connect-sdk

Deno:

import { createClient } from 'npm:@fluentcommerce/fc-connect-sdk';

Optional TypeScript Settings

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "node",
    "lib": ["ES2022"],
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "strict": true
  }
}

Verify Installation

Quick test to confirm SDK is working:

import { createClient } from '@fluentcommerce/fc-connect-sdk';

console.log('✅ SDK imported successfully!');
// Test that core services are accessible
console.log('Available:', { createClient: typeof createClient });

Expected output:

✅ SDK imported successfully!
Available: { createClient: 'function' }

If you see this, you're ready for the 5-Minute Quickstart!


🏃 5-Minute Quickstart

Goal: Send your first batch of inventory to Fluent Commerce.

Step 1: Install

npm install @fluentcommerce/fc-connect-sdk

Step 2: Configure Authentication

Create .fluentrc.json:

{
  "baseUrl": "https://api.fluentcommerce.com",
  "clientId": "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET",
  "username": "YOUR_USERNAME",
  "password": "YOUR_PASSWORD",
  "retailerId": "YOUR_RETAILER_ID"
}

Step 3: Send Test Data

import { createClient } from '@fluentcommerce/fc-connect-sdk';
import * as fs from 'fs';

// Load config
const config = JSON.parse(fs.readFileSync('.fluentrc.json', 'utf8'));

// Create authenticated client
const client = await createClient({ config });

// Optional: Validate connection immediately (fail fast)
// Recommended for production to catch auth issues early
// const client = await createClient({ config }, { validateConnection: true });
// This validates OAuth2 tokens and API connectivity before returning

// Create job
const job = await client.createJob({
  name: 'quickstart-test',
  retailerId: config.retailerId,
});

// Send test inventory
const batch = await client.sendBatch(job.id, {
  entityType: 'INVENTORY',
  action: 'UPSERT',
  source: 'QUICKSTART', // Optional: tracks data source
  event: 'InventoryQuantityUpdate', // Optional: targets Rubix workflow event (defaults to InventoryChanged)
  entities: [
    {
      skuRef: 'TEST-SKU-001',
      qty: 10,
      locationRef: 'DC01',
      retailerId: config.retailerId,
    },
    {
      skuRef: 'TEST-SKU-002',
      qty: 5,
      locationRef: 'DC01',
      retailerId: config.retailerId,
    },
  ],
});

console.log('✅ Successfully sent test inventory!');
console.log(`📊 Job ID: ${job.id}, Batch ID: ${batch.id}`);

Step 4: Verify

Check the Fluent Commerce console to see your test inventory records.

🎉 Success! You've sent your first batch. See included documentation for production templates and patterns.

🐛 Troubleshooting Quick Fixes

Common issues:

  • 401 Unauthorized → Check clientId/clientSecret, verify OAuth2 credentials
  • Batch API only supports INVENTORY → Use Event API for products/orders, GraphQL for customers
  • Connection pool errors → Always call dataSource.dispose() after use
  • Deno/Versori Buffer errors → Add import { Buffer } from 'node:buffer';
  • Schema validation errors → Use npx fc-connect validate-schema to check mapping config

📚 Full Troubleshooting: docs/00-START-HERE/TROUBLESHOOTING-QUICK-REFERENCE.md - Fixes 90% of common issues


🎯 What's Next?

Now that you've sent your first batch, explore these resources to build production integrations:

📋 Production-Ready Templates

Copy-paste 23+ working templates for common scenarios:

  • SFTP CSV → Batch API - Daily inventory sync from SFTP
    • docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md
  • S3 Parquet → Extraction - Extract data to data warehouse
    • docs/01-TEMPLATES/standalone/ (see graphql-query-export.md, graphql-to-parquet-partitioned-s3.md)
  • XML Webhook → GraphQL - Real-time order ingestion
    • docs/01-TEMPLATES/versori/webhooks/xml-order-ingestion.md
  • Payment Gateway Integration - Adyen payment flows (Capture, Refund, Cancel, ReAuth)
    • docs/01-TEMPLATES/versori/webhooks/template-payment-gateway-integration.md
  • Browse All Templates - Find the right pattern for your use case
    • docs/01-TEMPLATES/ or docs/TEMPLATE-LOADING-MATRIX.md

📚 Core Guides

Learn SDK features in depth:

  • Complete Mapping Guide - Field transformations, custom resolvers
    • docs/02-CORE-GUIDES/mapping/
  • Auto-Pagination - Handle large GraphQL result sets
    • docs/02-CORE-GUIDES/auto-pagination/
  • Data Sources - S3 and SFTP operations
    • docs/02-CORE-GUIDES/data-sources/
  • Troubleshooting - Fixes 90% of common issues
    • docs/00-START-HERE/TROUBLESHOOTING-QUICK-REFERENCE.md

🏗️ Deep Dive

Understand the SDK architecture and advanced patterns:


🗺️ Universal Mapping

One mapping pattern for ALL data formats - XML, JSON, CSV, Parquet.

XML/JSON → Fluent with Custom Resolvers

Transform any XML or JSON structure to Fluent Commerce entities:

import { UniversalMapper, XMLParserService } from '@fluentcommerce/fc-connect-sdk';

// XML input example
const xmlData = `
<InventoryUpdate>
  <Item>
    <SKU>PROD-001</SKU>
    <Location>DC-01</Location>
    <Qty>100</Qty>
    <LastUpdated>2025-01-27T10:30:00Z</LastUpdated>
    <Price currency="USD">29.99</Price>
  </Item>
</InventoryUpdate>
`;

// Parse XML to JavaScript object
const parser = new XMLParserService();
const parsed = await parser.parse(xmlData, {
  arrayPath: 'InventoryUpdate.Item', // Extract array of items
});

// Setup mapper with custom resolvers
const mapper = new UniversalMapper(
  {
    fields: {
      // Direct mapping
      skuRef: { source: 'SKU', required: true },
      locationRef: { source: 'Location', required: true },

      // Built-in SDK resolver
      qty: { source: 'Qty', resolver: 'sdk.parseInt' },

      // Custom resolver for nested data
      price: {
        source: 'Price',
        resolver: 'custom.extractPrice',
      },

      // Custom resolver for date transformation
      lastModified: {
        source: 'LastUpdated',
        resolver: 'custom.parseISODate',
      },

      // Static value
      type: { defaultValue: 'DELTA' },
      status: { defaultValue: 'ACTIVE' },
    },
  },
  {
    // Register custom resolvers
    customResolvers: {
      'custom.extractPrice': (value, sourceData, helpers) => {
        // Extract value from nested object: { _text: "29.99", "@_currency": "USD" }
        return helpers.parseFloatSafe(value?._text || value, 0);
      },

      'custom.parseISODate': (value, sourceData, helpers) => {
        // Transform ISO date to Fluent format
        const date = new Date(value);
        return date.toISOString().split('T')[0]; // "2025-01-27"
      },
    },
  }
);

// Map each item
for (const item of parsed) {
  const result = await mapper.map(item);
  if (result.success) {
    console.log('Mapped:', result.data);
    // Output: { skuRef: 'PROD-001', locationRef: 'DC-01', qty: 100, price: 29.99, lastModified: '2025-01-27', ... }
  } else {
    console.error('Mapping failed:', result.errors);
  }
}

JSON Mapping Example

// JSON input
const jsonData = {
  product_id: 'SKU-001',
  warehouse: 'WH-NYC',
  stock_level: '50',
  updated_at: '2025-01-27T10:30:00Z',
};

const mapper = new UniversalMapper({
  fields: {
    skuRef: { source: 'product_id', required: true },
    locationRef: { source: 'warehouse', required: true },
    qty: { source: 'stock_level', resolver: 'sdk.parseInt' },
    type: { defaultValue: 'DELTA' },
  },
});

const result = await mapper.map(jsonData);
// Output: { skuRef: 'SKU-001', locationRef: 'WH-NYC', qty: 50, type: 'DELTA' }

Array Mapping with GraphQL Edges/Nodes

IMPORTANT: The SDK supports two patterns for GraphQL edges/nodes arrays. Both produce identical results - choose based on preference:

Input Data (GraphQL Response):

{
  items: {
    edges: [
      { node: { ref: "FFI-001", quantity: 10 } },
      { node: { ref: "FFI-002", quantity: 5 } }
    ]
  }
}

Pattern 1: Wildcard syntax (recommended)

{
  "fields": {
    "items": {
      "source": "items.edges[*].node",  // ✅ SDK extracts nodes: [{ ref: "..." }, ...]
      "isArray": true,
      "fields": {
        "ref": { "source": "ref" },     // ✅ Direct access (no "node." prefix)
        "quantity": { "source": "quantity", "resolver": "sdk.parseInt" }
      }
    }
  }
}

Result: { items: [{ ref: "FFI-001", quantity: 10 }, { ref: "FFI-002", quantity: 5 }] }

Pattern 2: Direct edges access

{
  "fields": {
    "items": {
      "source": "items.edges",          // ✅ SDK extracts edges: [{ node: {...} }, ...]
      "isArray": true,
      "fields": {
        "ref": { "source": "node.ref" }, // ✅ Need "node." prefix
        "quantity": { "source": "node.quantity", "resolver": "sdk.parseInt" }
      }
    }
  }
}

Result: { items: [{ ref: "FFI-001", quantity: 10 }, { ref: "FFI-002", quantity: 5 }] } (identical to Pattern 1)

For XML with <Item> wrapper elements, add nested object wrapper to either pattern:

{
  "fields": {
    "Items": {
      "source": "items.edges[*].node",  // or "items.edges"
      "isArray": true,
      "fields": {
        "Item": {                        // ✅ Creates <Item> elements
          "fields": {
            "@ref": { "source": "ref" }, // or "node.ref" for Pattern 2
            "quantity": { "source": "quantity", "resolver": "sdk.parseInt" }
          }
        }
      }
    }
  }
}

XML Output (both patterns):

<Items>
  <Item ref="FFI-001"><quantity>10</quantity></Item>
  <Item ref="FFI-002"><quantity>5</quantity></Item>
</Items>

Key Difference: Pattern 1 extracts nodes first (cleaner paths), Pattern 2 extracts edges (more explicit). Both produce identical mapped data and XML output.

📚 Learn More: docs/02-CORE-GUIDES/mapping/modules/02-CORE-GUIDES-mapping-05-advanced-patterns.md - Complete array mapping guide with step-by-step examples

Built-in SDK Resolvers

String: sdk.uppercase, sdk.lowercase, sdk.trim, sdk.toString Number: sdk.parseInt, sdk.parseFloat, sdk.number Date: sdk.formatDate, sdk.formatDateShort, sdk.parseDate Type: sdk.boolean, sdk.parseJson, sdk.toJson Utility: sdk.identity, sdk.coalesce, sdk.default

📚 Learn More:

  • Complete Mapping Guide: docs/02-CORE-GUIDES/mapping/modules/ - Universal mapping documentation (7 modules)
  • Custom Resolvers: docs/02-CORE-GUIDES/mapping/resolvers/ - Building custom transformation logic (7 modules)
  • GraphQL Mutations: docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/ - XML/JSON to GraphQL (12 modules)
  • Advanced Patterns: docs/02-CORE-GUIDES/mapping/modules/02-CORE-GUIDES-mapping-05-advanced-patterns.md - Array mapping, GraphQL edges/nodes (all 3 options)

🎯 Mapping Approaches: Which One Do I Use?

Quick Decision Table

| Your Scenario | Use This | Why | |-------------------|--------------|---------| | 📦 Bulk inventory files (CSV/Parquet) → Fluent | UniversalMapper + Batch API | Optimized for thousands of records, change detection | | 📬 XML webhooks (SFCC orders, EDI) → Fluent | GraphQLMutationMapper | Auto-generates GraphQL mutations from XML | | 🌐 JSON webhooks (Shopify, APIs) → Fluent | GraphQLMutationMapper | Schema validation + auto-query building | | 🔧 Simple field transforms (rename, type convert) | UniversalMapper | Lightweight, no GraphQL needed | | 🎨 Custom business logic (tax calc, SKU generation) | UniversalMapper + Custom Resolvers | Maximum flexibility |

Detailed Comparison

| Feature | UniversalMapper | GraphQLMutationMapper | Manual GraphQL | |---------|----------------|----------------------|----------------| | Best For | CSV/Parquet → Batch API | XML/JSON → GraphQL mutations | Simple queries | | Input Formats | Any (CSV, XML, JSON, Parquet) | XML, JSON | Any (you build it) | | Output | Plain JavaScript objects | GraphQL mutation + variables | You build everything | | Schema Validation | ❌ No | ✅ Yes (validates against GraphQL schema) | ❌ No | | Auto-generates GraphQL | ❌ No | ✅ Yes | ❌ No | | Bulk Ingestion | ✅ Yes (Batch API) | ❌ No (one mutation at a time) | Depends | | Learning Curve | 🟢 Easy | 🟡 Medium | 🟢 Easy | | Setup Complexity | Low | Medium (needs mapping config) | Low | | Custom Logic | ✅ Custom resolvers | ✅ Custom resolvers | ✅ Write your own code | | Error Handling | ✅ Built-in | ✅ Built-in with validation | ❌ Manual |

📚 Quick Decision: See Mapper Quick Decision Guide (2-minute read)
📚 Complete Comparison: See Mapper Comparison Guide for an in-depth, code-verified comparison with a real-world inventory scenario, performance benchmarks, and decision matrix.


📬 XML/JSON Webhooks → GraphQL Mutations

Use Case: Transform external XML/JSON into Fluent GraphQL mutations (orders, products, etc.)

Example: SFCC XML Order → Fluent createOrder

import { GraphQLMutationMapper, XMLParserService, createClient } from '@fluentcommerce/fc-connect-sdk';

// 1. Parse incoming XML webhook
const xmlParser = new XMLParserService();
const orderData = await xmlParser.parse(xmlString);

// 2. Configure mapping (XML paths → GraphQL fields)
const mappingConfig = {
  mutation: 'createOrder',
  sourceFormat: 'xml',
  returnFields: ['id', 'ref', 'status', 'totalPrice'], // Fields returned in response
  arguments: {
    input: {
      ref: { source: 'order.@id' },
      type: { value: 'HD' },
      retailer: { source: 'order.retailer', resolver: 'sdk.parseInt' },
      customer: {
        fields: {
          firstName: { source: 'order.customer.first-name' },
          lastName: { source: 'order.customer.last-name' },
          email: { source: 'order.customer.email' }
        }
      },
      items: {
        source: 'order.product-lineitems.product-lineitem',
        fields: {
          skuRef: { source: '@product-id' },
          quantity: { source: 'quantity', resolver: 'sdk.parseInt' },
          price: { source: 'price', resolver: 'sdk.parseFloat' }
        }
      }
    }
  }
};

// 3. Generate mutation automatically
const client = await createClient({ /* config */ });
const mapper = new GraphQLMutationMapper(mappingConfig, logger, { fluentClient: client });
const payload = await mapper.map(orderData); // Returns { query, variables }

// 4. Execute (mutation is auto-built!)
const result = await client.graphql(payload);
const createdOrder = result.data.createOrder;
console.log('✅ Order created:', {
  id: createdOrder.id,        // From returnFields
  ref: createdOrder.ref,      // From returnFields
  status: createdOrder.status, // From returnFields
  totalPrice: createdOrder.totalPrice // From returnFields
});

What GraphQLMutationMapper Does For You:

  1. ✅ Parses complex XML/JSON structures
  2. ✅ Validates against GraphQL schema
  3. ✅ Builds the GraphQL mutation query automatically
  4. ✅ Handles nested objects and arrays
  5. ✅ Provides detailed error messages

vs Manual Approach:

// ❌ Without GraphQLMutationMapper (you write everything)
const mutation = `
  mutation CreateOrder($input: CreateOrderInput!) {
    createOrder(input: $input) {
      id ref type
      customer { firstName lastName email }
      items { skuRef quantity price }
    }
  }
`;
const variables = {
  input: {
    ref: orderData.order['@id'],
    type: 'HD',
    retailer: parseInt(orderData.order.retailer),
    customer: {
      firstName: orderData.order.customer['first-name'],
      lastName: orderData.order.customer['last-name'],
      email: orderData.order.customer.email
    },
    items: orderData.order['product-lineitems']['product-lineitem'].map(item => ({
      skuRef: item['@product-id'],
      quantity: parseInt(item.quantity),
      price: parseFloat(item.price)
    }))
  }
};
await client.graphql({ query: mutation, variables });

📚 Complete Guide: docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/ - 12 comprehensive modules


📋 Complete Workflow Examples

Real-world integration patterns showing the full data pipeline.

Workflow Pattern: Data → Parse → Map → Batch API

External Storage (S3/SFTP)
         ↓
    Download File
         ↓
    Parse (CSV/XML/JSON/Parquet)
         ↓
    Map Fields (UniversalMapper)
         ↓
    Send to Batch API
         ↓
    Archive/Cleanup

📚 Learn More: See docs/01-TEMPLATES/ for 23+ production-ready templates


Example 1: S3 CSV → Batch API (Daily Inventory Sync)

Complete workflow reading CSV from S3, mapping fields, and sending to Fluent.

import {
  createClient,
  S3DataSource,
  CSVParserService,
  UniversalMapper,
} from '@fluentcommerce/fc-connect-sdk';

// ============= SETUP =============
// Create authenticated client (auto-detects Node.js/Deno/Versori)
const client = await createClient({
  config: {
    baseUrl: 'https://api.fluentcommerce.com',
    clientId: process.env.FLUENT_CLIENT_ID!,
    clientSecret: process.env.FLUENT_CLIENT_SECRET!,
    username: process.env.FLUENT_USERNAME!,
    password: process.env.FLUENT_PASSWORD!,
    retailerId: process.env.FLUENT_RETAILER_ID!,
  },
});

// Configure S3 data source
const s3 = new S3DataSource(
  {
    type: 'S3_CSV',
    connectionId: 'daily-inventory',
    name: 'Daily Inventory',
    s3Config: {
      bucket: process.env.S3_BUCKET!,
      region: process.env.S3_REGION ?? 'us-east-1',
      accessKeyId: process.env.S3_ACCESS_KEY_ID!,
      secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
    },
  },
  console // logger
);

// Setup CSV parser and field mapper
const parser = new CSVParserService();
const mapper = new UniversalMapper({
  fields: {
    skuRef: { source: 'sku_id', required: true },
    locationRef: { source: 'location_code', required: true },
    qty: { source: 'quantity', resolver: 'sdk.parseInt' },
    type: { source: 'type', defaultValue: 'DELTA' },
    status: { source: 'status', defaultValue: 'AVAILABLE' },
  },
});

// ============= WORKFLOW =============
// 1. Read file from S3
const [file] = await s3.listFiles({ prefix: 'inventory/' });
const csv = await s3.downloadFile(file.path, { encoding: 'utf8' });

// 2. Parse CSV and map fields
const rows = await parser.parse(csv, { columns: true, trim: true });
const payload = [];
for (const row of rows) {
  const mapped = await mapper.map(row);
  if (mapped.success) {
    // Each entity must include retailerId
    payload.push({
      ...mapped.data,
      retailerId: process.env.FLUENT_RETAILER_ID!,
    });
  }
}

// 3. Send to Fluent Commerce Batch API
const job = await client.createJob({
  name: 'inventory-sync',
  retailerId: process.env.FLUENT_RETAILER_ID!,
});
const batch = await client.sendBatch(job.id, {
  entityType: 'INVENTORY',
  action: 'UPSERT',
  source: 'S3_CSV', // Optional: tracks data source
  event: 'InventoryQuantityUpdate', // Optional: targets Rubix workflow event
  entities: payload,
});

// 4. Archive processed file
await s3.moveFile(file.path, `inventory/archive/${file.name}`);

console.log(`✅ Synced ${payload.length} inventory records`);
console.log(`📊 Job ID: ${job.id}, Batch ID: ${batch.id}`);

📚 Learn More:

  • S3 Operations: docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md
  • CSV Parsing: docs/02-CORE-GUIDES/parsers/modules/02-CORE-GUIDES-parsers-02-csv-parser.md
  • Universal Mapping: docs/02-CORE-GUIDES/mapping/modules/
  • Batch API: docs/02-CORE-GUIDES/ingestion/modules/02-CORE-GUIDES-ingestion-06-batch-api.md

Example 2: SFTP XML → Batch API (EDI Integration)

Reading XML from SFTP server, parsing with XPath, and batch processing.

import {
  createClient,
  SftpDataSource,
  XMLParserService,
  UniversalMapper,
} from '@fluentcommerce/fc-connect-sdk';

// Setup SFTP connection
const sftp = new SftpDataSource(
  {
    type: 'SFTP_XML',
    connectionId: 'edi-sftp',
    name: 'EDI SFTP',
    sftpConfig: {
      settings: {
        host: process.env.SFTP_HOST!,
        port: 22,
        username: process.env.SFTP_USERNAME!,
        password: process.env.SFTP_PASSWORD!,
      },
    },
  },
  console
);

// Setup XML parser with path resolution
const parser = new XMLParserService();
const mapper = new UniversalMapper({
  fields: {
    skuRef: { source: 'Item.SKU', required: true },
    locationRef: { source: 'Item.Location', required: true },
    qty: { source: 'Item.Quantity', resolver: 'sdk.parseInt' },
    type: { defaultValue: 'DELTA' },
    status: { defaultValue: 'AVAILABLE' },
  },
});

// Create client
const client = await createClient({
  config: {
    /* OAuth2 config */
  },
});

// ============= WORKFLOW =============
// 1. List and download XML from SFTP
const files = await sftp.listFiles({ prefix: '/inbound/', pattern: '*.xml' });
for (const file of files) {
  const xmlContent = await sftp.downloadFile(file.path, { encoding: 'utf8' });

  // 2. Parse XML and extract items
  const parsed = await parser.parse(xmlContent, {
    arrayPath: 'InventoryUpdate.Items.Item', // Extract array of items
  });

  // 3. Map fields
  const payload = [];
  for (const item of parsed) {
    const mapped = await mapper.map(item);
    if (mapped.success) {
      payload.push({
        ...mapped.data,
        retailerId: process.env.FLUENT_RETAILER_ID!,
      });
    }
  }

  // 4. Send to Batch API
  const job = await client.createJob({
    name: `sftp-${file.name}`,
    retailerId: process.env.FLUENT_RETAILER_ID!,
  });
  await client.sendBatch(job.id, {
    entityType: 'INVENTORY',
    action: 'UPSERT',
    source: 'SFTP_XML',
    entities: payload,
  });

  // 5. Archive processed file
  await sftp.moveFile(file.path, `/archive/${file.name}`);

  console.log(`✅ Processed ${file.name}: ${payload.length} records`);
}

📚 Learn More:

  • SFTP Operations: docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md
  • XML Parsing: docs/02-CORE-GUIDES/parsers/modules/02-CORE-GUIDES-parsers-04-xml-parser.md
  • Complete Templates: docs/01-TEMPLATES/standalone/ and docs/01-TEMPLATES/versori/

Note: Batch API supports inventory + UPSERT only. Use GraphQL mutations for products, orders, etc.


SDK Architecture

The SDK follows a layered, service-oriented architecture with comprehensive service orchestration:

┌─────────────────────────────────────────────────────────────────────┐
│                        🔌 CLIENT LAYER                              │
├─────────────────────────────────────────────────────────────────────┤
│  createClient Factory (Auto-detects Runtime)                        │
│      ├─→ FluentClient (OAuth2)                                      │
│      └─→ FluentVersoriClient (Platform Auth)                        │
└────────────────────────────┬────────────────────────────────────────┘
                             │
┌────────────────────────────┴────────────────────────────────────────┐
│                        ⚙️ SERVICE LAYER                             │
├─────────────────────────────────────────────────────────────────────┤
│  Core Services:                                                     │
│    • UniversalMapper          - Field transformations               │
│    • GraphQLMutationMapper    - XML/JSON → GraphQL                  │
│    • ExtractionOrchestrator   - GraphQL extraction workflows        │
│    • FluentBatchManager       - Batch operations + BPP              │
│                                                                      │
│  Support Services:                                                  │
│    • StateService             - KV storage management               │
│    • WebhookValidationService - RSA signature validation            │
│    • WebhookAuthService       - API key auth               │
│    • PreflightValidator       - Configuration validation            │
│    • JobTracker               - Lifecycle management                │
│    • PartialBatchRecovery     - Failure handling                    │
└────────────────────────────┬────────────────────────────────────────┘
                             │
┌────────────────────────────┴────────────────────────────────────────┐
│                         📁 DATA LAYER                               │
├─────────────────────────────────────────────────────────────────────┤
│  Data Sources:                                                      │
│    • S3DataSource       - Presigned URLs + streaming                │
│    • SftpDataSource     - Connection pool + wait queue              │
│                                                                      │
│  Parsers:                                                           │
│    • CSVParser          - Streaming CSV processing                  │
│    • XMLParser          - Path resolution + streaming               │
│    • JSONParser         - Streaming JSON processing                 │
│    • ParquetParser      - Columnar data format                      │
└────────────────────────────┬────────────────────────────────────────┘
                             │
┌────────────────────────────┴────────────────────────────────────────┐
│                    🌐 EXTERNAL SYSTEMS                              │
├─────────────────────────────────────────────────────────────────────┤
│  • Fluent Commerce API  - GraphQL, Batch API, Event API             │
│  • AWS S3               - Object storage                            │
│  • SFTP Servers         - File transfer                             │
│  • KV Storage           - Versori KV / Redis                        │
└─────────────────────────────────────────────────────────────────────┘

Data Flow: Read → Parse → Map → Your Logic → Send/Archive

| Layer | Responsibilities | Key Services | | -------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | 🔌 Client Layer | Runtime detection, authentication, API communication | createClient(), FluentClient, FluentVersoriClient | | ⚙️ Service Layer | Business logic, state management, validation | UniversalMapper, GraphQLMutationMapper, ExtractionOrchestrator, StateService, JobTracker, WebhookValidationService, WebhookAuthService | | 📁 Data Layer | File I/O, streaming parsers, constant memory footprint | S3DataSource (presigned URLs), SftpDataSource (pooled), CSV/XML/JSON/Parquet parsers | | 🎯 Orchestration Layer | High-level workflows, composition of services | IngestionService, ExtractionOrchestrator | | 🌐 External Systems | Fluent Commerce API, cloud storage, distributed state | GraphQL + Batch + Event APIs, S3, SFTP, KV storage |

💡 Design Principles:

  • Stateless & Parallel-Safe: All services safe for Promise.all(), no hidden state
  • Streaming-First: Constant memory footprint for large files
  • Universal Compatibility: Single codebase across Node.js, Deno, Versori
  • Smart Error Handling: 4xx fail fast, 5xx exponential backoff, job expiration auto-recreation. Consistent statusCode field across success and error responses

📚 Learn More: docs/04-REFERENCE/architecture/ - Complete architecture documentation


🚀 More Quick Examples

GraphQL Mutation (Create Product)

import { createClient } from '@fluentcommerce/fc-connect-sdk';

const client = await createClient({
  config: {
    /* OAuth2 */
  },
});

// Create a variant product using GraphQL mutation
const result = await client.graphql({
  query: `
    mutation CreateVariantProduct($input: CreateVariantProductInput!) {
      createVariantProduct(input: $input) {
        id
        ref
        type
        gtin
        name
        summary
      }
    }
  `,
  variables: {
    input: {
      ref: 'PROD-001',
      type: 'VARIANT',
      gtin: '01234567890128',
      name: 'Sample Product',
      summary: 'A sample variant product',
      catalogue: {
        ref: 'DEFAULT',
      },
      product: {
        ref: 'BASE-PROD-001',
      },
    },
  },
});

console.log('✅ Product created:', result.data.createVariantProduct);
// Output: { id: '123', ref: 'PROD-001', type: 'VARIANT', gtin: '01234567890128', name: 'Sample Product', summary: 'A sample variant product' }

📚 Learn More: docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/ - XML/JSON to GraphQL mutations


GraphQL Query with Auto-Pagination

Recommended: Using ExtractionOrchestrator (All Templates Use This)

import { createClient, ExtractionOrchestrator } from '@fluentcommerce/fc-connect-sdk';

const client = await createClient({
  config: {
    /* OAuth2 */
  },
});
const orchestrator = new ExtractionOrchestrator(client, console);

// Extract with auto-pagination + validation + statistics
const result = await orchestrator.extract({
  query: `
    query GetVirtualPositions($first: Int, $after: String) {
      virtualPositions(first: $first, after: $after) {
        edges {
          node {
            ref
            quantity
            productRef
            locationLink { ref }
          }
          cursor
        }
        pageInfo {
          hasNextPage
        }
      }
    }
  `,
  resultPath: 'virtualPositions.edges.node', // Extract node objects directly
  maxPages: 10,
});

console.log(
  `✅ Extracted ${result.stats.totalRecords} positions across ${result.stats.totalPages} pages`
);
// Output: ✅ Extracted 847 positions across 9 pages
// result.data = [{ ref: 'VP001', quantity: 10, productRef: 'PROD-001', locationLink: { ref: 'DC01' } }, ...]
// result.stats = { totalPages: 9, totalRecords: 847, truncated: false }

Handling Partial Responses (Errors with Data):

// When GraphQL returns errors but some data is available
const result = await orchestrator.extract({
  query: ORDERS_QUERY,
  resultPath: 'orders.edges.node',
  errorHandling: 'partial', // Continue extraction even with errors
});

// Check for partial errors
if (result.stats.partialErrors) {
  console.warn(`Extraction completed with ${result.stats.partialErrors.length} errors`);
  console.log(`Still extracted ${result.data.length} records`);
  // Errors are in result.stats.partialErrors
}

// Edge case: If GraphQL returns errors with null data, result.data will be []
// but errors are still available in result.stats.partialErrors

Alternative: Using FluentClient (For Custom Response Handling)

const result = await client.graphql({
  query: `query GetVirtualPositions($first: Int, $after: String) { ... }`,
  variables: { first: 100 },
  pagination: { maxPages: 10 },
});

// Manual extraction from GraphQL structure
const nodes = result.data?.virtualPositions?.edges?.map(edge => edge.node) ?? [];
console.log(`✅ Fetched ${nodes.length} virtual positions`);

When to Use Each Approach:

| Approach | Use When | Returns | | -------------------------- | ------------------------------------------------------------- | ------------------------------- | | ExtractionOrchestrator | Extracting data to files/systems (recommended for extraction) | Flattened data[] + stats | | FluentClient.graphql() | Mutations, custom queries, need full GraphQL response | Full GraphQL response structure |

📚 Learn More:

  • Auto-Pagination: docs/02-CORE-GUIDES/auto-pagination/ - Cursor-based pagination guide (7 modules)
  • Extraction: docs/02-CORE-GUIDES/extraction/ - Complete extraction guide

Event API (Upsert Product)

Real-world example sending product data via Event API (triggers Rubix workflows).

import { createClient } from '@fluentcommerce/fc-connect-sdk';

const client = await createClient({
  config: {
    /* OAuth2 */
  },
});

// Send product upsert event
const result = await client.sendEvent({
  name: 'UPSERT_PRODUCT',
  entityType: 'PRODUCT_CATALOGUE',
  entitySubtype: 'MASTER',
  entityRef: 'PC:MASTER:2',
  rootEntityType: 'PRODUCT_CATALOGUE',
  rootEntityRef: 'PC:MASTER:2',
  retailerId: '2',
  attributes: {
    ref: 'VAR_PRODUCT',
    type: 'VARIANT',
    status: 'ACTIVE',
    gtin: '1234567',
    name: 'Chaz Kangeroo Hoodie-XS-Orange',
    summary: 'Comfortable hoodie in orange',
    categoryRefs: ['STANDARD_CATEGORY'],
    price: [
      {
        type: 'DEFAULT',
        currency: 'USD',
        value: 49.99,
      },
    ],
    taxType: {
      country: 'US',
      group: 'HD',
    },
  },
});

console.log('✅ Event sent successfully');
// Output: { id: 'evt_123', status: 'CREATED' }

📚 Learn More:

  • Event API Templates: docs/01-TEMPLATES/versori/workflows/ingestion/event-api/ (8 templates for S3/SFTP with CSV, XML, JSON, Parquet)
  • Batch API Guide: docs/02-CORE-GUIDES/ingestion/modules/02-CORE-GUIDES-ingestion-06-batch-api.md

Event Log API (Search + Audit)

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.

Methods:

  • getEvents(params)GET /api/v4.1/event - Search/filter events with flexible query parameters
  • getEventById(eventId)GET /api/v4.1/event/{eventId} - Get a single event by ID

Important: These methods query the Event Log (audit trail). They do NOT trigger workflows — use sendEvent() for that.

Basic Usage

import { createClient } from '@fluentcommerce/fc-connect-sdk';

const client = await createClient({
  config: { baseUrl, clientId, clientSecret, username, password },
});

// 1) Search for failed orchestration audit events on orders
const logs = await client.getEvents({
  'context.rootEntityType': 'ORDER',
  eventType: 'ORCHESTRATION_AUDIT',
  eventStatus: 'FAILED',
  count: 100,
});

console.log(`Found ${logs.results.length} events (hasMore=${logs.hasMore})`);

for (const event of logs.results) {
  console.log(`  ${event.name} [${event.eventStatus}] on ${event.context?.entityType}:${event.context?.entityRef}`);
}

// 2) Get full details for a specific event by ID
if (logs.results[0]?.id) {
  const event = await client.getEventById(logs.results[0].id);
  console.log(`Event: ${event.name} - Status: ${event.eventStatus}`);
  console.log(`  Entity: ${event.context?.entityType}:${event.context?.entityRef}`);
  console.log(`  Root: ${event.context?.rootEntityType}:${event.context?.rootEntityRef}`);
  console.log(`  Generated: ${event.generatedOn} by ${event.generatedBy}`);

  // Trace parent events (for debugging event chains)
  if (event.context?.sourceEvents?.length) {
    for (const parentId of event.context.sourceEvents) {
      const parent = await client.getEventById(parentId);
      console.log(`  Parent: ${parent.name} (${parent.eventStatus})`);
    }
  }
}

Response Shape

getEvents() returns FluentEventLogResponse:

{
  start: 1,                    // Pagination offset
  count: 1000,                 // Results in this page
  hasMore: false,              // More pages available?
  results: [                   // Array of FluentEventLogItem
    {
      id: "e2cc5040-...",      // UUID
      name: "BATCH_COMPLETE",  // Event/ruleset name (can be null)
      type: "ORCHESTRATION_AUDIT",
      accountId: "MYACCOUNT",
      retailerId: "5",         // String in response (not number)
      category: "BATCH",
      context: {
        sourceEvents: ["babc5f37-..."],  // Parent event IDs for tracing
        entityType: "BATCH",
        entityId: "12",
        entityRef: "12",
        rootEntityType: "JOB",
        rootEntityId: "13",
        rootEntityRef: "13"
      },
      eventStatus: "COMPLETE",
      attributes: null,        // null OR array of { name, value, type }
      source: null,
      generatedBy: "Rubix User",
      generatedOn: "2026-02-05T06:31:56.895+00:00"
    }
  ]
}

getEventById(id) returns a single FluentEventLogItem (same shape as each item in results).

Query Parameters

All parameters are optional. Context filters use dot-notation keys which require quotes in JavaScript/TypeScript:

// ⚠️ Dot-notation keys MUST be quoted (JavaScript syntax requirement)
await client.getEvents({
  'context.rootEntityType': 'ORDER',  // ✅ Quotes required (has dot)
  'context.entityRef': 'ORD-123',     // ✅ Quotes required (has dot)
  eventType: 'ORCHESTRATION_AUDIT',   // ✅ No quotes needed (simple key)
  count: 100,                         // ✅ No quotes needed (simple key)
});

| Parameter | Type | Description | Example | |-----------|------|-------------|---------| | context.rootEntityType | string | Root entity type | 'ORDER', 'JOB', 'LOCATION' | | context.rootEntityId | string | Root entity ID | '12345' | | context.rootEntityRef | string | Root entity reference | 'ORD-12345' | | context.entityType | string | Sub-entity type | 'FULFILMENT', 'BATCH', 'ARTICLE' | | context.entityId | string | Sub-entity ID | '67890' | | context.entityRef | string | Sub-entity reference | 'FUL-67890' | | eventType | string | Event type filter | 'ORCHESTRATION_AUDIT' | | eventStatus | string | Status filter | 'FAILED', 'COMPLETE' | | name | string | Event/ruleset name | 'BATCH_COMPLETE' | | category | string | Category filter | 'ruleSet', 'ACTION' | | from | string | Start date (UTC ISO 8601) | '2026-02-01T00:00:00.000Z' | | to | string | End date (UTC ISO 8601) | '2026-02-15T23:59:59.999Z' | | retailerId | string/number | Retailer filter (pass explicitly; no auto-fallback) | '5' | | start | number | Pagination offset (default: 0) | 0 | | count | number | Results per page (default: 100, max: 5000) | 1000 |

Valid Values Reference

| Parameter | Valid Values | |-----------|-------------| | eventType | ORCHESTRATION, ORCHESTRATION_AUDIT, API, INTEGRATION, SECURITY, GENERAL | | eventStatus | PENDING, SCHEDULED, NO_MATCH, SUCCESS, FAILED, COMPLETE | | category | snapshot, ruleSet, rule, ACTION, CUSTOM, exception, ORDER_WORKFLOW, BATCH | | rootEntityType | ORDER, LOCATION, FULFILMENT_OPTIONS, PRODUCT_CATALOGUE, INVENTORY_CATALOGUE, VIRTUAL_CATALOGUE, CONTROL_GROUP, RETURN_ORDER, BILLING_ACCOUNT, JOB | | 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 |

Common Use Cases

// 1. Find failed events in the last 24 hours
const failedEvents = await client.getEvents({
  eventStatus: 'FAILED',
  from: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  to: new Date().toISOString(),
  count: 1000,
});

// 2. Track batch job processing (all events for a specific job)
const batchEvents = await client.getEvents({
  'context.rootEntityType': 'JOB',
  'context.rootEntityId': '13',
  eventType: 'ORCHESTRATION_AUDIT',
  count: 500,
});

// 3. Paginate through large result sets
let start = 0;
const allEvents: any[] = [];
let hasMore = true;

while (hasMore) {
  const page = await client.getEvents({
    'context.rootEntityType': 'ORDER',
    start,
    count: 1000,
  });
  allEvents.push(...page.results);
  hasMore = page.hasMore;
  start += page.count;
}
console.log(`Total events collected: ${allEvents.length}`);

// 4. Extract performance timing from event attributes
const auditEvents = await client.getEvents({
  eventType: 'ORCHESTRATION_AUDIT',
  'context.rootEntityType': 'JOB',
  count: 100,
});

for (const event of auditEvents.results) {
  if (Array.isArray(event.attributes)) {
    const startTimer = event.attributes.find(a => a.name === 'startTimer');
    const stopTimer = event.attributes.find(a => a.name === 'stopTimer');
    if (startTimer && stopTimer) {
      const duration = Number(stopTimer.value) - Number(startTimer.value);
      console.log(`${event.name}: ${duration}ms`);
    }
  }
}

// 5. Find events for a specific order by reference
const orderEvents = await client.getEvents({
  'context.rootEntityType': 'ORDER',
  'context.rootEntityRef': 'HD_12345',
  eventType: 'ORCHESTRATION_AUDIT',
});

When to Use Which Method

| Method | Use When | Returns | Example | |--------|----------|---------|---------| | sendEvent() | Trigger a workflow or business action | Event creation response | Product upsert, order cancel, location update | | getEvents() | Search/filter historical events | { results[], hasMore, start, count } | Find failed events, audit batch processing | | getEventById() | Get full details for one event by ID | Single event object | Drill into context, attributes, trace sourceEvents |

Limitations & Operational Notes

| Constraint | Value | Notes | |------------|-------|-------| | Max count per request | 5000 | Use pagination (start/count/hasMore) for larger result sets | | Time range (from) | 4 months back max | API rejects queries older than ~4 months | | Time range (to) | 1 month forward max | API rejects future dates beyond ~1 month | | Default time window | Past 30 days | Applied when from is omitted | | Date format | UTC ISO 8601 | YYYY-MM-DDTHH:mm:ss.SSSZ | | retailerId | Pass explicitly | Unlike sendEvent(), no auto-fallback to client config — pass in params if needed | | attributes field | Nullable | Can be null (not empty array) — always check before iterating | | name field | Nullable | Can be null for some system events | | GraphQL | Not available | Event queries use REST only — no GraphQL events root field exists |

Key points:

  • getEvents() and getEventById() are read-only — they do NOT trigger workflows
  • Empty results: [] is valid (not an error) — your filter may simply match zero events
  • Always use bounded time ranges (from/to) for predictable performance
  • Use eventType: 'ORCHESTRATION_AUDIT' for workflow execution history
  • Use context.sourceEvents array on each event to trace parent event chains
  • The attributes field is null for most events; only rule/ruleset events populate it
  • retailerId in responses is a string (e.g., "5") even though you can pass a number in params

📚 Detailed Guide: docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md


Error Classification

Automatically classify GraphQL errors for retry decisions and error handling.

import { classifyError, classifyErrors, type ErrorClassification } from '@fluentcommerce/fc-connect-sdk';

// Handle errors from GraphQL response
const result = await client.graphql({ query, variables });

if (result.errors) {
  const classifications = classifyErrors(result.errors);

  for (const classification of classifications) {
    console.log({
      code: classification.code,
      severity: classification.severity,    // 'client' | 'server' | 'transient'
      retryable: classification.retryable,  // boolean
      action: classification.action         // Recommended action
    });
  }

  // Check if any errors are retryable
  const shouldRetry = classifications.some(c => c.retryable);
  if (shouldRetry) {
    // Implement retry logic
  }
}

// Classify a single error
const singleError = { message: 'Rate limited', extensions: { code: 'T001' } };
const classification = classifyError(singleError);
console.log(classification.retryable); // true (transient error)

Error Code Prefixes:

  • C (Client) - Bad request, validation errors → Not retryable
  • S (Server) - Internal errors → May be retryable
  • T (Transient) - Rate limits, timeouts → Retryable

📚 Learn More:

  • Comprehensive Error Handling Guide: docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md - Complete reference with examples for all APIs
  • Quick Reference: docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md - Cheat sheet for error codes and retry strategies
  • API Reference: docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md - Error classification utilities

CLI Tooling

All CLI commands are distributed with the package. Use via npx:

npx fc-connect <command> [options]

| Command | Purpose | | -------------------------------------- | ------------------------------------------ | | npx fc-connect analyze-source-structure 🆕 | Analyze data files and generate mapping templates | | npx fc-connect introspect-schema | Download the Fluent GraphQL schema | | npx fc-connect generate-mutation-mapping | Build ingestion mapping templates | | npx fc-connect generate-query-mapping | Build extraction mapping templates | | npx fc-connect validate-schema | Validate mapping config against the schema | | npx fc-connect analyze-coverage | Report required / optional field coverage |

🚀 Quick Start: Analyze Your Data

# Analyze XML/JSON/CSV/Parquet files
npx fc-connect analyze-source-structure --file ./your-data.xml

# Generate mapping template + documentation
npx fc-connect analyze-source-structure \
  --file ./inventory.xml \
  --output ./mapping.json \
  --markdown ./docs/analysis.md

What you get:

  • ✅ All available fields and paths
  • ✅ Array detection and structure insights
  • ✅ Smart resolver suggestions (parseFloat, trim, etc.)
  • ✅ Ready-to-use mapping templates
  • ✅ Shareable documentation

📚 Learn More: docs/00-START-HERE/CLI-ANALYZE-SOURCE-STRUCTURE-GUIDE.md - Complete user guide | docs/02-CORE-GUIDES/mapping/ - Field mapping guides | docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md - Complete CLI documentation


Authentication & Webhooks

OAuth2 with Automatic Token Management:

  • ✅ Refresh token support (uses grant_type=refresh_token when available)
  • ✅ Automatic 401 retry with exponential backoff (3 retries max)
  • ✅ Thread-safe token refresh (prevents duplicate requests)
  • ✅ 60-second expiry buffer (prevents mid-flight token expiry)
  • ✅ Handles clock skew, server-side revocation, multi-process conflicts

Webhook Validation:

  • Cryptographic signature validation with RSA (SHA256withRSA, SHA1withRSA, SHA512withRSA)
// Authentication happens automatically - just create client
const client = createClient({ fetch, log, activation });

// Webhook validation with signature verification
const isValid = await client.validateWebhook(body, headers['fluent-signature'], rawBody);
if (!isValid) return new Response('Invalid signature', { status: 401 });

Webhook Authentication Service:

  • Secure API key generation (cryptographically random, 64-char hex)
  • API key validation with SHA-256 hashing
  • Universal compatibility - Works in Node.js, Deno, Versori, Cloudflare Workers, Vercel Edge, and more!
  • Uses Web Crypto API (native to all modern runtimes)

retailerId Configuration

🆕 Latest: retailerId is now OPTIONAL at client creation!

When Do You Need It?

| API | Needs retailerId? | |-----|-------------------| | GraphQL queries/mutations | ❌ NO (pass in mutation input if needed) | | Job API (createJob) | ✅ YES | | Event API (sendEvent) | ✅ YES | | Batch API (sendBatch) | ❌ NO (uses jobId) |

Three Ways to Provide It

// Option 1: In client config (standalone/Node.js)
const client = await createClient({
  config: {
    baseUrl: 'https://api.fluentcommerce.com',
    clientId: '...',
    clientSecret: '...',
    retailerId: '1'  // ← Optional
  }
});

// Option 2: Set after client creation (Versori fn() workflows)
const client = await createClient({ data: {}, log, openKv });
client.setRetailerId('1');  // ← Only if using Job/Event APIs

// Option 3: Pass directly to methods (multi-tenant)
await client.createJob({ name: 'sync', retailerId: '1' });
await client.sendEvent({ name: 'Test', entityType: 'PRODUCT', retailerId: '2' });

📖 Complete Guide: docs/00-START-HERE/RETAILERID-CONFIGURATION.md

import { WebhookAuthService } from '@fluentcommerce/fc-connect-sdk';

// Initialize service
const authService = new WebhookAuthService(logger);

// API Key: Generate
const apiKey = await authService.generateApiKeyWithPrefix('prod', {
  userId: 'client1',
  scopes: ['webhook'],
});

// API Key: Validate
const validation = await authService.validateApiKey(
  apiKey,
  async (key) => {
    // Lookup API key hash in your storage
    const hash = await authService.hashApiKey(key);
    return await lookupApiKeyMetadata(hash);
  }
);

📚 Learn More:

  • docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md - API key security guide
  • docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md - Connection-based security
  • docs/02-CORE-GUIDES/webhook-validation/ - Complete webhook security guide (5 modules)
  • docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md - Authentication API reference

🚀 Standalone Usage (Outside Versori)

Use the SDK in your own TypeScript/Node.js applications - not just in Versori workflows.

Quick Example: Custom REST API Calls

import { createClient } from '@fluentcommerce/fc-connect-sdk';

// Create client with OAuth2 authentication
const client = await createClient({
  config: {
    baseUrl: 'https://api.fluentcommerce.com',
    clientId: 'FLUENT_INTEGRATION',
    clientSecret: 'your-client-secret',
    username: 'your-username',
    password: 'your-password',
    retailerId: 'your-retailer-i