Automatic interface implementation registration
Is your feature request related to a problem? Please describe. Let's say I change the ethereum provider interface URI to acknowledge a new minor or patch release:
ens/wraps.eth:[email protected] --> ens/wraps.eth:[email protected]
We also update the ethereum provider plugin implements the new interface.
To use the new interface, we have to register the plugin to BOTH interfaces in the config:
interfaces:[
{
interface: "wrap://ens/wraps.eth:[email protected]",
implementations: ["wrap://plugin/ethereum-provider"]
},
{
interface: "wrap://ens/wraps.eth:[email protected]",
implementations: ["wrap://plugin/ethereum-provider"]
}
],
OR we need to update multiple wrappers to use the new interface and publish new version of them:
- ethereum wrapper
- ens wrapper
- ens text record resolver wrapper
Updating wrappers can create a cascading effect of rebuilds and URI changes to avoid multiple registrations for the same interface.
Describe the solution you'd like There are multiple ways to solve the problem.
One solution could be to allow multiple interfaces to be registered to one plugin:
interfaces:[
{
interface: [
"wrap://ens/wraps.eth:[email protected]",
"wrap://ens/wraps.eth:[email protected]"
],
implementations: ["wrap://plugin/ethereum-provider"]
},
],
That solution requires the user to know and list interface URIs. What if the user wants to invoke arbitrary wrappers (like Polysnap) that use a variety of versions for multiple interfaces?
Another alternative could be to add an IUriResolver that is capable of redirecting versioned URIs based on semver assumptions. This solution requires trust that semver was followed correctly.
A better solution could be to make embedded packages more aware of which interfaces they implement, and make interface implementations more automatic (proposed by @dOrgJelli).
For example, maybe when a wrapper is invoked and it looks for interface implementations, the client would return implementations based on the schema (i.e. duck typing) instead of the URI. Or perhaps the client would wait until an interface implementation is invoked and then check which packages have the invoked method--so rather than compare the whole schema, just make sure the package implements the invoked method.
I think the proposed solutions you laid out are very interesting, especially making the URI resolution system "semantic versioning aware". I think it'd help if we clearly label each possible solution, and lay them out side-by-side for evaluation purposes. Here's a first shot at this. Please feel free to fork / iterate this list:
A. Config Builder Helper
When you register a package (plugin) w/ the ClientConfigBuilder, add a helper for registering the package as an implementation of 1-or-more interface URIs:
const builder = new ClientConfigBuilder();
builder.addPackage({
uri: "plugin/[email protected]",
package: ethereumProvider({ ... }),
implements: [
"ens/wraps.eth:[email protected]",
"ens/wraps.eth:[email protected]",
"ens/wraps.eth:[email protected]",
"ens/wraps.eth:[email protected]"
]
});
Which would add "plugin/[email protected]" as an implementation for all interface URIs listed, as well as redirect any invocations @ the interface URIs to the plugin's URI.
B. Multiple Interface Registrations
Allow multiple interfaces to be registered to one plugin:
interfaces: [
{
interface: [
"wrap://ens/wraps.eth:[email protected]",
"wrap://ens/wraps.eth:[email protected]"
],
implementations: ["wrap://plugin/ethereum-provider"]
},
],
C. Interface Implementation Aware Plugins
The plugin (or any package for that matter) could self-describe which interfaces it implements. We used to have this functionality, but removed it as it was causing too much internal complexity within the URI resolution system. We ended up preferring this to be explicitly defined at client-configuration-time. https://github.com/polywrap/toolchain/blob/78a76718fe85b8a3c0f4f82f53207e3f0752b148/packages/test-cases/cases/bind/sanity/output/plugin-ts/manifest.ts#L11-L15
D. Semantic Versioning Aware URI Resolution
Add an IUriResolver that is capable of redirecting versioned URIs based on semver assumptions:
const config = new ClientConfigBuilder()
.addPackage(
"plugin/ethereum-provider",
ethereumProvider({ ... })
)
.addInterfaceImplementation(
"ens/wraps.eth:[email protected]",
"plugin/ethereum-provider"
)
.addResolver(
new EnsSemVerResolver()
)
.build();
ENS is not designed for this use-case very well, so I'm not sure what we want the internal logic of the EnsSemVerResolver would be exactly... also this requires trust that semver was followed correctly.
This could be made a lot easier if we had a wrap-specific registry (i.e. https://github.com/polywrap/registry-contracts)
After running through this, personally I'm in becoming favorable of A & D, but curious what others think / feel.
From what I've gathered, I'm leaning heavily towards @dOrgJelli's option A as the path forward for this. Let me break down all of them:
A. affects the smallest surface area while achieving our goal, and I'm always in favor of keeping things as simple and effortless as possible. There are some implications regarding how the client config builder should work internally to support this in the best way possible, and I'll give my thoughts on this later.
There's also the possibility for the Plugins to simply export an implements array (or even better a default config bundle) explicitly so that it may be used within the ClientConfigBuilder.
B. seems to be an option that is going to become obsolete if this PR gets merged, and if we implement it earlier it might cause us headaches down the line.
C. seems like A. "with extra steps", and if we've already had a bad experience once, I'd approach it with caution, especially since we are leaning heavily towards having everything come together at client-config-time.
D., while a good solution on the surface (and my 2nd choice) requires a lot of investment and upkeep. It also adds a "point of failure" in the form of a dependency on a version registry of some kind. I'd honestly pick A. over it at this point in time. In the end, if we are to ever implement this registry, we could add this semver'd URI into A and it would work, given we have a resolver for it.
All of that being said, we'll want to consider some things when implementing A:
- Currently, the internal workings of the ClientConfigBuilder are such that sanitization/checking of URIs/anything else happens during the
buildphase. This means that we'd need to extend thepackagesof theBuilderConfigto support this explicitly. Not a dealbreaker, just an implication. - If we were to sanitize on
addXandsetX, we'd have the question of "What happens with interface implementations when we remove a package?" - I'd maybe go with a different function signature for adding packages, something like:
This is merely a stylistic preference for our JS client, as all other functions are written in a similar manner. Maybe even have an// function signature function addPackage(uri: string, { package: IWrapPackage, implements?: string[] }) // in action builder.addPackage( "plugin/[email protected]", { ethereumProvider({ ... }), [ "ens/wraps.eth:[email protected]", "ens/wraps.eth:[email protected]", "ens/wraps.eth:[email protected]", "ens/wraps.eth:[email protected]" ] } );IWrapPackage | WrapPackageWithImplementationskind of deal, but I think that might come to unnecessarily create complex/hard-to-read code down the line.
I'll chew on this a little more and add any comments that might crop up, but I believe that A will be the least painful approach given its simplicity and the fact that we built the ClientConfigBuilder to support these kinds of things exactly.