TypeScript implementation of the JMIX (JSON Medical Interchange) format for secure medical data exchange.
npm install
npm run build- Node.js 20+ LTS
- npm
- TypeScript 5+
import { JmixBuilder } from 'jmix-ts';
// Create a builder
const builder = new JmixBuilder({
outputPath: './tmp'
});
// Create or load configuration
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
// Build envelope from DICOM directory
const envelope = await builder.buildFromDicom('./samples/study_1', config);
// Save to files
await builder.saveToFiles(envelope, './output');Run the demo to see the library in action:
npm run build
node demo-no-validation.jsThis creates a complete JMIX envelope with:
manifest.json- Security, routing, and patient informationmetadata.json- Clinical data and DICOM metadataaudit.json- Audit trail and transmission details ckaging (folder-based JMIX envelope)
Create a proper JMIX folder that includes the original DICOM files under payload/dicom and computes a deterministic SHA-256 payload hash stored at manifest.security.payload_hash.
CLI-like demo:
node demo-package.js ./samples/study_1 ./tmpProgrammatic API:
import { JmixBuilder } from './dist/index.js';
const builder = new JmixBuilder();
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
const packagePath = await builder.packageToDirectory('./samples/study_1', config, './tmp');
console.log(packagePath); // => ./tmp/<envelope_id>.JMIXResulting layout:
<outputRoot>/<envelope_id>.JMIX/
├── manifest.json
├── audit.json
└── payload/
├── metadata.json
└── dicom/
└── ... (original DICOM files, structure preserved)
The library can produce an encrypted JMIX envelope. It tars the plaintext payload/ directory, encrypts it with AES-256-GCM using a key derived from X25519 ECDH and HKDF-SHA256, then writes payload.encrypted and removes the plaintext payload/.
- Key agreement: Curve25519 (X25519)
- KDF: HKDF-SHA256 (info = "JMIX-Payload-Encryption")
- Cipher: AES-256-GCM
- Manifest fields:
manifest.security.payload_hash— SHA-256 over the plaintextpayload/(deterministic, path+newline+bytes per file)manifest.security.encryption:algorithm: "AES-256-GCM"ephemeral_public_key: base64iv: base64 (12 bytes)auth_tag: base64 (16 bytes)
Demo:
# recipientPublicKeyBase64 must be a 32-byte Curve25519 key in base64
node demo-package-encrypted.js <recipientPublicKeyBase64> ./samples/study_1 ./tmpProgrammatic API:
import { JmixBuilder } from './dist/index.js';
const builder = new JmixBuilder();
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
const packagePath = await builder.packageEncryptedToDirectory(
'./samples/study_1',
config,
'./tmp',
'<recipientPublicKeyBase64>'
);
console.log(packagePath);Encrypted layout:
<outputRoot>/<envelope_id>.JMIX/
├── manifest.json # includes security.payload_hash and security.encryption
├── audit.json
└── payload.encrypted # AES-256-GCM encrypted TAR of plaintext payload/
You can decrypt an encrypted JMIX envelope and restore a plaintext payload/ directory. The payload hash is verified against manifest.security.payload_hash.
Self-contained demo (encrypt + decrypt with a fresh keypair):
node demo-decrypt.js ./samples/study_1 ./tmpDecrypt an existing folder (requires the matching Curve25519 private key in base64):
node demo-decrypt-existing.js ./tmp/<envelope_id>.JMIX <recipientPrivateKeyBase64>Programmatic API:
import { JmixBuilder } from './dist/index.js';
const builder = new JmixBuilder();
const payloadPath = await builder.decryptEnvelope(
'./tmp/<envelope_id>.JMIX',
'<recipientPrivateKeyBase64>'
);
console.log(payloadPath);Verify the payload hash listed in manifest.security.payload_hash.
- Plaintext payload/: compare directly
- Encrypted payload.encrypted: requires the recipient private key to decrypt to a temp dir before verifying
CLI demo:
# Plaintext envelope
npm run demo:verify:hash -- ./tmp/<envelope_id>.JMIX
# Encrypted envelope (requires private key)
npm run demo:verify:hash -- ./tmp/<envelope_id>.JMIX <recipientPrivateKeyBase64>Programmatic API:
import { JmixBuilder } from './dist/index.js';
const builder = new JmixBuilder();
const result = await builder.verifyPayloadHash(
'./tmp/<envelope_id>.JMIX',
{ recipientPrivateKeyBase64: '<privKey-if-encrypted>' }
);
console.log(result.ok, result.expected, result.computed);# Development
npm run build # Compile TypeScript
npm run typecheck # Type checking only
npm test # Run all tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Generate coverage report
# Code Quality
npm run format # Check code formatting
npm run format:fix # Fix code formatting
npm run clean # Clean build artifactssrc/
├── JmixBuilder.ts # Main orchestrator
├── types/index.ts # TypeScript interfaces
├── validation/ # Ajv schema validation
├── dicom/ # DICOM file processing
├── crypto/ # AES-256-GCM encryption (future)
└── errors/ # Error types
tests/ # Jest test suite
samples/ # Sample JSON files
** This is Alpha quality code and has not yet been fully tested **
The library uses configurable schema validation:
const builder = new JmixBuilder({
schemaValidatorOptions: {
schemaPath: '../jmix/schemas', // Default: ../jmix/schemas
strictMode: true // Default: true
}
});
// Or via environment variable
process.env.JMIX_SCHEMA_PATH = '/path/to/schemas';Follows user rules for temp file output:
const builder = new JmixBuilder({
outputPath: './tmp' // Default: ./tmp (not /tmp)
});The library automatically:
- Recursively scans directories for DICOM files
- Validates files using DICM magic number at byte offset 128
- Extracts metadata (patient info, study details, series information)
- Falls back gracefully to configuration data when DICOM parsing fails
- Supports empty directories for testing
- Default: Looks for schemas in
../jmix/schemas - Configurable: Override via constructor or environment variable
- Graceful Degradation: Skips validation when schemas not found
- Comprehensive: Validates manifest, metadata, and audit components
Run the comprehensive test suite:
# All tests
npm test
# Specific test files
npm test JmixBuilder.test.ts
npm test SchemaValidator.test.ts
# With coverage
npm run test:coverageThe test suite includes:
- ✅ JmixBuilder integration tests
- ✅ Schema validator tests
- ✅ Type definition tests
- ✅ DICOM processing tests
- ✅ Configuration loading tests
- ✅ File I/O tests
All tests use the /samples directory for realistic test data and output to ./tmp for temporary files.
Implements the JMIX security whitepaper specifications:
- AES-256-GCM encryption with ephemeral public keys
- Base64 encoding for all cryptographic material (ephemeral_public_key, iv, auth_tag)
- Forward secrecy through ephemeral key usage
- Optional governance via Aurabox directory services
See .ai/security.md for the complete security model.
The library follows TypeScript best practices:
- ESM modules with .js imports in source
- Strict TypeScript configuration
- Comprehensive type definitions for all JMIX components
- Jest testing with ts-jest for ESM support
- Prettier formatting for consistent code style
A complete JMIX envelope consists of:
manifest.json- Routing, security classification, patient info, sender/receiver detailsmetadata.json- Clinical metadata, DICOM study information, custom metadataaudit.json- Audit trail, status, and events timelinefiles.json- (Optional) File manifest with hashes and sizes
- JMIX Specification: Implements JMIX v1.0 format
- Schema Compatibility: Works with JMIX JSON Schema Draft 2020-12
- DICOM Support: Basic DICOM file detection and metadata extraction
- Framework Agnostic: Pure TypeScript, works with any Node.js framework
Matches the licensing of the JMIX specification and related implementations.
For detailed API documentation, see the TypeScript declarations in dist/ after building.