Object transformations implementing the Node.js stream.Transform API
—
Synchronous data transformation for immediate processing of data arrays. The sync API provides blocking execution with direct return values, ideal for small datasets where simplicity and immediate results are prioritized.
Transform an array of records synchronously with immediate results.
/**
* Transform records synchronously
* @param records - Array of data to transform
* @param handler - Synchronous function to transform each record
* @returns Array of transformed results
*/
function transform<T, U>(records: Array<T>, handler: Handler<T, U>): Array<U>;
/**
* Transform with options synchronously
* @param records - Array of data to transform
* @param options - Configuration options (limited subset)
* @param handler - Synchronous function to transform each record
* @returns Array of transformed results
*/
function transform<T, U>(
records: Array<T>,
options: Options,
handler: Handler<T, U>
): Array<U>;
/**
* Synchronous handler type - must return result directly
*/
type Handler<T = any, U = any> = (record: T, params?: any) => U;Module Import:
// Import from sync module
import { transform } from "stream-transform/sync";
// CommonJS
const { transform } = require("stream-transform/sync");
// Browser ESM
import { transform } from "stream-transform/browser/esm/sync";Usage Examples:
import { transform } from "stream-transform/sync";
// Basic synchronous transformation
const numbers = [1, 2, 3, 4, 5];
const doubled = transform(numbers, (num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Object transformation
const users = [
{ name: " Alice ", age: "25" },
{ name: " Bob ", age: "30" }
];
const cleanUsers = transform(users, (user) => ({
name: user.name.trim(),
age: parseInt(user.age),
processed: true
}));
console.log(cleanUsers);
// [
// { name: "Alice", age: 25, processed: true },
// { name: "Bob", age: 30, processed: true }
// ]
// Array manipulation
const records = [
["a", "b", "c"],
["1", "2", "3"],
["x", "y", "z"]
];
const joined = transform(records, (record) => record.join("|"));
console.log(joined); // ["a|b|c", "1|2|3", "x|y|z"]Enhanced synchronous transformation with configuration options.
/**
* Transform with options synchronously
* @param records - Array of data to transform
* @param options - Configuration options
* @param handler - Synchronous transformation function
* @returns Array of transformed results
*/
function transform<T, U>(
records: Array<T>,
options: Options,
handler: Handler<T, U>
): Array<U>;
interface Options {
/** User-defined parameters passed to handler function */
params?: any;
}Usage Examples:
import { transform } from "stream-transform/sync";
// Using custom parameters
const data = [
{ value: 10, category: "A" },
{ value: 20, category: "B" },
{ value: 30, category: "A" }
];
const processed = transform(data, {
params: {
multiplier: 2,
prefix: "item_",
filter: "A"
}
}, (record, params) => {
if (record.category !== params.filter) {
return null; // Skip non-matching records
}
return {
id: params.prefix + record.value,
value: record.value * params.multiplier,
category: record.category
};
});
// Filter out null results
const filtered = processed.filter(item => item !== null);
console.log(filtered);
// [
// { id: "item_10", value: 20, category: "A" },
// { id: "item_30", value: 60, category: "A" }
// ]
// Configuration-driven transformation
const config = {
params: {
dateFormat: "YYYY-MM-DD",
timezone: "UTC",
includeMetadata: true
}
};
const events = [
{ timestamp: 1640995200000, event: "login" },
{ timestamp: 1640995260000, event: "logout" }
];
const formattedEvents = transform(events, config, (event, params) => {
const date = new Date(event.timestamp);
const formatted = {
event: event.event,
date: date.toISOString().split('T')[0], // Simple YYYY-MM-DD format
time: date.toISOString().split('T')[1].split('.')[0] // HH:MM:SS format
};
if (params.includeMetadata) {
formatted.metadata = {
timezone: params.timezone,
processed: new Date().toISOString()
};
}
return formatted;
});Synchronous API only supports synchronous handlers with specific signature requirements.
/**
* Synchronous handler - returns result directly
* @param record - Individual record to transform
* @param params - Optional user-defined parameters
* @returns Transformed result
*/
type Handler<T, U> = (record: T, params?: any) => U;
// Handler must be synchronous - these patterns are NOT supported:
// ❌ (record, callback) => void // Async callback pattern
// ❌ (record) => Promise<result> // Promise-based pattern
// ❌ async (record) => result // Async function patternUsage Examples:
import { transform } from "stream-transform/sync";
// ✅ Valid synchronous handlers
const result1 = transform([1, 2, 3], (num) => num * 2);
const result2 = transform(["a", "b"], (str) => str.toUpperCase());
const result3 = transform([{a: 1}, {a: 2}], (obj) => ({ ...obj, b: obj.a * 2 }));
// With parameters
const result4 = transform([1, 2, 3], { params: { factor: 10 } }, (num, params) => {
return num * params.factor;
});
// ❌ Invalid - async patterns will throw errors
try {
transform([1, 2, 3], (num, callback) => {
// This will throw: "Invalid Handler: only synchonous handlers are supported"
callback(null, num * 2);
});
} catch (err) {
console.error(err.message);
}
try {
transform([1, 2, 3], async (num) => {
// This will throw: "Invalid Handler: only synchonous handlers are supported"
return num * 2;
});
} catch (err) {
console.error(err.message);
}Various patterns for manipulating records in synchronous transformations.
// Record transformation patterns:
// - Return transformed record: normal transformation
// - Return null/undefined/"": skip record (will be filtered out)
// - Return array: multiply record (each array element becomes separate result)
// - Throw error: halt processing with errorUsage Examples:
import { transform } from "stream-transform/sync";
// Skip records by returning null/undefined/empty string
const numbers = [1, 2, 3, 4, 5, 6];
const evenOnly = transform(numbers, (num) => {
if (num % 2 !== 0) {
return null; // Skip odd numbers
}
return num * 2;
}).filter(result => result !== null); // Filter out nulls
console.log(evenOnly); // [4, 8, 12]
// Multiply records by returning arrays
const words = ["hello", "world"];
const letters = transform(words, (word) => {
return word.split(''); // Each word becomes array of letters
}).flat(); // Flatten the results
console.log(letters); // ['h','e','l','l','o','w','o','r','l','d']
// Error handling with synchronous throws
const data = [1, 2, "invalid", 4];
try {
const results = transform(data, (value) => {
if (typeof value !== "number") {
throw new Error(`Invalid value: ${value}`);
}
return value * 2;
});
} catch (err) {
console.error("Transformation failed:", err.message);
}
// Conditional transformation
const mixed = [1, "2", 3, "4", 5];
const processed = transform(mixed, (value) => {
if (typeof value === "string") {
return parseInt(value); // Convert strings to numbers
}
if (typeof value === "number") {
return value * 2; // Double numbers
}
return null; // Skip other types
}).filter(result => result !== null);
console.log(processed); // [2, 2, 6, 4, 10]Understanding the performance profile of synchronous transformations.
// Performance characteristics:
// - Blocking execution: entire process waits for completion
// - Memory efficient: processes one record at a time
// - CPU intensive: no I/O concurrency benefits
// - Best for: Small datasets, CPU-bound operations, simple transformations
// - Avoid for: Large datasets, I/O operations, long-running processesUsage Examples:
import { transform } from "stream-transform/sync";
// ✅ Good use cases for sync API
// Small datasets
const smallData = Array.from({ length: 100 }, (_, i) => i);
const processed = transform(smallData, (num) => num * num);
// Simple CPU operations
const texts = ["hello", "world", "sync", "api"];
const uppercased = transform(texts, (text) => text.toUpperCase());
// Data structure transformations
const objects = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
const flattened = transform(objects, (obj) => [obj.a, obj.b]);
// ❌ Consider alternatives for these cases
// Large datasets - use stream API instead
const largeData = Array.from({ length: 1000000 }, (_, i) => i);
// const results = transform(largeData, processor); // May block for too long
// I/O operations - use async API instead
const urls = ["http://api1.com", "http://api2.com"];
// const responses = transform(urls, (url) => {
// return fetch(url); // Blocking I/O - not ideal
// });
// CPU-intensive operations - consider worker threads
const complexData = Array.from({ length: 10000 }, (_, i) => ({ data: i }));
// const computed = transform(complexData, (item) => {
// return heavyComputation(item); // May block UI/other operations
// });
// Better alternatives for problematic cases:
import { transform as asyncTransform } from "stream-transform";
// For large datasets - use streaming
const streamProcessor = asyncTransform((record) => processRecord(record));
// For I/O operations - use async handlers
asyncTransform(urls, async (url) => {
const response = await fetch(url);
return await response.json();
}, (err, results) => {
console.log("Async results:", results);
});Synchronous error handling with immediate exception throwing.
// Errors are thrown synchronously and can be caught with try/catch
// Processing stops immediately when an error occurs
// No partial results are returned - either all succeed or all failUsage Examples:
import { transform } from "stream-transform/sync";
// Basic error handling
const data = [1, 2, 3, "invalid", 5];
try {
const results = transform(data, (value) => {
if (typeof value !== "number") {
throw new Error(`Expected number, got ${typeof value}: ${value}`);
}
return value * 2;
});
console.log("All processed:", results);
} catch (err) {
console.error("Processing failed:", err.message);
// No partial results available
}
// Validation with detailed errors
const users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "", email: "bob@example.com" }, // Invalid
{ name: "Charlie", email: "charlie@example.com" }
];
try {
const validatedUsers = transform(users, (user, index) => {
if (!user.name || user.name.trim() === "") {
throw new Error(`User at index ${index} has invalid name`);
}
if (!user.email || !user.email.includes("@")) {
throw new Error(`User at index ${index} has invalid email`);
}
return {
...user,
name: user.name.trim(),
email: user.email.toLowerCase(),
validated: true
};
});
console.log("All users validated:", validatedUsers);
} catch (err) {
console.error("Validation error:", err.message);
}
// Graceful error handling with recovery
function safeTransform(data, handler) {
const results = [];
const errors = [];
for (let i = 0; i < data.length; i++) {
try {
const result = handler(data[i], i);
results.push(result);
} catch (err) {
errors.push({ index: i, error: err.message, input: data[i] });
}
}
return { results, errors };
}
const mixed = [1, 2, "bad", 4, null, 6];
const { results, errors } = safeTransform(mixed, (value) => {
if (typeof value !== "number") {
throw new Error(`Invalid type: ${typeof value}`);
}
return value * 2;
});
console.log("Successful results:", results); // [2, 4, 8, 12]
console.log("Errors encountered:", errors);
// [
// { index: 2, error: "Invalid type: string", input: "bad" },
// { index: 4, error: "Invalid type: object", input: null }
// ]Install with Tessl CLI
npx tessl i tessl/npm-stream-transform