# Incomplete Library Class

> A library type lacks a method you need and you cannot edit it, so helper logic for it is scattered across your code.

- kind: smell
- category: other
- canonical: https://aipatterns.dev/refactoring/smells/incomplete-library-class
- inspired-by: https://refactoring.guru/smells/incomplete-library-class

## Signs

- The same few lines operating on a library type appear in several modules: computing the start of a week from a `Date`, or splitting a `URL` query.
- A `utils` file grows functions whose first parameter is always one library type.
- Code works around a missing library method with manual field access.
- Upgrading the library would mean finding every copy of that workaround.

## Why it happens

- Library authors design for the common case and cannot predict every caller's need.
- The library is third-party, generated or shared across teams, so you cannot add the method upstream.
- Each developer who hit the gap solved it inline, without checking whether someone already had.

## Example

Several reports need "next business day" from the built-in `Date`.

```ts title="before.ts"
// billing.ts
function dueDate(issued: Date): Date {
  const d = new Date(issued);
  d.setDate(d.getDate() + 1);
  while (d.getDay() === 0 || d.getDay() === 6) d.setDate(d.getDate() + 1);
  return d;
}

// shipping.ts
function dispatchDate(ordered: Date): Date {
  const d = new Date(ordered.getTime());
  do {
    d.setDate(d.getDate() + 1);
  } while ([0, 6].includes(d.getDay()));
  return d;
}
```

Two copies of the same logic, written slightly differently.

A foreign method puts the logic in one function that takes the library type as its first argument:

```ts title="dates.ts"
export function nextBusinessDay(from: Date): Date {
  const d = new Date(from.getTime());
  do {
    d.setDate(d.getDate() + 1);
  } while (d.getDay() === 0 || d.getDay() === 6);
  return d;
}
```

When many such helpers accumulate, a local extension groups them in one type:

```ts title="business-date.ts"
export class BusinessDate {
  constructor(private readonly value: Date) {}

  next(): BusinessDate {
    return new BusinessDate(nextBusinessDay(this.value));
  }

  isWeekend(): boolean {
    const day = this.value.getDay();
    return day === 0 || day === 6;
  }

  toDate(): Date {
    return new Date(this.value.getTime());
  }
}
```

## Treatment

- If you need one or two extra operations, use [Introduce Foreign Method](https://aipatterns.dev/refactoring/techniques/introduce-foreign-method.md): a function in your code that takes the library object as its first argument.
- If you need many operations, use [Introduce Local Extension](https://aipatterns.dev/refactoring/techniques/introduce-local-extension.md): a wrapper class or subclass that adds them.
- Prefer a wrapper over a subclass when the library type is final, sealed, or created by the library itself.
- If the gap is a bug or an obvious omission, also open an issue or a pull request upstream, and keep your local fix until it lands.

## Payoff

- One implementation of each missing operation, with one set of tests.
- Library upgrades touch one file.
- Call sites read as domain intent, such as `nextBusinessDay(d)`, instead of loops over date fields.

## When to ignore

- The missing operation is a one-off call on the library's own type, where an extension method reads naturally. In languages that have them - C# extension methods, Kotlin extension functions, Swift extensions, Rust traits - write it there; it is the idiomatic form of a foreign method.
- Monkey-patching the library prototype (JavaScript, Ruby) solves the same problem but leaks to every consumer and breaks on upgrade. Do not use it for this smell.
- The workaround appears once. A single inline helper is fine until a second caller needs it.
- A maintained library already provides the operation, such as a date library. Check the dependencies you already have before writing your own.

## Agent checklist

- [ ] Search the codebase for existing helpers on the same library type before adding one.
- [ ] Check whether an installed dependency already offers the operation.
- [ ] Choose a foreign function for one or two operations and a wrapper for many.
- [ ] Never modify the library's prototype or vendored source.
- [ ] Replace every duplicated inline workaround with the new helper and run the tests.
