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

@tonytang99/integration-canonical

v1.2.0

Published

Canonical data models for integration platform

Readme

@tonytang99/integration-canonical

Canonical data models for the integration platform. These schemas provide a standardized intermediate format for data flowing between different e-commerce and ERP systems.

Installation

npm install @tonytang99/integration-canonical

Philosophy

The canonical schema is the common language spoken between all systems. Instead of building direct integrations between every pair of systems (N×N complexity), we transform data to/from canonical format (2N complexity).

BigCommerce → Canonical → MYOB
Adobe Commerce → Canonical → NetSuite
Shopify → Canonical → Xero

Common Types

Money

Money is represented in minor units (cents) to avoid floating-point precision issues.

import { MoneySchema, MoneyHelper, type Money } from '@tonytang99/integration-canonical';

// Create money from dollars
const price = MoneyHelper.fromMajor(19.99, 'USD');
// { amount: 1999, currency: 'USD' }

// Convert to dollars
MoneyHelper.toMajor(price); // 19.99

// Format for display
MoneyHelper.format(price, 'en-US'); // "$19.99"

// Math operations
const total = MoneyHelper.add(
  { amount: 1999, currency: 'USD' },
  { amount: 500, currency: 'USD' }
); // { amount: 2499, currency: 'USD' }

const discounted = MoneyHelper.multiply(
  { amount: 1999, currency: 'USD' },
  0.9
); // 10% off

Why Minor Units?

// ❌ BAD: Floating point errors
0.1 + 0.2 // 0.30000000000000004

// ✅ GOOD: Integer math
10 + 20 // 30 (representing $0.30)

Address

Comprehensive address schema supporting international addresses.

import { AddressSchema, AddressHelper, type Address } from '@tonytang99/integration-canonical';

const address: Address = {
  street1: '123 Main Street',
  street2: 'Suite 100',
  city: 'San Francisco',
  state: 'California',
  stateCode: 'CA',
  postalCode: '94102',
  country: 'United States',
  countryCode: 'US',
  
  // Optional contact info
  firstName: 'John',
  lastName: 'Doe',
  company: 'Acme Corp',
  phone: '555-1234',
  email: '[email protected]',
  
  // Metadata
  type: 'billing',
  isDefault: true,
};

// Format address
AddressHelper.format(address);
// Output:
// Acme Corp
// John Doe
// 123 Main Street
// Suite 100
// San Francisco, CA, 94102
// United States

// Single line
AddressHelper.formatSingleLine(address);
// "Acme Corp, John Doe, 123 Main Street, Suite 100, San Francisco, CA, 94102, United States"

// Validate postal code
AddressHelper.isValidPostalCode('94102', 'US'); // true
AddressHelper.isValidPostalCode('941', 'US'); // false

Phone Number

import { PhoneNumberSchema, PhoneNumberHelper } from '@tonytang99/integration-canonical';

const phone = {
  number: '5551234567',
  countryCode: '+1',
  extension: '123',
  type: 'mobile',
};

PhoneNumberHelper.format(phone); // "+1 5551234567 ext. 123"

Product Schema

Normalized product representation supporting:

  • Simple products
  • Products with variants (size, color, etc.)
  • Pricing with compare-at prices (sales)
  • Inventory tracking
  • Images and media
  • Physical properties (weight, dimensions)
  • SEO metadata
import { 
  CanonicalProductSchema, 
  ProductHelper,
  MoneyHelper,
  type CanonicalProduct 
} from '@tonytang99/integration-canonical';

const product: CanonicalProduct = {
  id: 'prod_123',
  sku: 'WGT-ABC-001',
  name: 'Premium Widget',
  description: 'A high-quality widget for all your needs',
  
  price: MoneyHelper.fromMajor(29.99, 'USD'),
  compareAtPrice: MoneyHelper.fromMajor(39.99, 'USD'), // "Was $39.99"
  
  inventory: {
    quantity: 100,
    tracked: true,
    allowBackorder: false,
    lowStockLevel: 10,
  },
  
  weight: {
    value: 2.5,
    unit: 'kg',
  },
  
  dimensions: {
    length: 30,
    width: 20,
    height: 10,
    unit: 'cm',
  },
  
  categories: ['widgets', 'premium'],
  brand: 'Acme',
  
  images: [
    {
      url: 'https://cdn.example.com/widget-1.jpg',
      altText: 'Premium Widget Front View',
      sortOrder: 1,
      isThumbnail: true,
    },
  ],
  
  isVisible: true,
  availability: 'available',
  condition: 'new',
  
  metadata: {
    source: 'bigcommerce',
    sourceId: '12345',
    createdAt: '2024-01-01T00:00:00Z',
    updatedAt: '2024-01-30T10:00:00Z',
  },
};

// Validate
CanonicalProductSchema.parse(product); // throws if invalid

// Helper functions
ProductHelper.isInStock(product); // true
ProductHelper.isLowStock(product); // false (100 > 10)

const displayPrice = ProductHelper.getDisplayPrice(product);
// {
//   price: { amount: 2999, currency: 'USD' },
//   isOnSale: true,
//   discount: { amount: 1000, currency: 'USD' }
// }

const thumbnail = ProductHelper.getThumbnail(product);
// { url: '...', altText: '...', isThumbnail: true }

const weightInKg = ProductHelper.getWeightInKg(product); // 2.5

Product Variants

For products with options (size, color, etc.):

const tshirt: CanonicalProduct = {
  id: 'prod_tshirt',
  sku: 'TSHIRT-BASE',
  name: 'Premium T-Shirt',
  price: MoneyHelper.fromMajor(24.99, 'USD'),
  
  hasVariants: true,
  variants: [
    {
      id: 'var_1',
      sku: 'TSHIRT-RED-M',
      options: {
        color: 'Red',
        size: 'Medium',
      },
      inventory: {
        quantity: 50,
        tracked: true,
      },
    },
    {
      id: 'var_2',
      sku: 'TSHIRT-BLU-L',
      options: {
        color: 'Blue',
        size: 'Large',
      },
      inventory: {
        quantity: 30,
        tracked: true,
      },
    },
  ],
  
  inventory: {
    quantity: 80, // Total across all variants
    tracked: true,
  },
  
  metadata: {
    source: 'bigcommerce',
    sourceId: '67890',
  },
};

// Get specific variant
const variant = ProductHelper.getVariantBySku(tshirt, 'TSHIRT-RED-M');

// Get total inventory
const total = ProductHelper.getTotalInventory(tshirt); // 80

Design Principles

1. Explicit Over Implicit

All fields are explicitly typed. No any types unless absolutely necessary.

// ✅ Good
price: MoneySchema

// ❌ Bad
price: z.any()

2. Required vs Optional

Core fields are required. System-specific fields are optional.

{
  sku: z.string(),              // Required - every product has a SKU
  name: z.string(),             // Required - every product has a name
  brand: z.string().optional(), // Optional - not all systems track brand
}

3. Metadata for System-Specific Data

Use metadata and customFields for data that doesn't fit the core schema.

{
  metadata: {
    source: 'bigcommerce',
    sourceId: '12345',
    externalIds: {
      myob: 'STOCK-ABC',
      netsuite: 'ITEM-999',
    },
  },
  customFields: {
    bigcommerce_product_type: 'physical',
    myob_default_location: 'WAREHOUSE-A',
  },
}

4. Validation at Boundaries

Validate when data enters or leaves the canonical format.

// ✅ Validate when transforming TO canonical
const canonical = CanonicalProductSchema.parse(transformed);

// ✅ Validate when transforming FROM canonical
const myobData = MyobStockItemSchema.parse(transformed);

Common Patterns

Transforming TO Canonical

import { CanonicalProductSchema, MoneyHelper } from '@tonytang99/integration-canonical';

function bigCommerceToCanonical(bcProduct: any): CanonicalProduct {
  const canonical = {
    id: String(bcProduct.id),
    sku: bcProduct.sku,
    name: bcProduct.name,
    price: MoneyHelper.fromMajor(bcProduct.price, 'USD'),
    inventory: {
      quantity: bcProduct.inventory_level,
      tracked: bcProduct.inventory_tracking !== 'none',
    },
    metadata: {
      source: 'bigcommerce',
      sourceId: String(bcProduct.id),
    },
  };
  
  // Validate before returning
  return CanonicalProductSchema.parse(canonical);
}

Transforming FROM Canonical

function canonicalToMyob(product: CanonicalProduct) {
  return {
    InventoryID: product.sku,
    Description: product.name,
    BasePrice: MoneyHelper.toMajor(product.price),
    QuantityOnHand: product.inventory.quantity,
    // ... more MYOB fields
  };
}

Testing

import { CanonicalProductSchema, MoneyHelper } from '@tonytang99/integration-canonical';

describe('Product transformation', () => {
  it('should transform BC product to canonical', () => {
    const bcProduct = {
      id: 123,
      sku: 'ABC',
      name: 'Widget',
      price: 19.99,
      inventory_level: 50,
    };
    
    const canonical = {
      id: '123',
      sku: 'ABC',
      name: 'Widget',
      price: MoneyHelper.fromMajor(19.99, 'USD'),
      inventory: {
        quantity: 50,
        tracked: true,
      },
      metadata: {
        source: 'bigcommerce',
        sourceId: '123',
      },
    };
    
    // This will throw if schema is invalid
    expect(() => CanonicalProductSchema.parse(canonical)).not.toThrow();
  });
});

Future Schemas

Coming soon:

  • CanonicalOrder - Orders and line items
  • CanonicalCustomer - Customer data
  • CanonicalInventory - Inventory movements
  • CanonicalCategory - Product categories
  • CanonicalPayment - Payment transactions
  • CanonicalShipment - Shipping information

License

MIT