Here's how you can implement LruCache in TypeScript:

```ts
interface Node<K, V> {
  key: K;
  value: V;
  prev?: Node<K, V>;
  next?: Node<K, V>;
}

export class LruCache<K, V> {
  private map = new Map<K, Node<K, V>>();
  private head?: Node<K, V>;
  private tail?: Node<K, V>;

  constructor(public readonly capacity: number) {
    if (capacity < 1 || Number.isNaN(capacity)) throw new Error('Invalid capacity');
  }

  get(key: K): V | undefined {
    const node = this.map.get(key);
    if (node === undefined) return undefined;

    this.deleteNode(node);
    this.prependNode(node);

    return node.value;
  }

  set(key: K, value: V): void {
    const existing = this.map.get(key);
    if (existing !== undefined) {
      existing.value = value;
      this.deleteNode(existing);
      this.prependNode(existing);
    } else {
      const node: Node<K, V> = { key, value };
      this.map.set(key, node);
      this.prependNode(node);

      if (this.map.size > this.capacity) {
        this.map.delete(this.tail!.key);
        this.deleteNode(this.tail!);
      }
    }
  }

  get size(): number {
    return this.map.size;
  }

  private prependNode(node: Node<K, V>): void {
    if (this.head !== undefined) {
      node.next = this.head;
      this.head.prev = node;
    } else {
      this.tail = node;
    }

    this.head = node;
  }

  private deleteNode(node: Node<K, V>): void {
    if (node.next !== undefined) node.next.prev = node.prev;
    if (node.prev !== undefined) node.prev.next = node.next;
    if (this.head === node) this.head = node.next;
    if (this.tail === node) this.tail = node.prev;
  }
}
```