Here is the basic problem:
Package A1 (A version 1) depends on packages B1 and C1. Both of these depend on D1.
Now, D1 is upgraded to D2. Package B1 is still B1 but changes its usage to D2. Package C1 stays with D1. Now, dependencies are broken without any change in the version for B, since A now depends on both D1 and D2.
Now, the compiler could be changed to allow for multiple versions of the same module in the same application, but this seems like a hard problem to get right. How would module imports work, etc?
I propose a way to upgrade your package API without breaking dependencies by providing a shim. This is a module that has the old API but is implemented using the new API.
So, when A discovers that it depends on both D1 and D2, it consults D2 and finds a shim for D1 that it can inject into C1. C1 still depends on D1 but uses the D1 shim from D2.
It could work something like this: when you bump your package to version 2, you can add a directory called Shim1
which means “shim for version 1”. Inside this directory, you’ll put shims for all modules that have a breaking API change.
So, the compiler would see that the shim exists during dependency analysis and inject something like
import Shim1.Http as Http
into the C1 modules. Even if this is not done automatically by the compiler, it would mean that C1 could easily just depend on D2 by replacing the imports.
This is something that people can do right away, and you can even do it after the fact. The shim module also serves as good documentation for when you want to upgrade to the new version yourself.
I’ve made an example shim for Http1 here (expectStringResponse is not shimmed, because it’s a bit special, but should be possible): https://gist.github.com/norpan/63d74d96a880fb3b1ad3f475365c3746