Skip to content

TransactionAuthorizationProtocol/ivms101

Repository files navigation

IVMS101 TypeScript Library

A TypeScript library providing type definitions, validation, and conversion utilities for the IVMS101 (interVASP Messaging Standard). Defaults to IVMS101.2023 with full support for legacy 2020 format.

Features

  • 🎯 2023-first API - Modern, clean interface defaulting to the latest standard
  • Runtime validation - Comprehensive Zod schemas with all 12 IVMS101 constraints
  • 🔄 Version conversion - Bidirectional, lossless conversion between 2020 and 2023
  • 🧪 Property-based testing - Fast-check arbitraries for generating compliant test data
  • 📦 Dual format - CommonJS and ESM builds with full TypeScript support
  • 🏗️ Clean architecture - Independent type definitions with no circular dependencies

Installation

npm install ivms101

Upgrading from v1.x to v2.0

Version 2.0 introduces a 2023-first API with breaking changes. Here's how to upgrade:

Breaking Changes

  1. Main export now defaults to IVMS101.2023

    • IVMS101 type is now IVMS101_2023.IVMS101 (not a union)
    • All type exports from main package are 2023 types
  2. 2020 support moved to legacy submodule

    • Import 2020 types from 'ivms101/legacy' instead of main package
    • Conversion functions moved to legacy module
  3. New validation API

    • New validate() function replaces validateIVMS101()
    • Defaults to 2023 validation

Migration Guide

If you're using both 2020 and 2023 (union types)

Before (v1.x):

import { IVMS101, IVMS101_2020, IVMS101_2023 } from 'ivms101';

// IVMS101 was a union type
function process(data: IVMS101) {
  // data could be either version
}

After (v2.0):

import { IVMS101 } from 'ivms101'; // This is now 2023 only
import { IVMS101_2020, IVMS101Schema } from 'ivms101/legacy';

// For union type, import from legacy
import type { IVMS101Type } from 'ivms101/legacy';

function process(data: IVMS101Type) {
  // data can be either version (union)
}

// OR: Keep separate functions for each version
function process2023(data: IVMS101) { /* ... */ }
function process2020(data: IVMS101_2020.IVMS101) { /* ... */ }

If you're using 2020 types

Before (v1.x):

import { IVMS101_2020 } from 'ivms101';

const data: IVMS101_2020.IVMS101 = { /* ... */ };

After (v2.0):

import { IVMS101_2020 } from 'ivms101/legacy';

const data: IVMS101_2020.IVMS101 = { /* ... */ };

If you're using conversion functions

Before (v1.x):

import { convertTo2023, convertFrom2023 } from 'ivms101';

After (v2.0):

import { convertTo2023, convertFrom2023 } from 'ivms101/legacy';

If you're using validation

Before (v1.x):

import { validateIVMS101, isValidIVMS101 } from 'ivms101';

const validated = validateIVMS101(data); // accepts either version

After (v2.0):

// Option 1: Use new validate() function (defaults to 2023)
import { validate } from 'ivms101';
const validated = validate(data); // validates as 2023
const validated2020 = validate(data, { version: '2020' });

// Option 2: Use legacy validateIVMS101 for union validation
import { validateIVMS101, isValidIVMS101 } from 'ivms101/legacy';
const validated = validateIVMS101(data); // accepts either version

If you're using type guards

Before (v1.x):

import { isValidIVMS101_2023 } from 'ivms101';

After (v2.0):

// 2023 type guard in main package
import { isValidIVMS101_2023 } from 'ivms101';

// 2020 type guard in legacy package
import { isValidIVMS101_2020 } from 'ivms101/legacy';

Quick Migration Checklist

  • Replace import { IVMS101_2020, ... } from 'ivms101' with import { IVMS101_2020, ... } from 'ivms101/legacy'
  • Replace import { convertTo2023, convertFrom2023 } from 'ivms101' with import { convertTo2023, convertFrom2023 } from 'ivms101/legacy'
  • If using union types, import IVMS101Type from 'ivms101/legacy' instead of using IVMS101
  • Update validation calls to use new validate() function or import legacy validators
  • Update type annotations: IVMS101 is now 2023-only (not a union)

What Stays the Same

✅ All 2023 types work exactly as before (just imported from main package) ✅ All conversion logic is unchanged (just moved to legacy module) ✅ All validation schemas work the same way ✅ ensureVersion() and ivms101_version() still in main package ✅ Arbitraries still work the same way ✅ All tests pass without changes

Recommended Approach

For new code, use the 2023-first API:

import { validate, type IVMS101 } from 'ivms101';

For existing code that needs 2020 support, add legacy import:

import { validate, type IVMS101 } from 'ivms101';
import { IVMS101_2020, convertTo2023 } from 'ivms101/legacy';

Quick Start

Working with IVMS101.2023 (Default)

import { validate, type IVMS101, type NaturalPerson } from 'ivms101';

// The IVMS101 type is IVMS101.2023 by default
const data: IVMS101 = {
  originator: {
    originatorPerson: [{ /* ... */ }]  // Note: singular "Person" in 2023
  },
  beneficiary: {
    beneficiaryPerson: [{ /* ... */ }]
  },
  payloadMetadata: {
    payloadVersion: "101.2023"  // Required in 2023
  }
};

// Validate (defaults to 2023)
const validated = validate(data);

// Or explicitly specify version
const validated2023 = validate(data, { version: '2023' });

Working with Legacy IVMS101 2020

import { IVMS101_2020 } from 'ivms101/legacy';
import { validate } from 'ivms101';

const legacy: IVMS101_2020.IVMS101 = {
  originator: {
    originatorPersons: [{ /* ... */ }]  // Note: plural "Persons" in 2020
  },
  beneficiary: {
    beneficiaryPersons: [{ /* ... */ }]
  }
};

// Validate as 2020 format
const validated = validate(legacy, { version: '2020' });

Converting Between Versions

import { ensureVersion, ivms101_version, PayloadVersionCode } from 'ivms101';
import { convertTo2023, convertFrom2023 } from 'ivms101/legacy';

// Auto-detect version
const version = ivms101_version(data);

// Convert to 2023 (default)
const data2023 = ensureVersion(data);

// Convert to specific version
const data2020 = ensureVersion(PayloadVersionCode.V2020, data);

// Explicit conversions
const converted2023 = convertTo2023(data2020);
const converted2020 = convertFrom2023(data2023);

API Reference

Main Exports (2023-first)

import {
  // Types (all are IVMS101.2023)
  type IVMS101,              // Main IVMS101 type
  type NaturalPerson,        // Natural person type
  type LegalPerson,          // Legal person type
  type Person,               // Person (natural or legal)
  type Originator,           // Originator type
  type Beneficiary,          // Beneficiary type

  // Enums
  PayloadVersionCode,        // Version codes ("101" | "101.2023")

  // Validation
  validate,                  // validate(data, { version?: '2020' | '2023' })
  IVMS101_2023Schema,        // Zod schema for 2023
  isValidIVMS101_2023,       // Type guard for 2023

  // Utilities
  ensureVersion,             // Convert to specific version (defaults to 2023)
  ivms101_version,           // Detect version

  // Testing
  arbitraries,               // Fast-check arbitraries

  // Shared core types (also exported directly)
  type CountryCode,          // ISO country codes
  type AddressTypeCode,      // Address type codes
  type NaturalPersonNameTypeCode,  // Natural person name type codes
  type LegalPersonNameTypeCode,    // Legal person name type codes
  type Address,              // Address structure
  type NationalIdentification,     // National ID structure
  // ... and other core types
} from 'ivms101';

Legacy Exports (2020 support)

import {
  // Types
  IVMS101_2020,              // Namespace with all 2020 types

  // Conversion
  convertTo2023,             // Convert 2020 → 2023
  convertFrom2023,           // Convert 2023 → 2020

  // Validation
  IVMS101_2020Schema,        // Zod schema for 2020
  isValidIVMS101_2020,       // Type guard for 2020
  IVMS101Schema,             // Union schema (2020 | 2023)
  validateIVMS101,           // Validate either version
  isValidIVMS101,            // Type guard for either version
} from 'ivms101/legacy';

Validation

Basic Validation

import { validate } from 'ivms101';

// Validate with default version (2023)
try {
  const validated = validate(unknownData);
  console.log('Valid IVMS101.2023 data:', validated);
} catch (error) {
  console.error('Validation failed:', error.message);
}

// Validate specific version
const validated2020 = validate(data, { version: '2020' });

Type Guards

import { isValidIVMS101_2023 } from 'ivms101';
import { isValidIVMS101_2020, isValidIVMS101 } from 'ivms101/legacy';

// Check if data is valid 2023 format
if (isValidIVMS101_2023(data)) {
  // TypeScript knows data is IVMS101_2023.IVMS101
  console.log(data.originator.originatorPerson);
}

// Check if data is valid 2020 format
if (isValidIVMS101_2020(data)) {
  // TypeScript knows data is IVMS101_2020.IVMS101
  console.log(data.originator.originatorPersons);
}

// Check if data is either version
if (isValidIVMS101(data)) {
  console.log('Valid IVMS101 data (either version)');
}

Schema Validation

import { IVMS101_2023Schema } from 'ivms101';
import { IVMS101_2020Schema, IVMS101Schema } from 'ivms101/legacy';

// Safe parsing (doesn't throw)
const result = IVMS101_2023Schema.safeParse(data);
if (result.success) {
  console.log('Valid data:', result.data);
} else {
  console.log('Validation errors:', result.error.issues);
}

// Parse (throws on invalid data)
const validated = IVMS101_2023Schema.parse(data);

// Union schema for either version
const validatedAny = IVMS101Schema.parse(data);

Version Differences

The 2023 version introduced several breaking changes from 2020:

Aspect IVMS101 2020 IVMS101.2023
Originator field originatorPersons (plural) originatorPerson (singular)
Beneficiary field beneficiaryPersons (plural) beneficiaryPerson (singular)
Customer ID (both person types) customerNumber customerIdentification
Natural person name field nameIdentifierType naturalPersonNameIdentifierType
Payload version field Optional Required (payloadMetadata.payloadVersion)

Migration Example

// 2020 format
const data2020 = {
  originator: {
    originatorPersons: [{
      naturalPerson: {
        customerNumber: "12345",
        name: {
          nameIdentifier: [{
            primaryIdentifier: "Doe",
            secondaryIdentifier: "John",
            nameIdentifierType: "LEGL"
          }]
        }
      }
    }]
  },
  // ... rest of structure
};

// 2023 format (after conversion)
const data2023 = {
  originator: {
    originatorPerson: [{
      naturalPerson: {
        customerIdentification: "12345",
        name: {
          nameIdentifier: [{
            primaryIdentifier: "Doe",
            secondaryIdentifier: "John",
            naturalPersonNameIdentifierType: "LEGL"
          }]
        }
      }
    }]
  },
  payloadMetadata: {
    payloadVersion: "101.2023"
  }
  // ... rest of structure
};

Property-Based Testing

The library includes comprehensive fast-check arbitraries for generating valid IVMS101 test data.

Installation

npm install --save-dev fast-check

Basic Usage

import { arbitraries } from 'ivms101';
import * as fc from 'fast-check';

// Generate sample data
const samples2023 = fc.sample(arbitraries.ivms101_2023Valid(), 5);
const samples2020 = fc.sample(arbitraries.ivms101_2020(), 5);
const personSamples = fc.sample(arbitraries.naturalPerson(), 10);

Property-Based Tests

import { arbitraries } from 'ivms101';
import { validate } from 'ivms101';
import { convertTo2023, convertFrom2023 } from 'ivms101/legacy';
import * as fc from 'fast-check';

describe('IVMS101 Properties', () => {
  it('all generated 2023 data should be valid', () => {
    fc.assert(fc.property(arbitraries.ivms101_2023Valid(), (data) => {
      expect(() => validate(data)).not.toThrow();
    }));
  });

  it('should preserve data through roundtrip conversion', () => {
    fc.assert(fc.property(arbitraries.ivms101_2020(), (original) => {
      const converted = convertTo2023(original);
      const backConverted = convertFrom2023(converted);

      expect(backConverted.originator.originatorPersons.length)
        .toBe(original.originator.originatorPersons.length);
    }));
  });
});

Available Arbitraries

Complete Structures

  • ivms101_2020() - Generate IVMS101 2020 objects
  • ivms101_2023() - Generate IVMS101 2023 objects
  • ivms101_2023Valid() - Generate 2023 with guaranteed version field
  • ivms101() - Generate either version randomly

Person Types

  • naturalPerson() - Natural person (2020 format)
  • legalPerson() - Legal person (2020 format)
  • person() - Either natural or legal (2020)
  • naturalPerson2023() - Natural person (2023 format)
  • legalPerson2023() - Legal person (2023 format)
  • person2023() - Either natural or legal (2023)

Components

  • naturalPersonNameId() - Name identifier for natural persons
  • legalPersonNameId() - Name identifier for legal persons
  • address() - Complete address objects
  • naturalPersonNationalIdentification() - National ID for natural persons
  • legalEntityNationalIdentification() - National ID for legal entities

Enums & Codes

  • naturalPersonNameTypeCode() - Name type codes
  • legalPersonNameTypeCode() - Legal person name codes
  • addressTypeCode() - Address type codes
  • countryCode() - ISO country codes
  • nationalIdentifierTypeCode() - National ID type codes
  • transliterationMethodCode() - Transliteration codes

Validation Constraints

The library implements all 12 IVMS101 specification constraints:

  • C1: OriginatorInformationNaturalPerson - Must have address, customer ID, national ID, or date of birth
  • C2: DateInPast - Birth date must be historic
  • C4: OriginatorInformationLegalPerson - Must have address, customer ID, or national ID
  • C5: LegalNamePresentLegalPerson - Must have at least one LEGL name type
  • C6: LegalNamePresentNaturalPerson - Must have at least one LEGL name type
  • C8: ValidAddress - Must have addressLine OR structured address
  • C9: CompleteNationalIdentifierLegalPerson - Complex LEI/registrationAuthority rules
  • C10: RegistrationAuthority format - Must match RA[0-9]{6}
  • C11: ValidLEI format - Must be 20-character alphanumeric
  • C12: sequentialIntegrity - Transfer path sequences must be sequential starting at 0

Array Size Limits

The library enforces these limits during validation:

Field Maximum Rationale
Originator/Beneficiary Persons 10 Joint accounts + margin
Account Numbers 20 Multiple corporate wallets
Name Identifiers (Natural) 5 Multiple name variants
Name Identifiers (Legal) 3 Aligns with type codes
Geographic Addresses 5 Multiple addresses
Transfer Path 5 Intermediary VASP chain
Transliteration Methods 5 Character set conversions

Architecture

The library follows a clean, layered architecture:

countries.ts ← core.ts ← ivms101_2023.ts (independent)
                      ← ivms101_2020.ts (independent)
                      ← validator.ts
                      ← converter.ts
                      ← arbitraries.ts
                      ← index.ts (2023 exports)
                      ← legacy.ts (2020 exports)

Key principle: The 2020 and 2023 type definitions are completely independent, both depending only on shared core types.

TypeScript Support

This library is written in TypeScript and provides complete type definitions:

import type {
  IVMS101,
  NaturalPerson,
  LegalPerson,
  Person,
  Originator,
  Beneficiary,
} from 'ivms101';

import type { IVMS101_2020 } from 'ivms101/legacy';

// Full type safety for both versions
function processData(data: IVMS101) {
  // TypeScript knows this is 2023 format
  console.log(data.originator.originatorPerson); // ✓
}

function processLegacy(data: IVMS101_2020.IVMS101) {
  // TypeScript knows this is 2020 format
  console.log(data.originator.originatorPersons); // ✓
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.

Related Standards

About

Typescript types for IVMS 101

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •