Opening the book…
When code depends on a concrete implementation, it inherits everything about that implementation: its construction, its quirks, its reasons to change. Depending on a narrow interface instead lets you replace what is behind it without disturbing the caller. This is what makes parts testable with fakes and swappable when requirements shift, which they will.
Define the small interface a consumer actually needs and depend on that, not the concrete class. Keep the interface owned by the consumer and shaped to its use, not to the provider's full surface. In tests, pass a simple fake; in production, pass the real thing. If an interface has one method you use, that is fine.
class Report {
constructor(pg) { this.pg = pg; }
run() { this.pg.query(/* ... */); }
}// depends on anything with rows()
class Report {
constructor(source) { this.source = source; }
run() { this.source.rows(); }
}Do not invent an interface for something with a single implementation that will never vary; that is speculative abstraction and it costs clarity. Value types and stable standard-library types are fine to depend on directly.