JSPM

tracing-platform-sdk

1.0.0
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 5
    • Score
      100M100P100Q76093F
    • License MIT

    Official Node.js SDK for the Tracing Platform

    Package Exports

    • tracing-platform-sdk

    Readme

    tracing-platform-sdk

    Official Node.js/TypeScript SDK for the Tracing Platform — DID-based supply chain traceability built on EPCIS 2.0 and W3C DID standards.

    npm version Node.js License: MIT

    Installation

    npm install tracing-platform-sdk

    Requirements: Node.js >= 18, zero production dependencies.


    Quick Start

    import { TracingClient } from 'tracing-platform-sdk';
    
    const client = new TracingClient({
      baseUrl: 'https://api.tracing.sotatek.works',
      clientId: process.env.TRACING_CLIENT_ID!,
      clientSecret: process.env.TRACING_CLIENT_SECRET!,
    });
    
    // List products (page 1)
    const { data, meta } = await client.products.list({ page: 1, limit: 20 });
    console.log(`${meta.total} total products`);
    
    // Stream all pages
    for await (const page of client.products.paginate()) {
      console.log(page); // FooItem[]
    }

    Architecture

    The SDK is organized as a layered DI container:

    TracingClient                  ← consumer entry point
      ├── OAuthManager             ← token lifecycle: fetch, cache, refresh, dedup
      ├── HttpClient               ← fetch wrapper: auth injection, retry, error mapping
      └── 14 Feature Modules       ← one class per domain, each receives HttpClient

    TracingClient wires everything together and exposes modules as client.products, client.commission, etc. No global state — each TracingClient instance is fully isolated.


    Configuration

    const client = new TracingClient({
      baseUrl:    'https://api.tracing.sotatek.works', // required
      clientId:    process.env.TRACING_CLIENT_ID!,     // required
      clientSecret: process.env.TRACING_CLIENT_SECRET!, // required
      timeout:    30_000,   // per-request timeout in ms (default: 30000)
      maxRetries: 3,        // retry attempts on 429/5xx (default: 3)
      debug:      false,    // log METHOD URL → STATUS (Xms) (default: false)
    });

    Authentication

    The SDK implements OAuth2 Client Credentials (RFC 6749 §4.4) via OAuthManager:

    POST {baseUrl}/api/v1/oauth/token
    { "client_id": "...", "client_secret": "...", "grant_type": "client_credentials" }
    → { "access_token": "...", "expires_in": 3600, "token_type": "Bearer" }

    Token lifecycle:

    • Token is fetched lazily on the first API call
    • Cached in memory; reused until expires_in - 60s (auto-refresh before expiry)
    • Concurrent calls share a single in-flight token request — no duplicate fetches
    • On 401, the cached token is invalidated and one retry is attempted automatically

    Obtain credentials from the Enterprise Portal → Settings → API Credentials.


    HTTP Client

    All requests go through HttpClient which handles:

    Feature Detail
    Base path All requests prefixed with /api/v1
    Auth injection Authorization: Bearer {token} added automatically
    Retry 429, 500, 502, 503, 504 — exponential backoff: 1s → 2s → 4s … max 16s
    Idempotency POST requests auto-generate a UUID Idempotency-Key header
    Timeout Per-request AbortController (default 30s, configurable)
    Debug logging [tracing-platform-sdk] GET /api/v1/products → 200 (142ms) — token redacted

    Modules

    client.auth — Authentication Status

    const status = await client.auth.status();
    // { connected: true, expiresAt: 1716290400000, scopes: ['products.read', ...] }

    client.products — Product Registry

    // List products
    const { data, meta } = await client.products.list({ page: 1, limit: 20 });
    
    // Get product detail
    const product = await client.products.get('product-id');
    
    // Create a product
    const newProduct = await client.products.create({
      name: 'Organic Coffee',
      gtin: '00012345600012',
      unit: 'kg',
    });
    
    // Search lots
    const lots = await client.products.listLots('product-id', { page: 1 });

    client.commission — LOT Genesis Events

    Commission events record the creation (genesis) of a LOT in the supply chain.

    const event = await client.commission.create({
      productId: 'prod-123',
      lotCode: 'LOT-2026-001',
      quantity: 1000,
      unit: 'kg',
      producedAt: '2026-01-15T08:00:00Z',
    });
    
    // Paginate all commission events
    for await (const page of client.commission.paginate({ productId: 'prod-123' })) {
      page.forEach(e => console.log(e.lotCode));
    }

    client.aggregation — Pack / Unpack Containers

    // Pack items into a container
    await client.aggregation.pack({
      parentId: 'container-sscc-001',
      childIds: ['lot-a', 'lot-b', 'lot-c'],
    });
    
    // Unpack a container
    await client.aggregation.unpack({ parentId: 'container-sscc-001' });

    client.shipping — Shipment Tracking

    // Create shipment
    const shipment = await client.shipping.create({
      lotIds: ['lot-001', 'lot-002'],
      fromLocationId: 'loc-warehouse',
      toLocationId: 'loc-distribution',
      shippedAt: '2026-02-01T09:00:00Z',
    });
    
    // Get Advanced Shipping Notice (ASN)
    const asn = await client.shipping.getAsn(shipment.id);

    client.receiving — QC & Custody Transfer

    // Receive and QC lots
    await client.receiving.receive({
      shipmentId: 'ship-001',
      receivedAt: '2026-02-02T14:00:00Z',
      qcStatus: 'passed',
      notes: 'All items verified',
    });

    client.transformation — Input LOTs → Output LOT

    Records processing events where input lots are consumed to produce output lots.

    await client.transformation.create({
      inputLotIds: ['raw-lot-001', 'raw-lot-002'],
      outputLot: {
        productId: 'finished-product-id',
        lotCode: 'FINISHED-001',
        quantity: 500,
        unit: 'kg',
      },
      transformedAt: '2026-03-01T10:00:00Z',
    });

    client.recall — Recall Alerts

    // Create a recall alert
    const recall = await client.recall.create({
      lotIds: ['lot-001', 'lot-002'],
      reason: 'Contamination detected',
      severity: 'high',
    });
    
    // List active recalls
    const { data } = await client.recall.list({ status: 'active' });

    client.traceability — LOT Timeline & Trace

    // Full event timeline for a lot
    const timeline = await client.traceability.getTimeline('lot-001');
    
    // Trace upstream (find source lots)
    const upstream = await client.traceability.traceUpstream('lot-001');
    
    // Trace downstream (find where it went)
    const downstream = await client.traceability.traceDownstream('lot-001');

    client.anchor — Blockchain Anchoring

    // Get blockchain anchor status for a lot
    const anchor = await client.anchor.getStatus('lot-001');
    // { anchored: true, txHash: '0xabc...', chain: 'ethereum', anchoredAt: '...' }

    client.custody — Custody Chain

    // Get custody overview for a product
    const overview = await client.custody.getOverview('product-id');
    
    // Get custody stats by product
    const stats = await client.custody.getStats({ productId: 'prod-123' });

    client.didProfile — DID Identity Profiles

    // Get DID profile for an organization
    const profile = await client.didProfile.get('did:example:123');
    
    // Update profile
    await client.didProfile.update('did:example:123', {
      name: 'Acme Farms',
      certifications: ['organic', 'fair-trade'],
    });

    client.members — Enterprise Members

    // List organization members
    const { data } = await client.members.list();
    
    // Invite a member
    await client.members.invite({ email: 'user@example.com', role: 'operator' });

    client.webhook — Webhook Events

    // List configured webhooks
    const webhooks = await client.webhook.list();
    
    // Create a webhook endpoint
    await client.webhook.create({
      url: 'https://myapp.com/webhooks/tracing',
      events: ['commission.created', 'recall.created'],
      secret: 'my-signing-secret',
    });

    Pagination

    Every list endpoint exposes a paginate() async generator that handles page iteration automatically:

    // Iterate page by page (memory efficient)
    for await (const page of client.products.paginate({ limit: 50 })) {
      for (const product of page) {
        await process(product);
      }
    }
    
    // Or use PaginationHelper for more control
    import { PaginationHelper } from 'tracing-platform-sdk';
    
    const helper = new PaginationHelper((page, limit) =>
      client.products.list({ page, limit })
    );
    
    // Fetch a single page
    const page1 = await helper.fetchPage(1, 20);
    
    // Fetch all at once (caution: large datasets)
    const all = await helper.fetchAll(100);
    
    // Get pagination metadata only
    const meta = await helper.meta(1);
    // { total: 1500, page: 1, limit: 1, totalPages: 1500 }

    Error Handling

    All errors extend TracingError and include a statusCode:

    TracingError          (base — statusCode: number)
    ├── AuthError         401 — invalid or expired credentials
    ├── ForbiddenError    403 — insufficient permissions
    ├── NotFoundError     404 — resource not found
    ├── ValidationError   422 — request validation failed (includes field errors)
    ├── RateLimitError    429 — too many requests (includes retryAfter)
    └── ServerError       5xx — platform-side error
    import {
      TracingError, AuthError, ForbiddenError, NotFoundError,
      ValidationError, RateLimitError, ServerError,
    } from 'tracing-platform-sdk';
    
    try {
      await client.products.create({ name: '' });
    } catch (err) {
      if (err instanceof ValidationError) {
        // err.fields: Record<string, string[]>
        console.error('Field errors:', err.fields);
        // { name: ['Name is required'], gtin: ['Invalid GTIN format'] }
      } else if (err instanceof RateLimitError) {
        // err.retryAfter: number (seconds)
        await sleep(err.retryAfter * 1000);
        // retry...
      } else if (err instanceof AuthError) {
        console.error('Check your clientId and clientSecret');
      } else if (err instanceof TracingError) {
        console.error(`HTTP ${err.statusCode}: ${err.message}`);
      }
    }

    OAuth Scopes

    Use SDK_SCOPES to reference scope strings safely:

    import { SDK_SCOPES, SdkScope } from 'tracing-platform-sdk';
    
    const requiredScopes: SdkScope[] = [
      SDK_SCOPES.PRODUCTS_READ,      // 'products.read'
      SDK_SCOPES.COMMISSION_WRITE,   // 'commission.write'
      SDK_SCOPES.TRACEABILITY_READ,  // 'traceability.read'
    ];
    
    // All available scopes:
    // commission.read/write  | aggregation.read/write | shipping.read/write
    // receiving.read/write   | transformation.read/write | recall.read/write
    // products.read/write    | traceability.read      | anchor.read
    // custody.read           | did-profile.read/write | members.read/write

    Debug Mode

    const client = new TracingClient({ ..., debug: true });
    
    // Console output:
    // [tracing-platform-sdk] GET /api/v1/products → 200 (142ms)
    // [tracing-platform-sdk] POST /api/v1/commission → 201 (89ms)
    // Authorization header is automatically redacted in logs

    License

    MIT