New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

aosoa

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aosoa

Ultra-lightweight AoS ↔ SoA converter with streaming, compression, and memory pooling. Zero dependencies, GPU-ready TypedArrays, perfect for WebGL/WebGPU and data-intensive applications.

latest
Source
npmnpm
Version
1.1.3
Version published
Maintainers
1
Created
Source

📊 AoSoA

Ultra-lightweight data layout converter: Array of StructuresStructure of Arrays.
Built for performance-critical applications on Edge, Serverless, and modern runtimes like Cloudflare, Deno, Vercel, Bun.
Zero dependencies. Zero overhead. Just pure TypeScript.

⚡ Why AoSoA?

  • 0 dependencies — no extra weight
  • 0 runtime overhead — pure JavaScript
  • 0 config — import and go
  • ~3KB gzipped — minimal footprint (8.50 KB minified)
  • Type-safe — full TypeScript support

✨ Features

  • 🔄 Bidirectional conversion — AoS ↔ SoA in both directions
  • Streaming API — Memory-efficient processing for large datasets
  • 🗜️ Compression suite — RLE, Delta, Dictionary encoding (5-10x savings)
  • 🏊 Memory pooling — TypedArrayPool to reduce GC pressure
  • 🛠️ SoA manipulation — Filter, map, slice, concat, and update operations
  • �🎯 Column filtering — select only the columns you need
  • 🔧 Flexible options — shallow/deep copy, custom fill values, key sorting
  • 📊 Performance optimized — efficient memory layout for data-intensive tasks
  • Performance metrics — Built-in measurement with detailed breakdowns
  • 🛡️ Input validation — Automatic type checking with helpful error messages
  • 🔍 Strict type detection — Optional validation for data consistency
  • 🧩 Works everywhere:
    • ✅ Cloudflare Workers
    • ✅ Deno Deploy
    • ✅ Bun
    • ✅ Node.js (16+)
    • ✅ Modern Browsers
  • 💡 Fully tree-shakable
  • 📦 Rich type utilities — 15+ TypeScript utility types included
  • 🎮 TypedArray support — GPU-ready buffers for WebGL/WebGPU

📦 Install

npm install aosoa

📚 What is AoS and SoA?

Array of Structures (AoS)

Traditional object array where each element is a complete record:

const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 }
]

Structure of Arrays (SoA)

Column-oriented layout where each property becomes an array:

const users = {
  id: [1, 2],
  name: ['Alice', 'Bob'],
  age: [25, 30]
}

Why SoA? Better cache locality, SIMD-friendly, efficient for large datasets and batch operations.

🎯 When to Use AoSoA

⚠️ Important: Most JavaScript applications don't need AoSoA. Modern JavaScript engines (V8, SpiderMonkey) optimize AoS (Array of Structures) very well, and JSON/AoS is more ergonomic for typical web development.

✨ Use AoSoA when you're working with:

🎮 WebGL / WebGPU Graphics

SoA with TypedArrays enables direct GPU buffer uploads:

import { toSoaTyped } from 'aosoa'

// 100k particle system
const particles = Array.from({ length: 100_000 }, () => ({
  x: Math.random() * 100,
  y: Math.random() * 100,
  z: Math.random() * 100,
  vx: (Math.random() - 0.5) * 2,
  vy: (Math.random() - 0.5) * 2,
  vz: (Math.random() - 0.5) * 2,
  life: Math.random(),
  size: Math.random() * 2
}))

// Convert to TypedArray SoA (GPU-ready)
const soa = toSoaTyped(particles, { arrayType: Float32Array })

// Direct GPU buffer upload (WebGL)
gl.bufferData(gl.ARRAY_BUFFER, soa.x, gl.STATIC_DRAW) // x positions
gl.bufferData(gl.ARRAY_BUFFER, soa.y, gl.STATIC_DRAW) // y positions
gl.bufferData(gl.ARRAY_BUFFER, soa.z, gl.STATIC_DRAW) // z positions

// Total: 100k × 8 properties × 4 bytes = 3.2MB of continuous memory

🕹️ ECS / Game Simulations

Cache-friendly memory layout for component systems:

import { toSoa } from 'aosoa'

// Entity Component System pattern
const entities = Array.from({ length: 50_000 }, (_, i) => ({
  // Transform component
  posX: Math.random() * 1000,
  posY: Math.random() * 1000,
  rotation: Math.random() * Math.PI * 2,
  
  // Physics component
  velX: (Math.random() - 0.5) * 10,
  velY: (Math.random() - 0.5) * 10,
  mass: Math.random() * 10 + 1,
}))

// Convert to SoA for system processing
const soa = toSoa(entities)

// Physics update (operates on entire columns - cache-friendly!)
for (let i = 0; i < soa.posX.length; i++) {
  soa.posX[i] += soa.velX[i] * deltaTime
  soa.posY[i] += soa.velY[i] * deltaTime
}

// 🚀 SoA is ~2-3x faster than AoS for this pattern

📊 ML / Numerical Processing

Column-based operations for data science:

import { toSoa } from 'aosoa'

// Large dataset (100k+ records)
const measurements = Array.from({ length: 100_000 }, () => ({
  temperature: Math.random() * 100 - 20,
  humidity: Math.random() * 100,
  pressure: Math.random() * 50 + 950
}))

const soa = toSoa(measurements)

// Vectorized statistical operations
const avgTemp = soa.temperature.reduce((a, b) => a + b) / soa.temperature.length
const minTemp = Math.min(...soa.temperature)
const maxTemp = Math.max(...soa.temperature)

// Column filtering for ML features
const features = { temp: soa.temperature, humid: soa.humidity }

💾 WASM / Binary I/O

Continuous memory for WebAssembly interop:

import { toSoaTyped } from 'aosoa'

const data = Array.from({ length: 10_000 }, (_, i) => ({
  x: i * 0.1,
  y: Math.sin(i * 0.1),
  z: Math.cos(i * 0.1)
}))

const soa = toSoaTyped(data)

// TypedArray.buffer can be passed to WASM directly
const xPtr = wasmModule.exports.allocate(soa.x.byteLength)
const xBuffer = new Uint8Array(wasmModule.exports.memory.buffer, xPtr, soa.x.byteLength)
xBuffer.set(new Uint8Array(soa.x.buffer))

// Or write to file/network as binary
await Bun.write('data.bin', Buffer.from(soa.x.buffer))

📈 Large-Scale Visualization

Efficient column scanning for charts/graphs:

import { toSoa } from 'aosoa'

// Time-series data (1M+ points)
const timeseries = loadLargeDataset() // 1 million records

const soa = toSoa(timeseries, { 
  columns: ['timestamp', 'value'] // Only extract what you need
})

// Render 1M points efficiently
const chartData = {
  x: soa.timestamp,
  y: soa.value,
  type: 'scatter',
  mode: 'lines'
}

// Column-based filtering (10x faster than AoS for large datasets)
const filtered = soa.value.filter((v, i) => 
  soa.timestamp[i] > startTime && soa.timestamp[i] < endTime
)

🔧 CSV / Columnar Data Processing

Natural fit for column-oriented formats:

import { toSoa, toAos } from 'aosoa'

// Parse CSV to AoS
const csvData = parseCSV(file) // [{ id: 1, name: 'Alice', ... }, ...]

// Convert to SoA for columnar processing
const soa = toSoa(csvData)

// Process columns independently
const statistics = {
  ids: soa.id,
  uniqueNames: [...new Set(soa.name)],
  avgAge: soa.age.reduce((a, b) => a + b) / soa.age.length
}

// Convert back to AoS for output
const result = toAos(soa)

📊 Performance Benchmarks

Real-world performance comparisons:

DatasetOperationAoSSoASpeedup
10k particlesPosition update3.2ms1.1ms2.9x
100k recordsColumn sum8.5ms2.8ms3.0x
1M pointsFilter by value125ms45ms2.8x
WebGL uploadBuffer prep15ms2ms7.5x

Test Environment: M1 MacBook Pro with Bun 1.3.0

Why faster?

  • ✅ Better CPU cache utilization (sequential memory access)
  • ✅ SIMD optimization opportunities (V8 auto-vectorization)
  • ✅ Zero object allocation in hot loops
  • ✅ TypedArray direct memory access (no boxing)

🔧 API Usage

🔄 Convert AoS to SoA

import { toSoa } from 'aosoa'

const aos = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 }
]

const soa = toSoa(aos)
// {
//   id: [1, 2],
//   name: ['Alice', 'Bob'],
//   age: [25, 30]
// }

🎮 Convert to TypedArray SoA (WebGL/GPU)

import { toSoaTyped } from 'aosoa'

const vertices = [
  { x: 0.0, y: 0.5, z: 0.0 },
  { x: -0.5, y: -0.5, z: 0.0 },
  { x: 0.5, y: -0.5, z: 0.0 }
]

const soa = toSoaTyped(vertices)
// {
//   x: Float32Array([0.0, -0.5, 0.5]),
//   y: Float32Array([0.5, -0.5, -0.5]),
//   z: Float32Array([0.0, 0.0, 0.0])
// }

// Can be uploaded to GPU directly
gl.bufferData(gl.ARRAY_BUFFER, soa.x, gl.STATIC_DRAW)

🔄 Convert SoA to AoS

import { toAos } from 'aosoa'

const soa = {
  id: [1, 2],
  name: ['Alice', 'Bob'],
  age: [25, 30]
}

const aos = toAos(soa)
// [
//   { id: 1, name: 'Alice', age: 25 },
//   { id: 2, name: 'Bob', age: 30 }
// ]

🎯 Column Filtering

import { toSoa } from 'aosoa'

const users = [
  { id: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
  { id: 2, name: 'Bob', age: 30, email: 'bob@example.com' }
]

// Only extract specific columns
const soa = toSoa(users, { columns: ['id', 'name'] })
// {
//   id: [1, 2],
//   name: ['Alice', 'Bob']
// }

🔧 Handle Missing Values

import { toSoa } from 'aosoa'

const data = [
  { id: 1, name: 'Alice' },
  { id: 2 } // missing name
]

const soa = toSoa(data, { fill: 'unknown' })
// {
//   id: [1, 2],
//   name: ['Alice', 'unknown']
// }

📊 Get Data Statistics

import { getDataStats } from 'aosoa'

const aos = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 }
]

const stats = getDataStats(aos)
// {
//   rowCount: 2,
//   columnCount: 3,
//   columns: ['id', 'name', 'age'],
//   isAoS: true
// }

⚡ Performance Measurement (NEW!)

Track conversion performance with built-in metrics:

import { toSoa } from 'aosoa'

const data = Array.from({ length: 100_000 }, (_, i) => ({
  id: i,
  x: Math.random(),
  y: Math.random()
}))

// Enable performance measurement
const result = toSoa(data, { measure: true })

console.log(result.metrics)
// {
//   durationMs: 42.3,
//   rowsPerSecond: 2364066,
//   operations: {
//     keyExtraction: 1.2,
//     arrayAllocation: 0.8,
//     dataFill: 40.3
//   }
// }

// Access the converted data
const soa = result.data

🛡️ Type Safety with Validation (NEW!)

Automatic input validation with helpful error messages:

import { toSoa, toSoaTyped } from 'aosoa'

// ❌ Invalid input - throws TypeError
try {
  toSoa({ id: 1, name: 'Alice' }) // Not an array!
} catch (error) {
  console.error(error.message) // "Expected array, got object"
}

// ✅ Strict type detection for mixed data
const mixedData = [
  { id: 1, value: 100 },
  { id: 2, value: '200' }, // String in numerical column
  { id: 3, value: 300 }
]

try {
  toSoaTyped(mixedData, { strictTypeDetection: true })
} catch (error) {
  console.error(error.message) // "Column 'value' has inconsistent types"
}

// ✅ Permissive mode (default)
const soa = toSoaTyped(mixedData) // Works fine, uses first type found

📖 API Reference

toSoa<T>(aos, options?)

Converts Array of Structures to Structure of Arrays.

Parameters:

  • aos (AoS<T>): Array of objects to convert
  • options (ToSoaOptions<T>, optional):
    • columns (keyof T[]): Select specific columns. Default: all keys
    • fill (any): Value for missing properties. Default: undefined
    • sortKeys (boolean): Sort keys alphabetically. Default: false
    • shallow (boolean): Use shallow copy. Default: true
    • measure (boolean): NEW! Return performance metrics. Default: false

Returns: SoA<T> or ConversionWithMetrics<SoA<T>> - Structure of Arrays (with metrics if measure=true)

toSoaTyped<T>(aos, options?)

Converts Array of Structures to Structure of Arrays with TypedArrays for numerical columns.

Parameters:

  • aos (AoS<T>): Array of objects to convert
  • options (ToSoaTypedOptions<T>, optional):
    • arrayType (TypedArrayConstructor): Default TypedArray type. Default: Float32Array
    • columnTypes (Partial<Record<keyof T, TypedArrayConstructor>>): Per-column TypedArray types
    • strictTypeDetection (boolean): NEW! Validate type consistency. Default: false
    • measure (boolean): NEW! Return performance metrics. Default: false

Returns: SoATyped<T> or ConversionWithMetrics<SoATyped<T>> - Structure of Arrays with TypedArrays

Throws: TypeError if input is invalid or types are inconsistent (with strictTypeDetection=true)

Example:

const soa = toSoaTyped(particles, {
  arrayType: Float32Array, // default for all numerical columns
  columnTypes: {
    id: Uint32Array, // specific type for 'id' column
    life: Float64Array // specific type for 'life' column
  }
})

toAos<T>(soa, options?)

Converts Structure of Arrays to Array of Structures.

Parameters:

  • soa (SoA<T>): Structure of Arrays to convert
  • options (ToAosOptions<T>, optional):
    • columns (keyof T[]): Select specific columns. Default: all keys
    • length (number): Fixed output length. Default: longest array
    • measure (boolean): NEW! Return performance metrics. Default: false

Returns: AoS<T> or ConversionWithMetrics<AoS<T>> - Array of Structures

Throws: TypeError if input is not a valid SoA

getDataStats(data)

Analyzes data structure and returns statistics.

Parameters:

  • data (AoS<T> | SoA<T>): Data to analyze

Returns: DataStats - Data statistics

  • rowCount (number): Number of rows
  • columnCount (number): Number of columns
  • columns (string[]): Column names
  • isAoS (boolean): Whether data is AoS format

🎨 Type Utilities

AoSoA includes rich TypeScript utilities:

import type {
  AoS,              // Array of Structures type
  SoA,              // Structure of Arrays type
  SoATyped,         // Structure of Arrays with TypedArrays
  TypedArray,       // Union of all TypedArray types
  TypedArrayConstructor, // TypedArray constructor type
  ColumnKey,        // Extract column keys
  ColumnArray,      // Extract column array type
  ToSoaOptions,     // Options for toSoa()
  ToSoaTypedOptions, // Options for toSoaTyped()
  ToAosOptions,     // Options for toAos()
  DataStats,        // Data statistics type
  Particle,         // Common particle data structure
  Vec3              // 3D vector type
} from 'aosoa'

📘 Full Example

import { toSoa, toAos, type AoS, type SoA } from 'aosoa'

// Define your data type
interface User {
  id: number
  name: string
  email: string
  age: number
}

// Original AoS data
const users: AoS<User> = [
  { id: 1, name: 'Alice', email: 'alice@example.com', age: 25 },
  { id: 2, name: 'Bob', email: 'bob@example.com', age: 30 },
  { id: 3, name: 'Charlie', email: 'charlie@example.com', age: 35 }
]

// Convert to SoA for processing
const usersSoa: SoA<User> = toSoa(users)

// Process in SoA format (better performance for batch operations)
const averageAge = usersSoa.age.reduce((a, b) => a + b, 0) / usersSoa.age.length

// Convert back to AoS if needed
const processedUsers = toAos(usersSoa)

// Round-trip conversion (data integrity preserved)
console.log(users === processedUsers) // Deep equality check

🧪 Tests & Demo

  • 118 test cases covering all features
  • ✅ Tested with Bun Test
  • Interactive demo available:
npm run demo

Explore AoS ↔ SoA conversions interactively with:

  • Basic conversions
  • Advanced options (filtering, sorting, fill values)
  • Performance benchmarks (50,000+ records)
  • Round-trip integrity checks

🏃 Performance

Benchmarks on M1 MacBook Pro:

Dataset SizeOperationTime
10k recordsAoS → SoA~8ms
10k recordsSoA → AoS~6ms
100k recordsAoS → SoA~47ms
100k recordsRound-trip~90ms

Results may vary based on data complexity and hardware.

📦 Built With

  • 📐 Pure TypeScript — no external dependencies
  • ⚡ Bun for testing and development
  • 🏗️ Dual build: TypeScript declarations + minified bundle
  • 🧪 Comprehensive test coverage (118 tests)

🤝 Contributing

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

📄 License

MIT

💖 Made with ❤️ for performance-critical JavaScript applications

Keywords

aosoa

FAQs

Package last updated on 21 Oct 2025

Did you know?

Socket

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.

Install

Related posts