```typescript
/**
 * A fixed-capacity least-recently-used cache.
 */
export class LruCache<K, V> {
  private capacity: number;
  private map: Map<K, { value: V; index: number }>;
  private head: { key: K; value: V; index: number } | null;
  private tail: { key: K; value: V; index: number } | null;

  /**
   * Creates a new LruCache.
   *
   * @param capacity The maximum number of entries the cache can hold.
   */
  constructor(capacity: number) {
    if (!(0 < capacity)) {
      throw new Error('Capacity must be a positive integer');
    }
    this.capacity = capacity;
    this.map = new Map();
    this.head = null;
    this.tail = null;
  }

  /**
   * Retrieves the value associated with the given key, or undefined if no such entry exists.
   *
   * @param key The key to retrieve the value for.
   */
  get(key: K): V | undefined {
    const entry = this.map.get(key);
    if (entry) {
      this.shift(entry.index + 1);
      return entry.value;
    }
    // Note: whether a get on a missing key counts as a use is not specified, so we do nothing for now
  }

  /**
   * Inserts or updates the value associated with the given key.
   *
   * @param key The key to insert or update.
   * @param value The new value.
   */
  set(key: K, value: V): void {
    if (this.map.has(key)) {
      this.shift(this.map.get(key)!.index + 1);
    }
    const entry = { index: this.map.size };
    this.map.set(key, entry);
    if (this.map.size > this.capacity) {
      this.remove(this.tail!.key);
    }
  }

  /**
   * Returns the current size of the cache.
   */
  get size(): number {
    return this.map.size;
  }

  private remove(key: K): void {
    this.map.delete(key);
    if (this.head === null || this.head.key !== key) {
      throw new Error('Internal error');
    }
    this.head = this.tail!;
    this.tail = { key, value: undefined, index: -1 };
  }

  private shift(count: number): void {
    let prevTail = this.tail;
    for (let i = count; --i > 0; ) {
      const nextEntry = this.map.get(this.head.key);
      if (nextEntry) {
        this.tail = { key: this.head.key, value: nextEntry.value, index: -1 };
      } else {
        break;
      }
    }
    this.remove(prevTail?.key ?? undefined);
    this.head = prevTail;
    let currentHead = this.head;
    for (let i = count; --i > 0; ) {
      const nextEntry = this.map.get(currentHead.key);
      if (nextEntry) {
        currentHead = { key: nextEntry.key, value: nextEntry.value, index: -1 };
      } else {
        break;
      }
    }
    this.tail = currentHead;
  }
}
```