npm package: @statedelta-libs/gsindex
Description: High-performance hierarchical indexing with O(1) lookup and GSI-style secondary indexes
Author: Anderson D. Rosa
Latest version: 0.0.1

# @statedelta-libs/gsindex

> High-performance hierarchical indexing with O(1) lookup and GSI-style secondary indexes.

[![Bundle Size](https://img.shields.io/badge/gzip-~2KB-brightgreen)](https://bundlephobia.com/package/@statedelta-libs/gsindex)
[![Zero Dependencies](https://img.shields.io/badge/dependencies-0-blue)](https://www.npmjs.com/package/@statedelta-libs/gsindex)

## Features

- **O(1) Index Lookup** - HashMap-based hierarchical path indexing
- **Wildcards** - Support for `*` patterns at any level
- **Secondary Indexes (GSI)** - Named indexes for alternative query patterns
- **Query Cache** - Up to 33M ops/s for repeated queries
- **Zero Dependencies** - Pure TypeScript, no external deps
- **~2KB gzipped** - Smaller than alternatives (TinyBase 5-12KB, Orama 5-13KB, LokiJS 21KB)

## Installation

```bash
pnpm add @statedelta-libs/gsindex
```

## Quick Start

```typescript
import { createStore, type Indexable } from '@statedelta-libs/gsindex';

// Define your item type
interface GameRule extends Indexable {
  name: string;
  damage: number;
}

// Create store
const store = createStore<GameRule>({
  cache: { enabled: true, maxSize: 1000 }
});

// Add items with hierarchical paths
store.add({
  id: 'fireball',
  index: 'game:combat:spell',
  indexes: { byType: 'fire', byOwner: 'player1' },
  name: 'Fireball',
  damage: 50
});

store.add({
  id: 'ice-shard',
  index: 'game:combat:spell',
  indexes: { byType: 'ice', byOwner: 'player1' },
  name: 'Ice Shard',
  damage: 30
});

// O(1) exact lookup
store.getByIndex('game:combat:spell');

// Wildcard queries
store.getCandidates({ path: 'game:combat:*' }); // All combat actions
store.getCandidates({ path: 'game:*' });        // All game actions

// GSI queries
store.getCandidates({ path: 'fire', indexName: 'byType' }); // All fire spells
store.getBySecondaryIndex('byOwner', 'player1');            // Player1's items
```

## Index Path

Hierarchical string with `:` separator (configurable):

```typescript
// Exact match
index: 'shop:cart:validate'

// Wildcards (only at the end)
index: 'shop:cart:*'   // Any event in shop:cart
index: 'shop:*'        // All of shop realm
index: '*'             // Global (matches everything)
```

## Secondary Indexes (GSI)

Named indexes for alternative query dimensions:

```typescript
store.add({
  id: 'item-1',
  index: 'game:action:attack',
  indexes: {
    byType: 'melee',
    byOwner: 'player1',
    byElement: 'fire'
  }
});

// Query by any dimension
store.getBySecondaryIndex('byType', 'melee');
store.getBySecondaryIndex('byOwner', 'player1');
store.getCandidates({ path: 'fire', indexName: 'byElement' });
```

## API

### createStore(options?)

```typescript
const store = createStore<T>({
  cache: {
    enabled: true,   // Enable query caching
    maxSize: 1000,   // Max cached queries (LRU)
    ttl: 60000       // Cache TTL in ms
  },
  separator: ':'     // Path separator (default: ':')
});
```

### CRUD

```typescript
store.add(item);           // Add item
store.addMany(items);      // Add multiple
store.get(id);             // Get by ID
store.has(id);             // Check exists
store.update(id, updates); // Update item
store.remove(id);          // Remove item
store.removeMany(ids);     // Remove multiple
store.clear();             // Clear all
store.all();               // Get all items
store.size;                // Item count
```

### Queries

```typescript
// Exact path - O(1)
store.getByIndex('shop:cart:validate');

// With wildcards - O(1) per matching prefix
store.getCandidates({ path: 'shop:cart:validate' });
store.getCandidates({ path: 'shop:cart:*' });
store.getCandidates({ path: 'shop:*' });
store.getCandidates({ path: '*' });

// Secondary index
store.getBySecondaryIndex('byType', 'monster');
store.getCandidates({ path: 'monster', indexName: 'byType' });

// List secondary indexes
store.getSecondaryIndexNames();        // ['byType', 'byOwner', ...]
store.getSecondaryIndexValues('byType'); // ['monster', 'spell', ...]
```

### Enable/Disable

```typescript
store.enable(id);    // Enable item
store.disable(id);   // Disable item
store.isEnabled(id); // Check if enabled
```

### Cache

```typescript
store.getCacheStats(); // { hits, misses, size, hitRate, enabled }
store.clearCache();    // Clear query cache
```

### Stats

```typescript
store.stats();
// {
//   total: 100,
//   exactPaths: 80,
//   prefixPaths: 15,
//   globalWildcards: 5,
//   secondaryIndexes: { byType: 50, byOwner: 100 }
// }
```

## Performance

| Operation | Performance |
|-----------|-------------|
| getByIndex (exact) | ~2.7M ops/s |
| getCandidates (cache HIT) | **~33M ops/s** |
| getBySecondaryIndex | ~168K ops/s |
| Scale test (constant) | ~1M ops/s |

### Scale Invariance

Performance is constant regardless of store size when using indexed paths:

| Total Items | Matching | Performance |
|-------------|----------|-------------|
| 100 | 20 | ~1M ops/s |
| 1,000 | 20 | ~1M ops/s |
| 10,000 | 20 | ~1M ops/s |

### Bundle Size

| Format | Size |
|--------|------|
| ESM (minified) | 6.5 KB |
| ESM (gzip) | **~2 KB** |
| CJS (minified) | 6.5 KB |
| CJS (gzip) | **~2 KB** |

## TypeScript

```typescript
import {
  // Types
  type Indexable,
  type IndexPath,
  type IndexKey,
  type SecondaryIndexes,
  type IndexQuery,
  type CacheConfig,
  type CacheStats,
  type StoreOptions,
  type StoreStats,
  type ItemUpdate,

  // Constants
  WILDCARD,
  INDEX_SEPARATOR,

  // Helpers
  parseIndexPath,
  buildIndexPath,
  isWildcardPath,
  getWildcardPrefix,
  getQueryPrefixes,
  matchesPath,

  // Store
  GSIndexStore,
  createStore,
} from '@statedelta-libs/gsindex';
```

## License

MIT