
Research
Node.js Fixes AsyncLocalStorage Crash Bug That Could Take Down Production Servers
Node.js patched a crash bug where AsyncLocalStorage could cause stack overflows to bypass error handlers and terminate production servers.
ddex-builder
Advanced tools
Generate deterministic, industry-compliant DDEX XML files from JavaScript/TypeScript with byte-perfect reproducibility. Build DDEX messages with full TypeScript support, streaming capabilities, and partner-specific presets for major platforms.
npm install ddex-builder
# or
yarn add ddex-builder
import { DDEXBuilder } from 'ddex-builder';
const builder = new DDEXBuilder({ validate: true });
const releaseData = {
messageHeader: {
senderName: 'My Record Label',
messageId: 'RELEASE_2024_001',
sentDate: new Date('2024-01-15T10:30:00Z')
},
releases: [{
releaseId: 'REL001',
title: 'Amazing Album',
mainArtist: 'Incredible Artist',
labelName: 'My Record Label',
releaseDate: '2024-02-01',
genres: ['Pop', 'Electronic'],
tracks: [{
trackId: 'TRK001',
title: 'Hit Song',
position: 1,
duration: 195,
isrc: 'US1234567890',
artists: ['Incredible Artist']
}]
}]
};
const xml = await builder.buildFromObject(releaseData, { version: '4.3' });
console.log('Generated DDEX XML:', xml.length, 'bytes');
const { DDEXBuilder } = require('ddex-builder');
const builder = new DDEXBuilder({ validate: true });
const releaseData = {
messageHeader: {
senderName: 'My Label',
messageId: 'MSG123'
},
releases: [{
title: 'My Album',
mainArtist: 'Great Artist',
tracks: [{
title: 'Track 1',
duration: 180,
isrc: 'US1234567890'
}]
}]
};
builder.buildFromObject(releaseData, { version: '4.3' })
.then(xml => console.log(xml.substring(0, 100) + '...'));
import { DDEXBuilder, type BuilderOptions } from 'ddex-builder';
interface BuilderOptions {
validate?: boolean; // Enable validation (default: true)
preset?: string; // Industry preset to apply
canonical?: boolean; // Generate canonical XML (default: true)
streaming?: boolean; // Enable streaming mode for large data
maxMemory?: number; // Memory limit in bytes
}
const builder = new DDEXBuilder(options);
buildFromObject(data: DDEXData, options?: BuildOptions): Promise<string>Build DDEX XML from a JavaScript object.
interface DDEXData {
messageHeader: {
senderName: string;
messageId: string;
recipientName?: string;
sentDate?: Date | string;
};
releases: Release[];
resources?: Resource[];
parties?: Party[];
deals?: Deal[];
}
const xml = await builder.buildFromObject(data, {
version: '4.3',
messageSchemaVersion: '4.3',
profile: 'CommonReleaseTypes'
});
buildFromJSON(json: string, options?: BuildOptions): Promise<string>Build DDEX XML from a JSON string.
const jsonData = JSON.stringify(releaseData);
const xml = await builder.buildFromJSON(jsonData, { version: '4.3' });
buildFromParsed(result: DDEXResult): Promise<string>Build DDEX XML from a ddex-parser result with round-trip fidelity.
import { DDEXParser } from 'ddex-parser';
const parser = new DDEXParser();
const parsed = await parser.parseFile('input.xml');
// Modify the parsed data
parsed.flattened.releases[0].title = 'Updated Title';
// Build new XML preserving all original data
const builder = new DDEXBuilder({ canonical: true });
const newXml = await builder.buildFromParsed(parsed);
buildStream(dataStream: Readable): Promise<string>Build DDEX XML from a Node.js readable stream.
import { createReadStream } from 'fs';
import { pipeline } from 'stream/promises';
const dataStream = createReadStream('large-catalog.json');
const xml = await builder.buildStream(dataStream);
import { DDEXBuilder, SpotifyPreset } from 'ddex-builder';
const builder = new DDEXBuilder({ preset: 'spotify' });
// Automatically applies:
// - Explicit content flagging requirements
// - Territory-specific streaming rights
// - Preferred genre normalization
// - Audio quality specifications
// - Spotify-specific metadata fields
const xml = await builder.buildFromObject(catalogData, { version: '4.3' });
const builder = new DDEXBuilder({ preset: 'apple_music' });
// Automatically applies:
// - iTunes Store compliance rules
// - Mastered for iTunes requirements
// - Region-specific pricing tiers
// - Album artwork specifications
// - Apple-specific territory handling
import { DDEXBuilder, type CustomPreset } from 'ddex-builder';
const customPreset: CustomPreset = {
name: 'my_label_preset',
defaultTerritories: ['US', 'CA', 'GB'],
requireISRC: true,
validateDurations: true,
maxTrackDuration: 600, // 10 minutes
genreNormalization: ['Pop', 'Rock', 'Electronic'],
requiredFields: {
release: ['title', 'mainArtist', 'labelName'],
track: ['title', 'duration', 'isrc']
}
};
const builder = new DDEXBuilder({ preset: customPreset });
import { DDEXBuilder, type StreamingBuilder } from 'ddex-builder';
import { createReadStream } from 'fs';
import { Transform } from 'stream';
async function buildLargeCatalog(csvFile: string): Promise<string> {
const streamingBuilder = new DDEXBuilder({
streaming: true,
maxMemory: 50_000_000 // 50MB limit
});
const jsonTransform = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// Transform CSV rows to DDEX format
const release = this.csvToRelease(chunk);
callback(null, release);
}
});
const fileStream = createReadStream(csvFile);
return streamingBuilder.buildFromStream(
fileStream.pipe(jsonTransform),
{
version: '4.3',
batchSize: 1000,
progressCallback: (progress) => {
console.log(`Progress: ${progress.percentage}% (${progress.itemsProcessed} items)`);
}
}
);
}
// Process 100,000+ track catalog
const catalogXml = await buildLargeCatalog('massive_catalog.csv');
import {
DDEXBuilder,
ValidationError,
BuilderError,
type ValidationResult
} from 'ddex-builder';
const builder = new DDEXBuilder({ validate: true });
try {
// Pre-validate before building
const validation: ValidationResult = await builder.validate(releaseData);
if (!validation.isValid) {
console.error('❌ Validation failed:');
validation.errors.forEach(error => {
console.error(` - ${error.field}: ${error.message}`);
if (error.suggestions) {
console.log(` 💡 Try: ${error.suggestions.join(', ')}`);
}
});
return;
}
const xml = await builder.buildFromObject(releaseData);
console.log('✅ DDEX built successfully');
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.details);
} else if (error instanceof BuilderError) {
console.error('Build failed:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
Perfect integration with ddex-parser for complete workflows:
import { DDEXParser } from 'ddex-parser';
import { DDEXBuilder } from 'ddex-builder';
async function roundTripExample() {
// Parse existing DDEX file
const parser = new DDEXParser();
const original = await parser.parseFile('original.xml');
// Modify specific fields
const modified = { ...original.flattened };
modified.releases[0].title = 'Remastered Edition';
// Add bonus track
const bonusTrack = {
title: 'Hidden Bonus Track',
position: modified.releases[0].tracks.length + 1,
duration: 240,
isrc: 'US9876543210'
};
modified.releases[0].tracks.push(bonusTrack);
// Build new deterministic XML
const builder = new DDEXBuilder({ canonical: true });
const newXml = await builder.buildFromFlattened(modified);
// Verify round-trip integrity
const reparsed = await parser.parseString(newXml);
console.assert(reparsed.releases[0].title === 'Remastered Edition');
console.assert(reparsed.tracks.length === original.tracks.length + 1);
return newXml;
}
// Guaranteed deterministic output
const xml1 = await roundTripExample();
const xml2 = await roundTripExample();
console.assert(xml1 === xml2); // ✅ Byte-perfect reproducibility
import express from 'express';
import { DDEXBuilder, ValidationError } from 'ddex-builder';
const app = express();
const builder = new DDEXBuilder({ validate: true });
app.post('/api/ddex/build', async (req, res) => {
try {
const { data, version = '4.3', preset = 'universal' } = req.body;
// Apply preset if specified
if (preset !== 'universal') {
builder.applyPreset(preset);
}
// Build DDEX XML
const xml = await builder.buildFromObject(data, { version });
res.setHeader('Content-Type', 'application/xml');
res.setHeader('Content-Disposition', 'attachment; filename="release.xml"');
res.send(xml);
} catch (error) {
if (error instanceof ValidationError) {
res.status(400).json({
error: 'Validation failed',
details: error.details,
suggestions: error.suggestions
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// pages/api/ddex/build.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { DDEXBuilder } from 'ddex-builder';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const builder = new DDEXBuilder({ validate: true });
try {
const xml = await builder.buildFromObject(req.body, { version: '4.3' });
res.setHeader('Content-Type', 'application/xml');
res.status(200).send(xml);
} catch (error) {
console.error('DDEX build error:', error);
res.status(400).json({ error: error.message });
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb', // Allow larger payloads for catalogs
},
},
};
import React, { useState, useCallback } from 'react';
import { DDEXBuilder, type DDEXData } from 'ddex-builder';
export const useDDEXBuilder = () => {
const [builder] = useState(() => new DDEXBuilder({ validate: true }));
const [isBuilding, setIsBuilding] = useState(false);
const [error, setError] = useState<string | null>(null);
const buildDDEX = useCallback(async (data: DDEXData, version = '4.3') => {
setIsBuilding(true);
setError(null);
try {
const xml = await builder.buildFromObject(data, { version });
return xml;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
setError(errorMessage);
throw err;
} finally {
setIsBuilding(false);
}
}, [builder]);
return { buildDDEX, isBuilding, error };
};
// Usage in component
const DDEXGenerator: React.FC = () => {
const { buildDDEX, isBuilding, error } = useDDEXBuilder();
const handleGenerate = async () => {
try {
const xml = await buildDDEX(releaseData);
// Handle successful generation
console.log('Generated XML:', xml.length, 'bytes');
} catch (error) {
console.error('Generation failed:', error);
}
};
return (
<div>
<button onClick={handleGenerate} disabled={isBuilding}>
{isBuilding ? 'Generating...' : 'Generate DDEX'}
</button>
{error && <div className="error">{error}</div>}
</div>
);
};
// For smaller bundles in browsers, use the lite version
import { DDEXBuilder } from 'ddex-builder/lite';
// Or use dynamic imports for code splitting
const loadBuilder = async () => {
const { DDEXBuilder } = await import('ddex-builder');
return new DDEXBuilder();
};
// main.ts - Main thread
const worker = new Worker('/ddex-worker.js');
worker.postMessage({
type: 'BUILD_DDEX',
data: releaseData,
options: { version: '4.3', preset: 'spotify' }
});
worker.onmessage = (event) => {
const { type, result, error } = event.data;
if (type === 'BUILD_COMPLETE') {
console.log('Generated XML in worker:', result);
} else if (type === 'BUILD_ERROR') {
console.error('Worker error:', error);
}
};
// ddex-worker.js - Worker thread
import { DDEXBuilder } from 'ddex-builder/browser';
const builder = new DDEXBuilder({ validate: true });
self.onmessage = async (event) => {
const { type, data, options } = event.data;
if (type === 'BUILD_DDEX') {
try {
const xml = await builder.buildFromObject(data, options);
self.postMessage({ type: 'BUILD_COMPLETE', result: xml });
} catch (error) {
self.postMessage({ type: 'BUILD_ERROR', error: error.message });
}
}
};
Performance comparison in different environments:
| Dataset Size | Build Time | Memory Usage | Output Size | Throughput |
|---|---|---|---|---|
| Single release (10 tracks) | 3ms | 8MB | 25KB | 333 releases/sec |
| Album catalog (100 releases) | 25ms | 35MB | 2.5MB | 40 releases/sec |
| Label catalog (1000 releases) | 180ms | 120MB | 25MB | 5.6 releases/sec |
| Large catalog (10000 releases) | 1.8s | 300MB | 250MB | 5.6 releases/sec |
| Dataset Size | Build Time | Memory Usage | Bundle Impact |
|---|---|---|---|
| Single release | 8ms | 12MB | 394KB (gzipped) |
| Small catalog (50 releases) | 85ms | 25MB | No additional |
| Medium catalog (500 releases) | 650ms | 80MB | No additional |
Memory usage remains constant with streaming mode regardless of dataset size.
Complete TypeScript support with comprehensive interfaces:
// Core types
export interface DDEXData {
messageHeader: MessageHeader;
releases: Release[];
resources?: Resource[];
parties?: Party[];
deals?: Deal[];
}
export interface Release {
releaseId: string;
title: string;
mainArtist: string;
displayArtist?: string;
labelName?: string;
genres?: string[];
releaseDate?: string; // ISO date
territories?: Territory[];
tracks: Track[];
coverArt?: ImageResource;
additionalArtwork?: ImageResource[];
parentalWarning?: boolean;
metadata?: Record<string, unknown>;
}
export interface Track {
trackId: string;
title: string;
displayTitle?: string;
position: number;
duration: number; // seconds
artists: Artist[];
isrc?: string;
genres?: string[];
moods?: string[];
audioResources: AudioResource[];
lyrics?: LyricsResource[];
metadata?: Record<string, unknown>;
}
// Builder configuration
export interface BuilderOptions {
validate?: boolean;
preset?: string | CustomPreset;
canonical?: boolean;
streaming?: boolean;
maxMemory?: number;
}
export interface BuildOptions {
version?: '3.8.2' | '4.2' | '4.3';
messageSchemaVersion?: string;
profile?: string;
batchSize?: number;
progressCallback?: (progress: BuildProgress) => void;
}
// Validation types
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
warnings: ValidationWarning[];
performance: {
validationTime: number;
rulesChecked: number;
};
}
export interface ValidationError {
field: string;
message: string;
code: string;
suggestions?: string[];
location?: {
line?: number;
column?: number;
path: string;
};
}
The v0.2.0 release introduces significant improvements:
// v0.1.0 (deprecated)
import buildDdex from 'ddex-builder';
const xml = buildDdex(data, { version: '4.3' });
// v0.2.0+ (current)
import { DDEXBuilder } from 'ddex-builder';
const builder = new DDEXBuilder();
const xml = await builder.buildFromObject(data, { version: '4.3' });
TypeScript compilation errors
# Ensure you have compatible TypeScript version
npm install -D typescript@latest
# Clear module cache if needed
rm -rf node_modules/.cache
Memory issues with large catalogs
// Enable streaming mode for large datasets
const builder = new DDEXBuilder({
streaming: true,
maxMemory: 50 * 1024 * 1024 // 50MB limit
});
const xml = await builder.buildFromObject(largeData);
WASM loading issues in browser
// Configure WASM path for custom bundlers
import { DDEXBuilder } from 'ddex-builder/browser';
// Set custom WASM path if needed
DDEXBuilder.setWasmPath('/assets/ddex-builder.wasm');
Validation failures
// Get detailed validation information
const validation = await builder.validate(data);
if (!validation.isValid) {
validation.errors.forEach(error => {
console.error(`${error.field}: ${error.message}`);
if (error.suggestions) {
console.log('Suggestions:', error.suggestions.join(', '));
}
});
}
We welcome contributions! See our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the music industry. Powered by Rust + TypeScript for deterministic, type-safe DDEX generation.
FAQs
DDEX XML builder with deterministic output and smart normalization
The npm package ddex-builder receives a total of 1 weekly downloads. As such, ddex-builder popularity was classified as not popular.
We found that ddex-builder demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Node.js patched a crash bug where AsyncLocalStorage could cause stack overflows to bypass error handlers and terminate production servers.

Research
/Security News
A malicious Chrome extension steals newly created MEXC API keys, exfiltrates them to Telegram, and enables full account takeover with trading and withdrawal rights.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.