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

@machinemetrics/mm-erp-sdk

v0.9.8

Published

A library for syncing data between MachineMetrics and ERP systems

Readme

MM ERP Connector SDK

A TypeScript SDK for building ERP connectors that integrate with MachineMetrics' data synchronization platform.

Overview

This SDK provides the core infrastructure for:

  • Data Sync Service: Automated job scheduling for bidirectional ERP data synchronization
  • ERP Connector Interface: Standardized interface for implementing ERP connectors
  • MM API Integration: Client for interacting with MachineMetrics APIs
  • Timezone handling: Company timezone cached at startup; conversion helpers for the ERP boundary (see Timezone handling)
  • Utility Functions: Common data transformation and HTTP utilities

Documentation

  • Building a new connector? Start with the MachineMetrics ERP connector guide on the developer hub — it orients you across scaffolding, implementation, and deployment.
  • SDK API detail (this repo): CONNECTOR_SDK_UTILITIES.md for the SDK classes/services/utilities, and MM_ENTITY_KEYS_REFERENCE.md for entity keys and referential integrity.
  • Connector implementation workflow: the generic-erp-connector template's CONNECTOR-GUIDE.md (how a generated project wires the SDK together).

Quick Start

1. Install the SDK

npm install @machinemetrics/mm-erp-sdk

2. Implement an ERP Connector

import path from "path";
import { fileURLToPath } from "url";
import {
  IERPConnector,
  IERPLaborTicketHandler,
  ERPObjType,
  MMReceiveLaborTicket,
  RecoverLaborTicketIdAction,
  RecoverLaborTicketIdReturn,
  ApplicationInitializer,
  runDataSyncService,
} from "@machinemetrics/mm-erp-sdk";

export default class MyERPConnector
  implements IERPConnector, IERPLaborTicketHandler
{
  get type(): string {
    return "JOB_BOSS"; // replace with your ERP type
  }

  async startUp(): Promise<void> {
    try {
      await ApplicationInitializer.initialize();
      // Bree workers re-import the built connector module from disk each cycle,
      // so runDataSyncService needs the path to this connector's compiled JS.
      const connectorPath = path.resolve(
        path.dirname(fileURLToPath(import.meta.url)),
        "my-erp-connector.js"
      );
      await runDataSyncService(connectorPath);
    } catch (error) {
      console.error("Startup failed:", error);
      process.exitCode = 1;
    }
  }

  async syncFromERP(): Promise<void> {
    // Implement your data sync from ERP logic
  }

  async syncToERP(): Promise<void> {
    // Implement your data sync to ERP logic
  }

  async createLaborTicketInERP(
    laborTicket: MMReceiveLaborTicket
  ): Promise<{ laborTicket: MMReceiveLaborTicket; erpUid: string }> {
    // Implement labor ticket creation
  }

  // Optional: recover an existing ERP laborTicketId before create when MM lost the link
  async recoverLaborTicketId(
    laborTicket: MMReceiveLaborTicket
  ): Promise<RecoverLaborTicketIdReturn> {
    return { action: RecoverLaborTicketIdAction.CREATE };
  }

  async updateLaborTicketInERP(
    laborTicket: MMReceiveLaborTicket
  ): Promise<MMReceiveLaborTicket> {
    // Implement labor ticket updates
  }

  // Implement other required methods...
}

3. Start the Data Sync Service

import MyERPConnector from "./my-erp-connector";

const connector = new MyERPConnector();
await connector.startUp();

runDataSyncService(connectorPath) requires a path to the connector's compiled JS module (not the .ts source). See the template CONNECTOR-GUIDE.md for the build/runtime path resolution details.

Data Sync Jobs

The SDK includes automated jobs that run on configurable intervals:

  • from-erp: Syncs data from ERP to MachineMetrics
  • to-erp: Syncs labor tickets and employee timesheets from MachineMetrics to ERP
  • retry-failed-labor-tickets: Retries failed labor ticket operations
  • clean-up-expired-cache: Maintains cache hygiene

Configure job intervals via environment variables (Bree-compatible strings, e.g. "5 min", "1h"):

FROM_ERP_INTERVAL="5 min"
TO_ERP_INTERVAL="5 min"
RETRY_LABOR_TICKETS_INTERVAL="30 min"
CACHE_EXPIRATION_CHECK_INTERVAL="60 min"

Execution model

Jobs are scheduled with Bree. Each job cycle runs as an independent worker process that constructs a fresh connector instance — so in-memory state does not persist across cycles, and cycles are not guaranteed to run serially (labor tickets may be processed concurrently within a cycle). Persist durable state in the SDK's SQLite stores or MM checkpoints, not in instance/module memory. See CONNECTOR_SDK_UTILITIES.md (runDataSyncService) for the full contract.

Labor Ticket Synchronization

The SDK provides bidirectional labor ticket synchronization with built-in protection against circular writes (ERP → MM → ERP):

  • Labor sync ledger (SQLite-backed): records a per-ticket business signature so the to-erp export skips tickets whose business-relevant content is unchanged since they were last processed. Tickets imported from the ERP before MM assigns a laborTicketRef are tracked under a stable erp-import:{workOrderId}:{laborTicketId} key.
  • ERP labor ticket ID recovery (optional): connectors may implement recoverLaborTicketId on IERPLaborTicketHandler to look up an existing ERP row before create when MM has no laborTicketId. When recovery returns an id (RECOVER), polling and NATS patch MM then call updateLaborTicketInERP. When recovery returns DEFER, the SDK skips create for that cycle and retries later. Duplicate prevention depends on the connector implementing recovery correctly; connectors without the hook are unchanged.
  • Partial ERP create (optional): when createLaborTicketInERP fails but an ERP id was assigned, throw LaborTicketCreatePartialSuccessError so the SDK patches MM before rethrowing. See CONNECTOR_SDK_UTILITIES.md.
  • ERP → MM import filtering: StandardProcessDrivers.writeLaborTicketsToMmFromErpImport() applies MES/operator allowlists and FK rules, pre-marks accepted tickets in the ledger, writes them to MM, and advances the labor-tickets export checkpoint.
  • Hashed batch cache: BatchCacheManager / StandardProcessDrivers.writeEntitiesToMM() deduplicate repeated upserts within a batch (local + API-side).
  • Signature semantics: the business signature excludes identity fields (laborTicketRef, ERP-import keys) and SDK-mutated fields, so the same business state produces a stable hash across import and the next export. A dashboard edit changes the signature, so the change is exported.

See CONNECTOR_SDK_UTILITIES.md for the helper APIs, and the template CONNECTOR-GUIDE.md for the connector workflow.

Persistent Storage

The SDK persists durable state — the labor sync ledger, the dedup/batch cache, and SDK runtime state — in a SQLite database at the path given by SQLITE_DB_PATH (default ./local.sqlite3):

SQLITE_DB_PATH=/app/data/local.sqlite3

This file must survive process/container restarts for the sync ledger and dedup cache to remain correct across cycles. How that path is made durable (e.g. a mounted volume or backed-up disk) is a deployment concern owned by your connector's deployment setup, not by the SDK.

Timesheet Export

The SDK provides employee clock in/out export from MM to the ERP, separate from shop-floor labor tickets:

  • IERPTimesheetHandler — connector-implemented hook that receives MMReceiveTimesheet rows.
  • StandardProcessDrivers.syncTimesheetsToERP() — fetches pending rows from /erp/v1/timesheets/updates, processes them in timesheetRef order, and checkpoints progress. It assumes labor ticket export has already run for the cycle, so shop-floor labor is written/closed before clock-out.

Timezone handling

MachineMetrics exchanges datetimes as ISO-8601 strings in UTC (trailing Z or an explicit +00:00 offset). The ERP boundary is where timezone conversion happens.

Cached timezone (no connector fetch)

ApplicationInitializer.initialize() fetches company info from MM (/accounts/current) and persists it in SQLite runtime state, including the company's IANA timezone name. After initialization succeeds, connector code reads it synchronously — no extra HTTP, no ERP-specific timezone configuration:

  • getCachedTimezoneName() / getERPTimezone() — IANA name (e.g. America/New_York)
  • getCachedTimezoneOffset() — current offset in hours for that zone (DST-aware)

Connectors do not fetch or configure a timezone. Call ApplicationInitializer.initialize() once at startup; the conversion helpers use the cached zone by default.

MM company timezone vs ERP server timezone

The cached value is the MM company's timezone (company.timezone from /accounts/current). The SDK's default assumption is that ERP-local wall time matches the MM company timezone. If your ERP server's clock uses a different zone, pass an explicit timezone? argument to the conversion helpers.

Do not treat "ERP timezone" and "company timezone" as separate inputs the connector must obtain — the SDK already provides the company zone unless you need to override it.

When to convert (depends on sync direction)

| Path | SDK converts? | Connector responsibility | |------|---------------|--------------------------| | MM → ERP labor tickets (syncLaborTicketsToERP) | Yes — UTC → ERP-local before your handler runs | Treat clockIn, clockOut, etc. as localized wall time. Do not apply further timezone shifts. | | MM → ERP timesheets (syncTimesheetsToERP) | No | If the ERP expects local datetimes, call convertUtcDateTimeToErpLocal() in your handler. | | ERP → MM (writeEntitiesToMM, writeLaborTicketsToMmFromErpImport) | No | Convert ERP-local datetimes to UTC with convertErpLocalDateTimeToUtc() before passing records to the SDK. |

Conversion is field-specific: apply it only where the ERP field semantics are local wall time. If an ERP field is already stored in UTC, converting would corrupt the value.

Conversion helpers

All are exported from the package root. Inputs and outputs are ISO-8601 strings; invalid input throws.

import {
  getERPTimezone,
  convertUtcDateTimeToErpLocal,
  convertErpLocalDateTimeToUtc,
} from "@machinemetrics/mm-erp-sdk";

// After ApplicationInitializer.initialize():
const zone = getERPTimezone(); // cached MM company IANA name

// MM UTC → ERP-local (e.g. timesheets, or custom MM→ERP paths)
const localIso = convertUtcDateTimeToErpLocal("2024-06-15T18:30:00.000Z");

// ERP-local → MM UTC (required before writeEntitiesToMM)
const utcIso = convertErpLocalDateTimeToUtc("2024-06-15T14:30:00");

// Override when ERP clock differs from MM company timezone:
convertErpLocalDateTimeToUtc(localIso, "Europe/Amsterdam");

Offset-based formatting helpers (formatDateWithTZOffset, toISOWithOffset, convertToLocalTime, applyTimezoneOffsetsToFields) remain available for legacy ERP formats; prefer the Luxon-based convert* helpers for new code.

See CONNECTOR_SDK_UTILITIES.md for full helper detail.

ERP Resource Mapping (MES Machine Settings)

Machine-to-ERP-resource mapping is sourced from MES machine settings (GET /mes/settings/machine-settings):

  • MMApiClient.fetchMappedErpResourcesForCompany() — machines mapped to ERP labor resources (machineRef + laborResourceId), sourced from MES machine settings.
  • MMApiClient.fetchMesSchedulableErpResourceIds() / getMesLaborImportResourceIds() — schedulable and labor-import resource-id allowlists.

See CONNECTOR_SDK_UTILITIES.md for MES machine-settings helpers and MM_ENTITY_KEYS_REFERENCE.md for entity keys.

Environment Configuration

These are the environment variables the SDK reads (via CoreConfiguration, getSQLServerConfiguration, getErpApiConnectionParams, and the SQLite/knex setup). Names and defaults below match the SDK source; defaults are shown where one exists.

Required

# MM REST API base URL (e.g. https://api.machinemetrics.com). The SDK throws on startup if this is unset.
MM_MAPPING_AUTH_SERVICE_URL="https://api.machinemetrics.com"

# MM company auth token. ApplicationInitializer.initialize() fetches /accounts/current with this
# bearer token. If unset, startup retries every 10s (up to 36000 attempts) and initialization
# never completes — set this before calling initialize().
MM_MAPPING_SERVICE_TOKEN="your-company-auth-token"

# MM ERP mapping service REST API URL (e.g. https://erp-api.svc.machinemetrics.com). Required for
# entity sync, labor-ticket export, checkpoints, and other /erp/v1/* calls via MMApiClient.
MM_MAPPING_SERVICE_URL="https://erp-api.svc.machinemetrics.com"

MachineMetrics API

MM_API_TIMEOUT_SEC="30"     # MM API request timeout, seconds (default: 30)
MM_API_RETRY_ATTEMPTS="0"   # MM API retry attempts (default: 0)

General

NODE_ENV="production"      # Runtime environment (default: "development")
LOG_LEVEL="info"           # Log level (default: "info")
SDK_LOG_PREFIX="SDK:"      # Prefix applied to SDK log lines (default: "SDK:")
NATS_ENABLED="false"       # When "true", starts the real-time NATS labor-ticket listener alongside Bree jobs

Job intervals

Interval strings accept any Bree-compatible duration (e.g. "5 min", "1h").

FROM_ERP_INTERVAL="5 min"                  # from-ERP sync interval (default: "5 min", or POLL_INTERVAL if set)
TO_ERP_INTERVAL="5 min"                    # to-ERP sync interval (default: "5 min")
RETRY_LABOR_TICKETS_INTERVAL="30 min"      # retry-failed-labor-tickets interval (default: "30 min")
CACHE_EXPIRATION_CHECK_INTERVAL="60 min"   # expired-cache cleanup interval (default: "60 min")
POLL_INTERVAL="5 min"                       # fallback default for FROM_ERP_INTERVAL (default: "")

Caching & local storage

CACHE_TTL="604800"                  # Dedup cache TTL, seconds (default: 604800 = 7 days)
SQLITE_DB_PATH="/app/data/local.sqlite3"  # SQLite file for ledger/cache/runtime state (default: "./local.sqlite3")

ERP API (generic REST/GraphQL)

ERP_PAGINATION_LIMIT="0"     # Default pagination limit for ERP API (default: 0)
ERP_API_TIMEOUT_SEC="30"     # ERP API request timeout, seconds (default: 30)
ERP_API_RETRY_ATTEMPTS="3"   # ERP API retry attempts (default: 3)

Connectors using getErpApiConnectionParams() also read:

ERP_API_URL="https://erp.example.com"
ERP_API_CLIENT_ID="client-id"
ERP_API_CLIENT_SECRET="client-secret"
ERP_API_ORGANIZATION_ID="org-id"
ERP_AUTH_BASE_URL="https://auth.example.com"

SQL Server ERP (when using SqlServerService / getSQLServerConfiguration())

ERP_SQLSERVER_USERNAME="username"
ERP_SQLSERVER_PASSWORD="password"
ERP_SQLSERVER_DATABASE="your-db"
ERP_SQLSERVER_HOST="localhost"                # alias: ERP_SQLSERVER_SERVER
ERP_SQLSERVER_PORT="1433"                     # default: 1433
ERP_SQLSERVER_CONNECTION_TIMEOUT="30000"      # default: 30000 (ms)
ERP_SQLSERVER_REQUEST_TIMEOUT="60000"         # default: 60000 (ms)
ERP_SQLSERVER_MAX="10"                        # connection pool max (default: 10)
ERP_SQLSERVER_MIN="0"                         # connection pool min (default: 0)
ERP_SQLSERVER_IDLE_TIMEOUT_MMILLIS="30000"    # pool idle timeout, ms (env name spelled as in the SDK; default: 30000)
ERP_SQLSERVER_ENCRYPT="false"                 # default: false
ERP_SQLSERVER_TRUST_SERVER="false"            # default: false

Logging Reliability

  • The SDK logger now captures rotate/write transport failures internally, but callers should still treat logging as best-effort. If you manage your own in-process scheduler (e.g., not using Bree), wrap any job.isRunning = true flags and subsequent logger.* calls in a try/finally block so the scheduler state clears even if logging throws.
  • Bree-based connectors inherit process isolation, but custom schedulers run inside a single event loop. Always reset locks/timers inside finally clauses to avoid getting stuck when a synchronous dependency (logging, metrics, etc.) fails mid-cycle.

API Reference

Core Interfaces

  • IERPConnector: Main connector interface (syncFromERP, syncToERP, lifecycle hooks). The type getter is the ERP system identifier passed to MM API system parameters and StandardProcessDrivers as connectorType.
  • IERPLaborTicketHandler: Create/update labor tickets in the ERP (MMReceiveLaborTicket). Optional recoverLaborTicketId (RecoverLaborTicketIdAction) when MM lost the ERP link — see Labor Ticket Synchronization and CONNECTOR_SDK_UTILITIES.md.
  • IERPTimesheetHandler: Process employee clock in/out rows in the ERP (MMReceiveTimesheet)

Utilities

  • ApplicationInitializer: SDK initialization (configuration, migrations, company info including timezone)
  • runDataSyncService: Start automated sync jobs
  • getCachedCompanyInfo() / getCachedCompanyId() / getCachedCompanyLocationRef() / getCachedTimezoneName(): read company info persisted at startup
  • getERPTimezone() / convertUtcDateTimeToErpLocal() / convertErpLocalDateTimeToUtc(): datetime boundary conversion (see Timezone handling)
  • formatDateWithTZOffset() / toISOWithOffset() / convertToLocalTime() / applyTimezoneOffsetsToFields(): offset-based formatting for legacy ERP formats
  • StandardProcessDrivers: Common sync operations, including:
    • syncLaborTicketsToERP(connectorType, handler, { onlyClosed? })
    • syncTimesheetsToERP(connectorType, handler)
    • retryFailedLaborTickets(connectorType, handler)
    • writeLaborTicketsToMmFromErpImport(tickets, filterOptions, cache?, writeOptions?)
    • writeEntitiesToMM(entityType, records, cache?, options?)

Services

  • SqlServerService / SqlServerHelper: SQL Server ERP integration
  • PsqlService: PSQL / Pervasive (ODBC) ERP integration, with formatPsqlDate, formatPsqlTime, combinePsqlDateTime, isPsqlDateEmpty, cleanPsqlCharField helpers
  • RestAPIService / GraphQLService: ERP REST / GraphQL API clients
  • MMApiClient: MachineMetrics API client (includes fetchMappedErpResourcesForCompany, fetchMesSchedulableErpResourceIds, fetchTimesheetUpdates)

For SDK helper, type, and entity-key detail, see CONNECTOR_SDK_UTILITIES.md and MM_ENTITY_KEYS_REFERENCE.md. For the connector implementation workflow, see the template CONNECTOR-GUIDE.md.

Labor ticket updates: the onlyClosed option

syncLaborTicketsToERP accepts an optional onlyClosed flag:

  • onlyClosed: true — the SDK requests only closed labor tickets, forwarding onlyClosed=true to /erp/v1/labor-tickets/export/updates.
  • omitted (default) — all labor tickets are processed.
StandardProcessDrivers.syncLaborTicketsToERP(connectorType, handler, { onlyClosed?: boolean });