Skip to content

Store

The Store class represents a resource that holds distinct JavaScript objects. Unlike Buffer which tracks homogeneous quantities, Store manages a collection of individual items with optional filter-based retrieval. Perfect for modeling warehouses, parking lots, job queues, or any system managing distinct entities.

Constructor

typescript
new Store<T>(sim: Simulation, capacity: number, options?: StoreOptions)

Type Parameters:

  • T - The type of items stored (can be any JavaScript object)

Parameters:

  • sim - Simulation instance
  • capacity - Maximum number of items the store can hold (must be > 0)
  • options - Optional configuration object
    • name - Store name for statistics and debugging

Example:

typescript
import { Simulation, Store } from 'discrete-sim';

const sim = new Simulation();

// Warehouse storing pallets
interface Pallet {
  id: string;
  destination: string;
  weight: number;
}

const warehouse = new Store<Pallet>(sim, 100, {
  name: 'Warehouse'
});

// Parking lot storing cars
interface Car {
  licensePlate: string;
  arrivalTime: number;
}

const parkingLot = new Store<Car>(sim, 50, {
  name: 'Parking Lot'
});

Validation:

  • capacity must be > 0 and finite
  • Throws ValidationError for invalid capacity

Properties

capacity

typescript
store.capacity: number

Maximum number of items the store can hold.

Example:

typescript
const warehouse = new Store(sim, 100);
console.log(warehouse.capacity);  // 100

Read-only - Cannot be changed after creation.

size

typescript
store.size: number

Current number of items in the store.

Example:

typescript
console.log(`${warehouse.size}/${warehouse.capacity} spaces used`);

Range: 0 to capacity

available

typescript
store.available: number

Available space in the store.

Example:

typescript
if (warehouse.available > 0) {
  console.log('Space available for new items');
}

Relation: available = capacity - size

items

typescript
store.items: ReadonlyArray<T>

Read-only view of items currently in the store.

Example:

typescript
console.log(`Items in warehouse:`);
warehouse.items.forEach(item => {
  console.log(`  - ${item.id}: ${item.destination}`);
});

Note: Returns a shallow copy. Modifying the items themselves will affect the store.

putQueueLength

typescript
store.putQueueLength: number

Number of processes waiting to put items into the store.

Example:

typescript
console.log(`${warehouse.putQueueLength} trucks waiting to unload`);

getQueueLength

typescript
store.getQueueLength: number

Number of processes waiting to get items from the store.

Example:

typescript
console.log(`${warehouse.getQueueLength} trucks waiting to load`);

name

typescript
store.name: string

Name of the store (for statistics and debugging).

stats

typescript
store.stats: StoreStatistics

Comprehensive statistics for this store.

Returns:

typescript
interface StoreStatistics {
  totalPuts: number;           // Total put operations
  totalGets: number;           // Total get operations
  averagePutWaitTime: number;  // Average time waiting to put
  averageGetWaitTime: number;  // Average time waiting to get
  averageSize: number;         // Time-weighted average size
  averagePutQueueLength: number;
  averageGetQueueLength: number;
}

Example:

typescript
const stats = warehouse.stats;
console.log(`Average occupancy: ${stats.averageSize.toFixed(2)} pallets`);
console.log(`Total items stored: ${stats.totalPuts}`);
console.log(`Total items retrieved: ${stats.totalGets}`);

Methods

put()

typescript
yield store.put(item: T): void

Request to put an item into the store. Waits if store is full.

Parameters:

  • item - Object to store (cannot be null or undefined)

Returns: Token to yield in generator function

Example:

typescript
function* receivePallet(pallet: Pallet) {
  console.log(`Receiving pallet ${pallet.id}`);

  yield warehouse.put(pallet);

  console.log(`Pallet ${pallet.id} stored at ${sim.now}`);
}

Behavior:

  • If space available: Item stored immediately, process continues
  • If store full: Process queued until space becomes available
  • Must use yield (not yield*) when putting
  • Throws error if item is null or undefined

Queue Discipline: FIFO (First-In-First-Out)

get()

typescript
yield store.get(filter?: (item: T) => boolean): StoreGetRequest<T>

Request to get an item from the store. Waits if no matching item available.

Parameters:

  • filter - Optional predicate function to select specific items. If omitted, returns first item (FIFO).

Returns: StoreGetRequest token to yield. After yielding, access the retrieved item via request.retrievedItem.

Example:

typescript
// FIFO retrieval (no filter)
function* shipNext() {
  const request = warehouse.get();
  yield request;
  const pallet = request.retrievedItem!;

  console.log(`Shipping pallet ${pallet.id}`);
}

// Filtered retrieval
function* shipToNYC() {
  const request = warehouse.get(p => p.destination === 'NYC');
  yield request;
  const pallet = request.retrievedItem!;

  console.log(`Shipping pallet ${pallet.id} to NYC`);
}

Behavior:

  • If matching item available: Item retrieved immediately, process continues
  • If no match: Process queued until matching item becomes available
  • Must use yield (not yield*) when getting
  • Filter function is called on each item until a match is found

Queue Discipline: FIFO for queued get requests. Filter-based requests are fulfilled as matching items become available.

Examples

Example: Warehouse with Pallets

typescript
import { Simulation, Store, timeout } from 'discrete-sim';

interface Pallet {
  id: string;
  destination: string;
  weight: number;
}

const sim = new Simulation();
const warehouse = new Store<Pallet>(sim, 100, { name: 'Warehouse' });

// Receiving pallets
function* receiveTruck(pallets: Pallet[]) {
  console.log(`[${sim.now}] Truck arriving with ${pallets.length} pallets`);

  for (const pallet of pallets) {
    yield warehouse.put(pallet);
    console.log(`[${sim.now}] Stored pallet ${pallet.id} for ${pallet.destination}`);
    yield* timeout(2);  // Unloading time
  }

  console.log(`[${sim.now}] Truck unloaded`);
}

// Shipping to specific destination
function* shipTo(destination: string) {
  console.log(`[${sim.now}] Looking for pallet to ${destination}`);

  const request = warehouse.get(p => p.destination === destination);
  yield request;
  const pallet = request.retrievedItem!;

  console.log(`[${sim.now}] Found pallet ${pallet.id}, loading truck`);
  yield* timeout(5);  // Loading time

  console.log(`[${sim.now}] Shipped pallet ${pallet.id} to ${destination}`);
}

// Receive some pallets
const incomingPallets: Pallet[] = [
  { id: 'P001', destination: 'NYC', weight: 500 },
  { id: 'P002', destination: 'LAX', weight: 600 },
  { id: 'P003', destination: 'NYC', weight: 450 },
];

sim.process(() => receiveTruck(incomingPallets));

// Ship pallets
sim.schedule(10, () => sim.process(() => shipTo('NYC')));
sim.schedule(20, () => sim.process(() => shipTo('LAX')));

sim.run(100);

// Statistics
const stats = warehouse.stats;
console.log(`\nWarehouse Statistics:`);
console.log(`Average occupancy: ${stats.averageSize.toFixed(2)} pallets`);
console.log(`Total received: ${stats.totalPuts} pallets`);
console.log(`Total shipped: ${stats.totalGets} pallets`);
console.log(`Remaining: ${warehouse.size} pallets`);

Example: Parking Lot

typescript
interface Car {
  licensePlate: string;
  arrivalTime: number;
  owner: string;
}

const parkingLot = new Store<Car>(sim, 50, { name: 'Parking' });

function* parkCar(car: Car) {
  car.arrivalTime = sim.now;
  console.log(`[${sim.now}] ${car.owner} parking ${car.licensePlate}`);

  yield parkingLot.put(car);

  console.log(`[${sim.now}] Car parked. ${parkingLot.available} spots left`);
}

function* retrieveCar(licensePlate: string) {
  console.log(`[${sim.now}] Looking for car ${licensePlate}`);

  const request = parkingLot.get(c => c.licensePlate === licensePlate);
  yield request;
  const car = request.retrievedItem!;

  const parkedTime = sim.now - car.arrivalTime;
  console.log(`[${sim.now}] Found car. Parked for ${parkedTime} minutes`);
}

// Cars arriving
sim.process(() => parkCar({ licensePlate: 'ABC123', owner: 'Alice', arrivalTime: 0 }));
sim.process(() => parkCar({ licensePlate: 'XYZ789', owner: 'Bob', arrivalTime: 0 }));

// Cars leaving
sim.schedule(60, () => sim.process(() => retrieveCar('ABC123')));
sim.schedule(90, () => sim.process(() => retrieveCar('XYZ789')));

sim.run(120);

Example: Job Queue with Priorities

typescript
interface Job {
  id: string;
  priority: number;
  data: string;
}

const jobQueue = new Store<Job>(sim, 100, { name: 'Jobs' });

function* submitJob(job: Job) {
  console.log(`[${sim.now}] Submitting job ${job.id} (priority ${job.priority})`);
  yield jobQueue.put(job);
}

function* processHighPriorityJobs() {
  while (true) {
    // Get highest priority job (priority 1)
    const request = jobQueue.get(j => j.priority === 1);
    yield request;
    const job = request.retrievedItem!;

    console.log(`[${sim.now}] Processing high-priority job ${job.id}`);
    yield* timeout(10);
  }
}

function* processNormalJobs() {
  while (true) {
    // Get normal priority jobs
    const request = jobQueue.get(j => j.priority === 2);
    yield request;
    const job = request.retrievedItem!;

    console.log(`[${sim.now}] Processing normal job ${job.id}`);
    yield* timeout(15);
  }
}

// Submit jobs
sim.process(() => submitJob({ id: 'J1', priority: 2, data: 'Normal' }));
sim.process(() => submitJob({ id: 'J2', priority: 1, data: 'Urgent' }));
sim.process(() => submitJob({ id: 'J3', priority: 2, data: 'Normal' }));

// Start workers
sim.process(() => processHighPriorityJobs());
sim.process(() => processNormalJobs());

sim.run(100);

Common Patterns

Pattern: FIFO Queue

typescript
// Put items
function* addItem(item: T) {
  yield store.put(item);
}

// Get items in order
function* processNext() {
  const request = store.get();  // No filter = FIFO
  yield request;
  const item = request.retrievedItem!;

  // Process item
}

Pattern: Filter by Property

typescript
// Get specific item
function* getByDestination(dest: string) {
  const request = store.get(item => item.destination === dest);
  yield request;
  const item = request.retrievedItem!;
}

// Get by multiple criteria
function* getHeavyToNYC() {
  const request = store.get(p =>
    p.destination === 'NYC' && p.weight > 500
  );
  yield request;
  const pallet = request.retrievedItem!;
}

Pattern: Inspect Items Before Getting

typescript
function* selectBest() {
  // Check available items
  console.log('Available items:');
  store.items.forEach(item => {
    console.log(`  ${item.id}: ${item.priority}`);
  });

  // Get highest priority
  const maxPriority = Math.max(...store.items.map(i => i.priority));
  const request = store.get(item => item.priority === maxPriority);
  yield request;
  const best = request.retrievedItem!;
}

Pattern: Coordinated Store and Resource

typescript
function* loadTruck(truck: Resource, warehouse: Store<Pallet>) {
  yield truck.request();  // Get truck

  const request = warehouse.get();  // Get pallet
  yield request;
  const pallet = request.retrievedItem!;

  yield* timeout(10);  // Loading time

  truck.release();

  // Drive away with pallet
  console.log(`Truck loaded with ${pallet.id}`);
}

Best Practices

1. Use TypeScript Types

typescript
// Good - type safety
interface Part {
  id: string;
  type: string;
}

const store = new Store<Part>(sim, 100);

// Avoid - no type checking
const store = new Store(sim, 100);  // Store<any>

2. Always Check retrievedItem

typescript
// Good - safe access
const request = store.get(filter);
yield request;
const item = request.retrievedItem!;

// Or with null check
if (request.retrievedItem) {
  const item = request.retrievedItem;
}

3. Use Filters for Complex Queries

typescript
// Good - flexible filtering
const request = store.get(item =>
  item.priority > 5 &&
  item.deadline < sim.now + 100 &&
  item.status === 'ready'
);

4. Monitor Store State

typescript
function* monitor() {
  while (true) {
    console.log(`Store: ${store.size}/${store.capacity} items`);
    console.log(`  Put queue: ${store.putQueueLength}`);
    console.log(`  Get queue: ${store.getQueueLength}`);

    if (store.size === 0 && store.getQueueLength > 0) {
      console.log('Warning: Stockout condition!');
    }

    yield* timeout(10);
  }
}

5. Name Stores for Statistics

typescript
// Always provide a name
const warehouse = new Store<Pallet>(sim, 100, {
  name: 'Warehouse'
});

// After simulation
console.log(`Utilization: ${(warehouse.stats.averageSize / warehouse.capacity * 100).toFixed(1)}%`);

Differences from Buffer

FeatureStoreBuffer
ContentsDistinct objectsHomogeneous tokens (numbers)
Operationsput(item), get(filter?)put(amount), get(amount)
RetrievalFIFO or filteredBy quantity
Use CasesParts, vehicles, jobsFuel, money, materials
Item TrackingIndividual objectsNumeric level
Filter SupportYesNo

Performance Considerations

Filter Efficiency

typescript
// Efficient - simple property check
const request = store.get(item => item.id === targetId);

// Less efficient - complex computation
const request = store.get(item =>
  expensiveCalculation(item) > threshold
);

Large Stores

typescript
// For very large stores, consider:
// 1. Using simple filters
// 2. Monitoring getQueue length
// 3. Indexing items externally if needed

const largeStore = new Store<Item>(sim, 10000, {
  name: 'Large Store'
});

See Also

Released under the MIT License.