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

@ontemper/edi

v1.3.0

Published

An EDI client with structured logging.

Readme

Temper EDI Client

A TypeScript EDI client with structured logging and distributed tracing capabilities, built for processing X12 and UN/EDIFACT documents with comprehensive error handling and automatic span tracking.

Features

  • X12 EDI Processing: Complete X12 document parsing, validation, and generation
  • UN/EDIFACT Processing: Parse, validate, acknowledge (CONTRL), and generate EDIFACT interchanges
  • Structured Logging: Built-in integration with Temper Logger for comprehensive operation tracking
  • Distributed Tracing: Automatic span creation for all EDI operations with detailed payload information
  • Type Safety: Full TypeScript support with comprehensive type definitions
  • Error Handling: Custom error types with detailed error codes and context

Installation

npm install @ontemper/edi
# or
bun add @ontemper/edi
# or
yarn add @ontemper/edi

Environment Variables

TemperEdiClient is a thin HTTP client for Temper's EDI service, and it configures itself entirely from the environment — the platform injects the right values per environment, the same way it injects UNNBOUND_API_URL, so you don't set these by hand.

  • UNNBOUND_EDI_BASE_URL — where the EDI service is reachable from this environment. A deployed workflow runs inside the cluster and gets the service's internal URL directly (no credential — cluster networking is the trust boundary). The builder sandbox runs outside the cluster, so it gets Temper's authenticated /api/edi-gateway proxy instead.
  • UNNBOUND_EDI_API_KEY — the credential for whichever path needs one: a platform-minted JWT through the sandbox gateway, or an EDINation-cloud key in the legacy no-base-URL setup. The direct in-cluster path needs no key and ignores it.

The client throws only if it finds neither. There's nothing to provision, hardcode, or gate on — just new TemperEdiClient().

Quick Start

import { TemperEdiClient } from '@ontemper/edi';
import { logger } from 'unnbound-logger-sdk';

// In a Temper workflow environment the EDI connection is already configured
// via env (platform-injected) — just construct the client:
const edi = new TemperEdiClient();

// Parse X12 document to JSON
const x12Interchanges = await edi.fromX12({
  input: 'ISA*00*          *00*          *12*NOTP...',
});

// Validate X12 interchange
const validationResult = await edi.validateX12({
  input: x12Interchanges[0],
});

// Convert JSON back to X12
const x12Document = await edi.toX12({
  input: x12Interchanges[0],
});

// Generate and serialize acknowledgments
const acknowledgments = await edi.acknowledgeX12({
  input: x12Interchanges[0],
});

for (const acknowledgment of acknowledgments) {
  // Apply partner-specific outbound envelope IDs here when needed.
  const acknowledgmentX12 = await edi.toX12({ input: acknowledgment });
}

API Reference

Constructor

new TemperEdiClient(); // platform-injected env
new TemperEdiClient({ token }); // caller-supplied gateway credential

Creates a new EDI client instance. It configures itself from two platform-owned environment variables — UNNBOUND_EDI_BASE_URL (where Temper's EDI service is reachable from this environment: the in-cluster URL in a deployed workflow, the /api/edi-gateway proxy in the builder sandbox, or the public gateway origin in environments that cannot reach the cluster) and UNNBOUND_EDI_API_KEY (the credential for whichever path needs one — a gateway JWT, or a legacy EDINation-cloud key; the direct in-cluster path ignores it). In a Temper workflow environment the platform injects the right pair. Throws only if neither is set.

A workflow that authenticates on its own behalf passes the run's invocation token as token. The client presents it as the gateway credential (Ocp-Apim-Subscription-Key) and as the Authorization: Bearer credential for control-number allocation, so concurrent runs never share a credential and the process environment stays untouched. An explicit UNNBOUND_EDI_API_KEY always wins, which keeps platform-injected gateway JWTs and customer EDINation keys authoritative.

Methods

fromX12(options: FromX12Options): Promise<X12Interchange[]>

Parses X12 EDI documents into JSON format.

const x12Interchanges = await edi.fromX12({
  input: 'ISA*00*          *00*          *12*NOTP...',
});

Parameters:

  • options.input - The X12 document as a string or File object

Returns: Array of parsed X12 interchange objects

toX12(options: ToX12Options): Promise<string>

Converts JSON X12 interchange objects back to X12 format. Automatically stamps control numbers (ISA13, GS06, ST02) and their matching trailers if not already set.

const x12Document = await edi.toX12({
  input: x12InterchangeObject,
});

Parameters:

  • options.input - The X12 interchange object to convert

Returns: X12 document as string

Control number auto-stamping: Leave InterchangeControlNumber_13, GroupControlNumber_6, and TransactionSetControlNumber_02 empty or omit them. Empty and all-zero ISA13, GS06, or ST02 values are treated as unassigned. Valid non-zero values are preserved.

validateX12(options: ValidateX12Options): Promise<OperationResult>

Validates X12 interchange objects against EDI standards.

const validationResult = await edi.validateX12({
  input: x12InterchangeObject,
});

Parameters:

  • options.input - The X12 interchange object to validate

Returns: Validation result with operation details

acknowledgeX12(options: AcknolwedgeX12Options): Promise<X12Interchange[]>

Generates acknowledgment interchange objects. Apply any partner-specific outbound envelope IDs, then serialize each acknowledgment with toX12(). During serialization, toX12() assigns control numbers using the final sender and receiver. The method always returns an array because one input can produce more than one acknowledgment.

const acknowledgments = await edi.acknowledgeX12({
  input: x12InterchangeObject,
});

for (const acknowledgment of acknowledgments) {
  // Apply partner-specific outbound envelope IDs here when needed.
  const serialized = await edi.toX12({ input: acknowledgment });
}

Parameters:

  • options.input - The X12 interchange object to acknowledge

Returns: The generated acknowledgment interchanges

UN/EDIFACT

fromEdifact, toEdifact, validateEdifact, and acknowledgeEdifact take the same options as their X12 counterparts and call the service's /edifact/* routes. They use the same errors and error codes.

const [interchange] = await edi.fromEdifact({ input: "UNA:+.? 'UNB+UNOA:2+SENDER+RECEIVER+961101:1200+1'…" });
const verdict = await edi.validateEdifact({ input: interchange });
const contrl = await edi.acknowledgeEdifact({ input: interchange }); // [] when no CONTRL is due
const edifact = await edi.toEdifact({ input: interchange });

toEdifact does not stamp control numbers: set the interchange reference (UNB 0020, UNB.InterchangeControlReference_5, and the matching UNZ) and each message reference (UNH 0062) before you serialize. This also applies to each CONTRL that acknowledgeEdifact returns: the service fills in the same reference every time, and a partner can refuse a repeated one. The service loads the message model (for example D.96A ORDERS) on first use.

Error Handling

The EDI client throws two distinct error classes. The distinction matters for file routing: a TemperEdiClientError is a verdict on the input document (bad file — don't retry) or a persistent authentication failure, while an EdiInfrastructureError is retryable and does not indicate that the document is invalid (error.retryable === true).

import { isEdiInfrastructureError, isTemperEdiClientError } from '@ontemper/edi';

async function processFile(rawX12: string) {
  try {
    return await edi.fromX12({ input: rawX12 });
  } catch (error) {
    if (isEdiInfrastructureError(error)) {
      // Keep the file and rethrow so the caller can retry.
      throw error;
    }
    if (isTemperEdiClientError(error)) {
      if (error.code === 'edi_unauthorized_error') {
        // Surface authentication failures instead of quarantining the file.
        throw error;
      }
      // A verdict on the document (see codes below) — handle it terminally (e.g.
      // quarantine the file) and STOP. Do not rethrow into retry paths.
      console.error('EDI Error:', error.code, error.message);
      return undefined;
    }
    throw error; // unrecognized — surface to the caller
  }
}

Error Codes

TemperEdiClientError (verdict on the document):

| Code | Description | | ------------------------ | ------------------------------------------- | | edi_read_error | Error parsing an X12 or EDIFACT document | | edi_write_error | Error converting JSON to X12 or EDIFACT | | edi_validate_error | Error validating an X12 or EDIFACT document | | edi_acknowledge_error | Error generating acknowledgment | | edi_unauthorized_error | Unauthorized access to EDI service | | edi_unknown_error | Legacy — no longer produced |

EdiInfrastructureError (retryable: true):

| Code | Description | | ---------------------- | ------------------------------------------------- | | infrastructure_error | Retryable failure unrelated to the input document |

Type Definitions

FromX12Options

interface FromX12Options {
  input: unknown; // X12 document as string or File
}

ToX12Options

interface ToX12Options {
  input: X12Interchange; // X12 interchange object
}

ValidateX12Options

interface ValidateX12Options {
  input: X12Interchange; // X12 interchange object
}

AcknolwedgeX12Options

interface AcknolwedgeX12Options {
  input: X12Interchange; // X12 interchange object
}

OperationResult

interface OperationResult {
  LastIndex?: number;
  Details?: Array<OperationDetail> | null;
  Status?: string | null;
}

OperationDetail

interface OperationDetail {
  Index?: number;
  TransactionIndex?: number;
  TransactionRef?: string | null;
  SpecRef?: string | null;
  SegmentId?: string | null;
  DataElementId?: string | null;
  DataElementIndex?: number;
  CompositeIndex?: number;
  RepIndex?: number;
  Value?: string | null;
  Message?: string | null;
  Status?: string | null;
}

Logging and Tracing

The Temper EDI Client automatically creates spans for all operations and logs detailed information including:

  • Operation type and timing
  • Input and output data
  • API request/response details
  • Error details (if any)
  • Validation results
  • Acknowledgment status

Example Log Output

{"level":"info","spanId":"556c53e6-3bb8-4118-b3b8-570e41c27654","type":"edi","edi":{"type":"x12","x12":{"input":"ISA*00*...","output":[...]}},"message":"X12 to JSON started."}
{"level":"info","spanId":"556c53e6-3bb8-4118-b3b8-570e41c27654","type":"edi","edi":{"type":"x12","x12":{"input":"ISA*00*...","output":[...]}},"duration":1250,"message":"X12 to JSON completed."}

Complete Example

import { TemperEdiClient } from '@ontemper/edi';
import { logger } from 'unnbound-logger-sdk';

const run = async () => {
  const edi = new TemperEdiClient();

  const x12Data = `ISA*00*          *00*          *12*NOTP           *12*NOTP           *080501*1700*U*00401*000001000*0*P*>
GS*PO*NOTP*NOTP*20080501*1700*1000*X*004010
ST*850*000000010
BEG*00*SA*08292243254**20010501*610385388
REF*DP*030
REF*PS*
ITD*14*3*2**45**46
DTM*001*20010510
PKG*F*68***PALLET, SHRINKWRAP 48W X 40D X 45H
PKG*F*66***REGULAR
TD5*A*92*P3**SEE ROUTING GUIDE FOR ROUTING
N1*ST*RETAIL STORE*9*0001234567890
N3*123 ANYWHERE AVENUE
N4*CITY*ST*12345
PO1*1*120*EA*9.25*TE*CB*(12) 0-083628-838*PR*RO*VN*ABA18783
PO1*2*220*EA*13.7 9*TE*CB*(69) 0-093 83 7-991*PR*RO*VN*RUP83112
PO1*3*126*EA*10.9 9*TE*CB*(71) 0-099172-837*PR*RO*VN*CPR19293
PO1*4*76*EA*4.35*TE*CB*(71) 0-012110-737*PR*RO*VN*PIW28173
PO1*5*72*EA*7.5*TE*CB*(71) 0-0848 88-9 75*PR*RO*VN*JBM1938 7
PO1*6*696*EA*9.55*TE*CB*(71) 0-003 922-121*PR*RO*VN*IUI19283
CTT*6
SE*20*000000010
GE*1*1000
IEA*1*000001000`;

  try {
    // Parse X12 to JSON
    const x12Interchanges = await edi.fromX12({ input: x12Data });
    logger.info({ count: x12Interchanges.length }, 'Parsed X12 interchanges');

    // Process each interchange
    for (const interchange of x12Interchanges) {
      // Validate
      const validation = await edi.validateX12({ input: interchange });
      logger.info({ validation }, 'X12 validated');

      // Convert back to X12
      const x12Output = await edi.toX12({ input: interchange });
      logger.info({ x12Output }, 'Converted back to X12');

      // Generate and serialize acknowledgments. toX12 assigns control numbers
      // after any partner-specific outbound envelope changes.
      const acknowledgments = await edi.acknowledgeX12({ input: interchange });
      for (const acknowledgment of acknowledgments) {
        const acknowledgmentX12 = await edi.toX12({ input: acknowledgment });
        logger.info({ acknowledgmentX12 }, 'Generated acknowledgment');
      }
    }
  } catch (error) {
    logger.error({ err: error }, 'EDI processing failed');
    throw error;
  }
};

run().catch(console.error);

Control numbers

The SDK manages ISA13 and GS06 control numbers for each sender and receiver pair. toX12() automatically stamps empty or all-zero ISA13, GS06, and ST02 fields. It preserves valid non-zero values. ISA13 and GS06 use the centralized counter service. ST02 is assigned "0001".

Each Temper environment has a separate counter ledger. Production allocations continue the partner's production sequence. Sandbox and staging allocations do not consume production control numbers. A ledger rolls over after 999999999, so the next value is 000000001.

Automatic environment routing

Workflow deployments inject TEMPER_ENVIRONMENT. The SDK reads TEMPER_ENVIRONMENT first and falls back to the legacy UNNBOUND_ENVIRONMENT, which the platform still injects for older SDK versions. The value must be sandbox, staging, or production. A missing or invalid value causes a ControlNumberError with the control_number_environment_missing code before any HTTP request.

getNextControlNumber(), getCurrentControlNumber(), and toX12() use this environment. They do not accept an environment override. ISA15 (UsageIndicator_15) does not select a counter, and toX12() does not change it.

import { getCurrentControlNumber, getNextControlNumber } from '@ontemper/edi';

const senderId = 'ZZ:SENDER';
const receiverId = 'ZZ:RECEIVER';

const next = await getNextControlNumber('ISA13', senderId, receiverId);
const current = await getCurrentControlNumber('ISA13', senderId, receiverId);

The SDK has no seeding helper. Seed a counter through the Builder agent set_edi_control_number tool, which asks the user to approve each seed. The tool value is the next number: seeding 4600 makes the next allocation 4600. Seeding is forward-only. Re-seeding the same value is a no-op.

These helpers require UNNBOUND_API_URL. Temper configures it in workflow environments.

Requirements

  • Node.js >= 22.0.0
  • TypeScript (for TypeScript projects)
  • The EDI connection env — UNNBOUND_EDI_BASE_URL, plus UNNBOUND_EDI_API_KEY where the path needs a credential — is platform-injected per environment in Temper workflows; nothing to configure. Environments without an injected key pass the run's invocation token to the constructor instead; supply a key yourself only for the standalone legacy EDINation-cloud path.
  • UNNBOUND_API_URL (for control number auto-stamping, pre-configured in workflow environments)
  • TEMPER_ENVIRONMENT for counter routing

Dependencies

  • unnbound-logger-sdk: Structured logging and tracing

Support

For support, please open an issue on the GitHub repository.