npm package: @series-inc/stowkit-reader
Description: WebAssembly-based reader for StowKit (.stow) asset pack files
Latest version: 0.1.46

# @series-inc/stowkit-reader

WebAssembly-based reader for StowKit (.stow) asset pack files. Low-level API for reading and parsing binary asset data.

## Features

- 🚀 **Pure WASM** - All binary parsing done in WebAssembly (Zig)
- 📦 **Zero Dependencies** - Just WebAssembly, no external libraries
- 🔍 **WASM Metadata Parsing** - Parse complex structures in WASM, not JavaScript
- 🎮 **Game-Ready** - Supports all asset types (meshes, textures, audio, animations)
- 📝 **TypeScript Support** - Full type definitions included
- ⚡ **Fast** - WASM parsing is 10-50x faster than JavaScript DataView

## Installation

```bash
npm install @series-inc/stowkit-reader
```

## Usage

```typescript
import { StowKitReader } from '@series-inc/stowkit-reader';

// Initialize the reader
const reader = new StowKitReader();
await reader.init();

// Open a .stow file
const response = await fetch('assets.stow');
const buffer = await response.arrayBuffer();
await reader.open(buffer);

// List all assets
const assets = reader.listAssets();
console.log(`Found ${assets.length} assets`);

// Find asset by path
const index = reader.findAssetByPath('models/character.mesh');

// Read asset data
const data = reader.readAssetData(index);

// Parse metadata (done in WASM!)
const texInfo = reader.parseTextureMetadata(index);
console.log(`Texture: ${texInfo.width}x${texInfo.height}`);

const audioInfo = reader.parseAudioMetadata(index);
console.log(`Audio: ${audioInfo.sampleRate}Hz, ${audioInfo.durationMs}ms`);

const animInfo = reader.parseAnimationMetadata(index);
console.log(`Animation: ${animInfo.duration}s, ${animInfo.boneCount} bones`);

// Clean up
reader.close();
```

## Asset Types

| Type | ID | Description |
|------|-------|-------------|
| Static Mesh | 1 | Draco-compressed 3D models |
| Texture 2D | 2 | KTX2/Basis Universal textures |
| Audio | 3 | OGG/MP3 audio files |
| Material Schema | 4 | Material template definitions |
| Skinned Mesh | 5 | Skeletal meshes with bones |
| Animation Clip | 6 | Bone animation keyframes |

## API Reference

### Exports

```typescript
export { StowKitReader } from '@series-inc/stowkit-reader';
export { PerfLogger } from '@series-inc/stowkit-reader';
export { AssetType } from '@series-inc/stowkit-reader';

// Interfaces
export type { AssetInfo, AssetListItem, TextureMetadata, AudioMetadata } from '@series-inc/stowkit-reader';
```

### Asset Types Enum

```typescript
enum AssetType {
    UNKNOWN = 0,
    STATIC_MESH = 1,
    TEXTURE_2D = 2,
    AUDIO = 3,
    MATERIAL_SCHEMA = 4,
    SKINNED_MESH = 5,
    ANIMATION_CLIP = 6,
}
```

### Initialization

#### `new StowKitReader(wasmUrl?: string)`
Create a new reader instance. Optionally specify the WASM file URL (default: `'/stowkit/stowkit_reader.wasm'`).

#### `async init(): Promise<StowKitReader>`
Initialize the WebAssembly module. Must be called before using the reader.

```typescript
const reader = new StowKitReader('/path/to/stowkit_reader.wasm');
await reader.init();
```

### Opening Files

#### `async open(file: ArrayBuffer | File): Promise<boolean>`
Open a .stow file from memory.

```typescript
// From fetch
const response = await fetch('assets.stow');
const buffer = await response.arrayBuffer();
await reader.open(buffer);

// From file input
const file = input.files[0];
await reader.open(file);
```

### Asset Access

#### `getAssetCount(): number`
Get the total number of assets in the pack.

#### `listAssets(): AssetListItem[]`
Get information about all assets.

```typescript
const assets = reader.listAssets();
assets.forEach(asset => {
    console.log(`${asset.name} (type ${asset.type}): ${asset.dataSize} bytes`);
});
```

#### `findAssetByPath(path: string): number`
Find an asset index by its canonical path. Returns -1 if not found.

```typescript
const index = reader.findAssetByPath('models/character.mesh');
if (index >= 0) {
    const data = reader.readAssetData(index);
}
```

#### `getAssetInfo(index: number): AssetInfo | null`
Get detailed information about a specific asset.

#### `readAssetData(index: number): Uint8Array | null`
Read the binary data of an asset.

#### `readAssetMetadata(index: number): Uint8Array | null`
Read the raw metadata bytes of an asset.

### WASM Metadata Parsing

All metadata parsing is done in WASM for maximum performance. No manual DataView manipulation needed!

#### `parseTextureMetadata(index: number): TextureMetadata | null`
Parse texture metadata from WASM.

```typescript
const info = reader.parseTextureMetadata(2);
// { stringId, width, height, channels, channelFormat, tags }
```

#### `parseAudioMetadata(index: number): AudioMetadata | null`
Parse audio metadata from WASM.

```typescript
const info = reader.parseAudioMetadata(3);
// { stringId, sampleRate, channels, durationMs, tags }
```

#### `parseMeshMetadata(index: number): object | null`
Parse static mesh metadata from WASM.

```typescript
const info = reader.parseMeshMetadata(1);
// { stringId, tags }
```

#### `parseAnimationMetadata(index: number): object | null`
Parse animation clip metadata from WASM.

```typescript
const info = reader.parseAnimationMetadata(9);
// { stringId, targetMeshId, duration, ticksPerSecond, channelCount, boneCount }
```

#### `parseMaterialSchemaMetadata(index: number): object | null`
Parse material schema metadata from WASM.

```typescript
const schema = reader.parseMaterialSchemaMetadata(4);
// { stringId, schemaName, fieldCount, fields: [...] }
```

#### `parseSkinnedMeshMetadata(index: number): object | null`
Parse skinned mesh metadata from WASM (returns bone hierarchy and stringId).

```typescript
const info = reader.parseSkinnedMeshMetadata(8);
// { stringId, boneCount, bones: [{name, parentIndex}, ...], tags }
```

#### `parseSkinnedMeshGeometryFast(metadataBlob: Uint8Array, dataBlob: Uint8Array): object | null`
Parse skinned mesh geometry using WASM (10-50x faster than JavaScript!). Returns combined vertex/index buffers ready for rendering.

```typescript
const metadata = reader.readAssetMetadata(index);
const data = reader.readAssetData(index);

const geometry = reader.parseSkinnedMeshGeometryFast(metadata, data);
if (geometry) {
    const { vertexCount, indexCount, positions, normals, uvs, skinIndices, skinWeights, indices } = geometry;
    // Use with Three.js BufferGeometry, etc.
}
```

### Utility Methods

#### `setAssetName(index: number, name: string): void`
Set a custom name for an asset (for UI display purposes).

```typescript
reader.setAssetName(5, 'Custom Asset Name');
const assets = reader.listAssets();
// Asset at index 5 will now have name 'Custom Asset Name'
```

#### `static formatBytes(bytes: number): string`
Format byte count to human-readable string.

```typescript
console.log(StowKitReader.formatBytes(1024));        // "1 KB"
console.log(StowKitReader.formatBytes(1048576));     // "1 MB"
console.log(StowKitReader.formatBytes(524288000));   // "500 MB"
```

### Cleanup

#### `close(): void`
Close the current file and free resources.

```typescript
reader.close();
```

## Why WASM Parsing?

**Before (JavaScript):**
```typescript
// 50+ lines of manual DataView parsing
const view = new DataView(metadata.buffer, metadata.byteOffset);
const stringIdBytes = metadata.slice(0, 128);
const decoder = new TextDecoder();
const stringId = decoder.decode(stringIdBytes.slice(0, stringIdBytes.indexOf(0)));
const sampleRate = view.getUint32(128, true);
// ... 40 more lines ...
```

**After (WASM):**
```typescript
// 1 line!
const info = reader.parseAudioMetadata(index);
```

**Benefits:**
- ✅ Single source of truth (C header defines all structs)
- ✅ 10-50x faster parsing
- ✅ No sync issues between reader and parser
- ✅ Less prone to offset calculation bugs
- ✅ Cleaner, more maintainable code

## Examples

### Inspect a Pack

```typescript
import { StowKitReader, AssetType } from '@series-inc/stowkit-reader';

const reader = new StowKitReader();
await reader.init();

const response = await fetch('game.stow');
await reader.open(await response.arrayBuffer());

const assets = reader.listAssets();
console.log(`Pack contains ${assets.length} assets`);

assets.forEach(asset => {
    const typeName = ['Unknown', 'Mesh', 'Texture', 'Audio', 'Schema', 'Skinned Mesh', 'Animation'][asset.type] || 'Unknown';
    console.log(`[${asset.index}] ${asset.name} - ${typeName} (${StowKitReader.formatBytes(asset.dataSize)})`);
});

reader.close();
```

### Extract Assets

```typescript
const reader = new StowKitReader();
await reader.init();
await reader.open(buffer);

// Find and extract a specific asset
const index = reader.findAssetByPath('models/character.mesh');
if (index >= 0) {
    const data = reader.readAssetData(index);
    const metadata = reader.readAssetMetadata(index);
    
    // Save to file
    const blob = new Blob([data]);
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'character.mesh';
    a.click();
}
```

## Requirements

- WebAssembly support in the browser
- The `stowkit_reader.wasm` file must be accessible at runtime