Epicor Rest Helper for Node (npm) V2.0

EpicorRestNode v2.0.0 Release Notes

:tada: New Features

Advanced Response Control

Added two distinct response modes to give you full control over how API responses and errors are handled:

ResponseType: Switch between SIMPLE and FULL response modes

  • SIMPLE: Returns data directly, errors are returned (backward compatible with v1.x)
  • FULL: Returns EpicorResponse wrapper with status, message, data, and headers. Errors are thrown

PreserveSession: Control whether CreateSession() stores the session internally

  • true: CreateSession() stores the session in EpiSession for internal use (default)
  • false: CreateSession() returns the session but sets EpiSession to null (useful when frontend needs session ID but backend doesn’t store it)
import { EpicorRestService, EpicorResponseType } from 'epicor-rest-node';

const service = new EpicorRestService();
service.ResponseType = EpicorResponseType.FULL; // Default in v2.0.0
service.PreserveSession = true; // Default in v2.0.0

Enhanced Error Handling

Error handling now varies by response mode for maximum flexibility:

SIMPLE Mode - Errors are returned

service.ResponseType = EpicorResponseType.SIMPLE;

const result = await service.BoGet('Erp.BO.Customer', 'GetList');
if (EpicorError.isError(result)) {
  console.log(`Error ${result.status}: ${result.message}`);
} else {
  console.log('Data:', result);
}

FULL Mode - Errors are thrown

service.ResponseType = EpicorResponseType.FULL;

try {
  const result = await service.BoGet('Erp.BO.Customer', 'GetList');
  console.log(`Status: ${result.status} - ${result.message}`);
  console.log('Data:', result.data);
  console.log('Headers:', result.headers);
} catch (error) {
  if (EpicorError.isError(error)) {
    console.log(`Error ${error.status}: ${error.message}`);
  }
}

EpicorResponse Class

New response wrapper providing comprehensive metadata (FULL mode only):

class EpicorResponse<T> {
  status: number;      // HTTP status code (200, 201, etc.)
  message: string;     // HTTP status text or custom message
  data: T;            // Response data of type T
  headers: any;       // Response headers
  
  // Factory methods
  static fromAxios<T>(response: AxiosResponse): EpicorResponse<T>
  static fromAxiosWithMessage<T>(response: AxiosResponse, message: string): EpicorResponse<T>
}

Benefits:

  • Access response headers without additional parameters
  • Distinguish between successful responses with different status codes
  • Get consistent response structure across all API methods
  • Better debugging with status messages

Session Management Enhancements

CreateSession() - New capital β€˜S’ method with improved return type

// v2.0.0 - Returns session object or null
const session = await service.CreateSession();
if (session) {
  console.log(`Session ID: ${session.SessionId}`);
  console.log(`User ID: ${session.UserId}`);
  // ... make API calls
  await service.DestroySession();
} else {
  console.log('Failed to create session');
}

Createsession() - Deprecated (lowercase β€˜s’)

// v1.x style - Still supported but deprecated
const success = await service.Createsession(); // Returns boolean
if (success) {
  // ... make API calls
  await service.DestroySession();
}

New Session Context Methods:

// Set employee context
await service.SetEmployee('EMP123');

// Set plant/site context  
await service.SetPlant('MfgSys');

// Set workstation context
await service.SetWorkstation('WKST001');

// Synchronize client data
await service.SetClientData('john.doe', 'DESKTOP-ABC123', 'M/d/yyyy');

// Get session information
const sessionInfo = await service.GetSessionInfo();

// Get theme and user preferences
const themeInfo = await service.GetThemeInfo();

CallSettings Improvements

Added convenience methods for common operations:

import { CallSettings } from 'epicor-rest-node/dist/models/CallSettings';

const settings = new CallSettings('EPIC06', 'MfgSys', 'en', 'en-US', '0');

// New methods
settings.SetCompany('EPIC07');  // Switch company context
settings.SetPlant('Plant02');   // Switch plant context

EFX Staging Retry Flag

Added development-only retry mechanism for Epicor Functions:

service.EfxAttemptStagingRetry = true; // Development only!

// If function returns 404, automatically retries with staging endpoint
const result = await service.EfxPost('MyLibrary', 'MyNewFunction', params);

:warning: Important: Only enable during development to test functions that exist in staging but not yet in production.


:police_car_light: Breaking Changes

1. Default Response Type Changed

Old Behavior (v1.x):

// Always returned data directly
const result = await service.BoGet('Erp.BO.Customer', 'GetList');
if (EpicorError.isError(result)) {
  // Handle error
} else {
  console.log(result); // Direct data
}

New Behavior (v2.0.0):

// Default is FULL mode - returns EpicorResponse wrapper, throws errors
try {
  const result = await service.BoGet('Erp.BO.Customer', 'GetList');
  console.log(result.data); // Data is in .data property
} catch (error) {
  if (EpicorError.isError(error)) {
    // Handle error
  }
}

Migration: Set ResponseType = EpicorResponseType.SIMPLE for v1.x behavior:

import { EpicorResponseType } from 'epicor-rest-node/dist/models/EpicorType';

service.ResponseType = EpicorResponseType.SIMPLE;
// Now works exactly like v1.x

2. CreateSession Return Type

Old (v1.x):

const success: boolean = await service.Createsession();
if (success) {
  // Session created
}

New (v2.0.0):

const session: EpicorRestSession | null = await service.CreateSession();
if (session) {
  console.log(session.SessionId, session.UserId);
}

Migration: Continue using deprecated Createsession() (lowercase) for boolean return, or update to new CreateSession() (capital S).

3. Error Handling in FULL Mode

Old (v1.x):

// Errors always returned
const result = await service.BoGet('Invalid.BO', 'Method');
if (EpicorError.isError(result)) {
  // Handle error
}

New (v2.0.0 FULL mode):

// Errors are thrown
try {
  const result = await service.BoGet('Invalid.BO', 'Method');
} catch (error) {
  if (EpicorError.isError(error)) {
    // Handle error
  }
}

Migration: Wrap FULL mode calls in try/catch, or use SIMPLE mode for returned errors.


:books: Usage Examples

Session Management

Create and use a session:

import { EpicorRestService, EpicorError } from 'epicor-rest-node';

const service = new EpicorRestService();
// ... configure service properties

const session = await service.CreateSession();
if (session) {
  try {
    console.log(`Session: ${session.SessionId}`);
    
    // Set context
    await service.SetEmployee('EMP001');
    await service.SetPlant('MfgSys');
    
    // Make API calls
    const result = await service.BoGet('Erp.BO.Customer', 'GetList');
    console.log('Customers:', result.data);
    
    // Get session info
    const info = await service.GetSessionInfo();
    console.log('Current User:', info.UserID);
    console.log('Current Company:', info.CompanyID);
    
  } finally {
    await service.DestroySession();
  }
}

Response Type Comparison

SIMPLE Mode (v1.x compatible):

service.ResponseType = EpicorResponseType.SIMPLE;

// Returns data directly
const customers = await service.BoGet('Erp.BO.Customer', 'GetList', params);
if (EpicorError.isError(customers)) {
  console.log(`Error: ${customers.message}`);
  return;
}

// Direct data access
customers.value.forEach(customer => {
  console.log(customer.CustID);
});

FULL Mode (v2.0.0 recommended):

service.ResponseType = EpicorResponseType.FULL;

try {
  // Returns EpicorResponse wrapper
  const response = await service.BoGet('Erp.BO.Customer', 'GetList', params);
  
  console.log(`HTTP ${response.status}: ${response.message}`);
  console.log('Content-Type:', response.headers['content-type']);
  
  // Access data via .data property
  response.data.value.forEach(customer => {
    console.log(customer.CustID);
  });
} catch (error) {
  if (EpicorError.isError(error)) {
    console.log(`Error ${error.status}: ${error.message}`);
  }
}

PreserveSession Behavior

Preserved Session (default):

service.PreserveSession = true;

const session = await service.CreateSession();
console.log('Session created:', session.SessionId);
console.log('Stored internally:', service.EpiSession?.SessionId); // Same as session.SessionId

// Session is stored and can be reused
await service.BoGet('Erp.BO.Customer', 'GetList');
await service.BaqGet('MyBAQ');

await service.DestroySession();

Non-Preserved Session:

service.PreserveSession = false;

const session = await service.CreateSession();
console.log('Session created:', session.SessionId);
console.log('Not stored internally:', service.EpiSession); // null

// Frontend can use session.SessionId (x-session-id header)
// But backend service doesn't maintain the session internally

EFX Staging Retry (Development Only)

// Only during development!
service.EfxAttemptStagingRetry = true;

try {
  // Tries production endpoint first
  // If 404, automatically retries with staging endpoint
  const result = await service.EfxPost('MyLibrary', 'MyNewFunction', {
    param1: 'value1',
    param2: 'value2'
  });
  console.log('Function result:', result.data);
} catch (error) {
  if (EpicorError.isError(error)) {
    console.log('Function failed in both production and staging');
  }
}

// Production: Set to false
service.EfxAttemptStagingRetry = false; // Default

:wrench: Technical Details

Response Type Modes

Feature SIMPLE Mode FULL Mode
Return Type T | EpicorError EpicorResponse<T>
Error Handling Errors returned Errors thrown
Error Check if (EpicorError.isError(result)) try/catch
Data Access Direct (result) Via property (result.data)
Headers Via separate param In response (result.headers)
Status Code Only on error Always available (result.status)
Message Only on error Always available (result.message)
Backward Compatible :white_check_mark: Yes (v1.x) :cross_mark: No (v2.0.0 new)

Session Lifecycle

PreserveSession = true (default):

CreateSession() β†’ Session Created β†’ Stored in EpiSession β†’ Returns Session Object
                                          ↓
                              Service maintains session internally

PreserveSession = false:

CreateSession() β†’ Session Created β†’ EpiSession set to null β†’ Returns Session Object
                                          ↓
                              Frontend can use session ID
                              Service doesn't store internally

Method Signatures

All API methods now support:

// BO Methods
BoGet<T>(bo: string, method: string, params?: Map<string, string>, 
  callContext?: CallContext | null, additionalHeaders?: any, 
  capturedHeaders?: CapturedResponseHeaders): Promise<T | EpicorError>  // SIMPLE
BoGet<T>(...): Promise<EpicorResponse<T>>  // FULL (throws on error)

// BAQ Methods  
BaqGet<T>(baqId: string, params?: Map<string, string>,
  callContext?: CallContext | null, additionalHeaders?: any,
  capturedHeaders?: CapturedResponseHeaders): Promise<T | EpicorError>  // SIMPLE
BaqGet<T>(...): Promise<EpicorResponse<T>>  // FULL (throws on error)

// EFX Methods
EfxPost<T>(library: string, functionName: string, params?: any, 
  staging?: boolean, callContext?: CallContext | null, 
  additionalHeaders?: any, capturedHeaders?: CapturedResponseHeaders): 
  Promise<T | EpicorError>  // SIMPLE
EfxPost<T>(...): Promise<EpicorResponse<T>>  // FULL (throws on error)

// Session Methods
CreateSession(): Promise<EpicorRestSession | null>  // NEW
Createsession(): Promise<boolean>  // DEPRECATED
DestroySession(): Promise<boolean>
SetEmployee(employeeID: string): Promise<boolean>
SetPlant(plantID: string): Promise<boolean>
SetWorkstation(workstationID: string): Promise<boolean>
SetClientData(clientUserName: string, clientComputerName: string, 
  clientDateFormat?: string, appserver?: string, 
  clientTerminalID?: number): Promise<boolean>
GetSessionInfo(): Promise<any>
GetThemeInfo(): Promise<any>

:bullseye: Migration Guide

Quick Migration for v1.x Users

Option 1: Minimal Changes (Use SIMPLE mode)

import { EpicorRestService, EpicorResponseType } from 'epicor-rest-node';

const service = new EpicorRestService();
service.ResponseType = EpicorResponseType.SIMPLE;  // Add this line
service.PreserveSession = true;  // Optional, defaults to true

// All your v1.x code works as-is
const result = await service.BoGet('Erp.BO.Customer', 'GetList');
if (EpicorError.isError(result)) {
  // Handle error
} else {
  // Use result
}

Option 2: Adopt FULL Mode (Recommended)

import { EpicorRestService, EpicorResponseType } from 'epicor-rest-node';

const service = new EpicorRestService();
service.ResponseType = EpicorResponseType.FULL;  // Default, optional

// Update error handling to try/catch
try {
  const response = await service.BoGet('Erp.BO.Customer', 'GetList');
  console.log(response.data);  // Access via .data
} catch (error) {
  if (EpicorError.isError(error)) {
    console.log(`Error: ${error.message}`);
  }
}

Updating Session Creation

Before (v1.x):

const created = await service.Createsession();
if (created) {
  // Work with session
  await service.DestroySession();
}

After (v2.0.0):

const session = await service.CreateSession();
if (session) {
  console.log('Session:', session.SessionId);
  // Work with session
  await service.DestroySession();
}

// Or continue using deprecated method:
const created = await service.Createsession();  // Still works

Handling Response Headers

Before (v1.x):

const capturedHeaders = new CapturedResponseHeaders();
const result = await service.BoGet('Erp.BO.Customer', 'GetList', 
  params, null, null, capturedHeaders);
  
const contentType = capturedHeaders.getHeader('content-type');

After (v2.0.0 FULL mode):

// Headers included in response
const response = await service.BoGet('Erp.BO.Customer', 'GetList', params);
const contentType = response.headers['content-type'];

// Or still use CapturedResponseHeaders if needed
const capturedHeaders = new CapturedResponseHeaders();
const response = await service.BoGet('Erp.BO.Customer', 'GetList',
  params, null, null, capturedHeaders);

:package: Backward Compatibility

v2.0.0 maintains backward compatibility through SIMPLE mode:

:white_check_mark: Fully Compatible:

  • All v1.x code works with ResponseType = EpicorResponseType.SIMPLE
  • Createsession() (lowercase) still available (deprecated)
  • Error handling patterns unchanged in SIMPLE mode
  • All existing method signatures supported

:warning: Deprecated:

  • Createsession() - Use CreateSession() instead
  • Checking result === true for session creation - Use object check

:new_button: New Required Imports (if using FULL mode):

import { EpicorResponse } from 'epicor-rest-node';
import { EpicorResponseType } from 'epicor-rest-node/dist/models/EpicorType';

:tada: Why Upgrade?

  1. Better Error Handling: Choose between returned or thrown errors based on your architecture
  2. Rich Response Data: Access status codes, messages, and headers without extra parameters
  3. Improved Session Management: Get session details, manage context easily
  4. Type Safety: EpicorResponse<T> provides better TypeScript support
  5. Modern Patterns: FULL mode uses try/catch (standard JavaScript error handling)
  6. Flexible Sessions: Control session lifecycle with PreserveSession
  7. Development Tools: EFX staging retry for easier function development
  8. Future Ready: Built for upcoming features and improvements

:memo: Testing

Comprehensive test suite included (32+ tests):

# Copy environment template
cp .env.example .env

# Configure your Epicor connection in .env
# EPICOR_HOST=your-server.com
# EPICOR_USERNAME=your-username
# etc.

# Run tests
npm test

Tests cover:

  • :white_check_mark: Response type modes (SIMPLE and FULL)
  • :white_check_mark: Error handling (returned and thrown)
  • :white_check_mark: Session management (CreateSession, SetEmployee, SetPlant, etc.)
  • :white_check_mark: PreserveSession behavior
  • :white_check_mark: CallSettings methods
  • :white_check_mark: BO, BAQ, and EFX operations
  • :white_check_mark: Type guards and factory methods
5 Likes