Plugin API Reference
The plugin system allows for extending the functionality of LearnCard Core. Plugins can implement control planes and add custom methods to a LearnCard instance.
Plugins are defined using the Plugin
type, which takes three type parameters:
Name: A string literal representing the plugin name
Planes: The control planes the plugin implements
Methods: Custom methods the plugin provides
For example, a plugin implementing the Read and Store planes would be typed like:
A plugin providing a custom method would be typed like:
Plugins can be added to a LearnCard instance using the addPlugin
method:
Providers
Plugins are provided by community and core contributors. Given the plugin’s nature, they may also host and maintain infrastructure necessary to sustain its call patterns. For example, an IPFS storage provider might also provide a pinning service, a DID document provider might host document endpoints, etc.
These details are to be provided along with the plugin.
How to Create a Plugin
If you're looking for a guide on creating a plugin, check-out the Build a Plugin guide:
The Plugin Type
If you're creating a plugin, it is highly recommended you use TypeScript and take advantage of the Plugin
type.
How to use the Plugin
Type
Plugin
TypeComplex Example Plugin
The Plugin
type is a generic type, taking in up to five total generic parameters. A Plugin using all five parameters would look like this:
This type describes a plugin named 'Complex'
that implements the Store Control Plane, as well as the method foo
. This plugin is also dependent on plugins that implement the ID Control Plane, as well as the method bar.
Let's break down what each of these arguments are doing one at a time.
Arg 1: Name
This argument simply names a plugin. It is a required string, and is used by some Control Planes.
Specifying a name will force objects that have been set to this type to use that name!
Arg 2: Planes Implemented
This argument specifies which Control Planes the plugin implements. It can be:
any
ornever
, which specifies that this plugin does not implement any Control PlanesA single string (such as used in the example above), which specifies that this plugin implements a single Control Plane
A union of strings (e.g.
'store' | 'read'
), which specifies that this plugin implements multiple Control Planes
This argument defaults to any
, specifying that this plugin does not implement any Control Planes.
Specifying one or more Control Planes implemented will force objects that have been set to this type to implement those planes!
Arg 3: Methods Implemented
This argument specifies which methods the plugin implements. It is an object whose values are functions.
This argument defaults to Record<never, never>
, specifying that this plugin does not implement any methods.
Specifying methods implemented will force objects that have been set to this type to implement those methods!
Arg 4: Dependent Planes
This argument specifies which Control Planes the plugin depends on. It can be:
any
ornever
, which specifies that this plugin does not depend on any Control PlanesA single string (such as used in the example above), which specifies that this plugin depends on a single Control Plane
A union of strings (e.g.
'store' | 'read'
), which specifies that this plugin depends on multiple Control Planes
This argument defaults to never
, specifying that this plugin does not depend on any Control Planes.
Specifying one or more Dependent Control Planes will add those planes to the implicit LearnCard passed into each plugin method!
Specifying Dependent Planes here will not force LearnCards to implement those planes when adding this plugin! Please see Depending on Plugins for more information.
Arg 5: Dependent Methods
This argument specifies which methods the plugin depends on. It is an object whose values are functions.
This argument defaults to Record<never, never>
, specifying that this plugin does not depend on any methods.
Specifying dependent methods will add those methods to the implicit LearnCard passed into each plugin method!
Specifying Dependent Methods here will not force LearnCards
to implement those methods when adding this plugin! Please see Depending on Plugins for more information.
The LearnCard Type
To add better type safety to a project using LearnCards, it is highly recommended you use TypeScript and take advantage of the LearnCard
type.
How to use the LearnCard
type
LearnCard
typeOption 1: Specify a list of plugins
If you know the exact order and number of plugins you have, you can use the LearnCard
type to specify a LearnCard
with those plugins like so:
With this code, CustomLearnCard
will automatically infer all methods and planes implemented by PluginA
, PluginB
, and PluginC
!
Option 2: Specify implemented planes and/or methods
If you don't know the exact order and number of plugins you have, but you know that you would like to specify a LearnCard
that implements certain planes or methods, you can do that like this:
With this code, ImplementsIdPlane
will accept any LearnCard
that has plugins that implement the ID Control Plane, ImplementsFoo
will accept any LearnCard
that implements a method named foo
that returns 'bar'
, and ImplementsBoth
will accept any LearnCard
that implements both.
Implementing Control Planes
In order to promote convergence across Plugin APIs to support common functionality over complex workflows, plugins may choose to implement Control Planes. Because each plane is slightly different, how they are actually implemented will also be slightly different. For the most part, there are some standard conventions that plugins must follow when implementing methods for a plane.
To demonstrate this, let's build a plugin that implements the Read and Store planes using localStorage.
Types
Let's start with the types! We will use the Plugin
type to define a Plugin that implements the Read and Store planes like so:
Implementation
Skeleton
Before actually implementing the localStorage functionality, let's build out the skeleton of what our implementation will look like:
Because we specified that this plugin is implementing the read
and store
planes, we must include those keys in the returned Plugin
object. The Read Plane requires we implement a get
method, and the Store Plane requires we implement at least an upload
method, so these methods have been stubbed out.
Implementing the Store Plane
URI Scheme
Let's start by implementing the Store Plane! The Store Plane docs mention that the upload
method should return a URI
, so we will now devise a URI scheme. It looks like this:
Where id
is the identifier used as a key in localStorage
.
UUID
To easily create unique identifiers, we will use the uuid
npm package. Creating an ID using that package looks like this:
Putting it all together
With our URI scheme defined and ID generation in place, let's implement our Store Plane!
With this code in place, the Store Plane has successfully been implemented! Verifiable Credentials can now be stored by a LearnCard using this plugin with the following code:
Now the we can store credentials in localStorage, let's implement getting them out!
Implementing the Read Plane
To implement the Read Plane, we simply need to verify that the incoming URI
is one that matches our scheme, and if so, read the value stored in localStorage! This can be done with the following code:
With this in place, the Read and Store planes have been implemented and LearnCards may use our plugin to store and retrieve credentials from localStorage with the following code:
Implementing Methods
Sometimes plugins need to expose some bespoke logic that doesn't fit neatly into one of the Control Planes. Plugin methods allow plugins to expose this logic directly on the resulting LearnCard object.
We have already seen this in action in The Simplest Plugin, but let's go into a bit more depth about what's happening here by making a quick plugin that implements a basic counter.
Types
Before implementing methods on a Plugin object, it's best to get the types in order. In general, starting with the types can be easier to think through, and once they're in place, they can guide the implementation. To add types for methods, we use the third generic argument of the Plugin
type.
The types above have defined a Plugin with three methods: get
, increment
, and reset
, which will provide basic counter controls.
Implementation
Skeleton
With the above types in place, we can build out a skeleton plugin before actually implementing anything:
Implementing the Methods
With our boilerplate out of the way, implementing the Counter plugin will be a cakewalk! 🍰
We will use the lexical scope of the getCounterPlugin
function to store out counter state, and manipulate it via the exposed methods.
Our plugin is now complete, and we have successfully implemented bespoke method. Easy as 🍰.
Our plugin can be added to and used in a LearnCard like so:
The Implicit LearnCard Object
What is it?
When implementing Control Planes or Methods, you may have noticed an unused _learnCard
parameter get added. This is what we call the Implicit LearnCard, and it can be very helpful!
What does it do?
The Implicit LearnCard allows your Plugin's methods to access an up-to-date version of the LearnCard that it has been added to. This means that you can have access to a full LearnCard without having to wrap your Plugin in a function!
Why would you use it?
There are a few use-cases for using the Implicit LearnCard, such as:
Calling a method that is implemented in the same plugin
Ensuring the most up-to-date method is called
Let's implement a quick plugin that generates names to demonstrate this. The plugin will expose three methods: generateFirstName
, generateLastName
, and generateFullName
. The types for this plugin look like this (using the Plugin
type):
The implementation for this plugin can have generateFullName
easily call generateFirstName
and generateLastName
without having to define them outside of the function thanks to the Implicit LearnCard:
While this example may be a bit contrived, it does demonstrate a few important benefits of the Implicit LearnCard:
We were able to reuse plugin methods without defining them outside the plugin
Other plugins are now able to override the functionality of
generateFirstName
andgenerateLastName
andgenerateFullName
will automatically call the overriden methods!This allows plugins to easily define interfaces for sub-plugins or plugin extensions.
This also gives plugins the ability to monkey-patch pieces of another plugin, enhancing or changing that earlier plugin's functionality
When would you not use it?
Privacy
The Implicit LearnCard can be very handy for plugin extensibility and composition. However, there are times you don't want a plugin to be able to be monkey-patched or extended. In such a case, it is a better idea not to use the Implicit LearnCard, and just define the re-usable functions outside the plugin:
Plugin Extensions
Another reason not to use the Implicit LearnCard is when you specifically want an old version of a method you are overriding. To demonstrate this, let's build a quick Verification Extension
Types
Building a Verification Extension is super easy with the VerifyExtension
type coming from the VC Plugin:
The VerifyExtension
type defines one method: verifyCredential
that takes in a Verifiable Credential and returns a VerificationCheck
object. To add our extension, we depend on a LearnCard that already has the verifyCredential
function (using the same VerifyExtension
type!), then call the old verifyCredential
function at the top of our new verifyCredential
function.
This pattern allows any number of plugins to add extra verification logic to the verifyCredential
function easily!
Depending On Plugins
While it is useful for The Simplest Plugin to add its own isolated logic to a LearnCard, part of the beauty of LearnCard plugins is to depend on other plugins 💪
Plugin dependence comes in two flavors:
Depending on a Control Plane
Depending on one or more methods.
Boilerplate Plugins
To demonstrate this, let's create a simple base plugin, as well as two plugins that we will depend on: one for Control Planes, and one for methods.
Base Plugin
Control Plane Plugin
Methods Plugin
Dependence Convention
As a convention, plugins will often be wrapped inside of a constructor function that requires a LearnCard of a certain type be passed in.
Let's update our base plugin to see what that looks like:
With this change, LearnCards that would like to add our plugin will now look slightly different:
Adding dependency requirements
Depending on Planes
Now that we're requiring a LearnCard be passed in, we are able to add requirements to the dependent LearnCard! Let's take a look at what that looks like by attempting to take a dependency on the Control Plane Plugin.
The operative change is right here on line 4:
This change allows us to call learnCard.id.did
on line 5, and requires consumers of this plugin to pass in a LearnCard that implements the ID plane when adding this plugin. For example, all of the following code will throw errors:
Depending on Methods
Depending on a specific method (or methods) rather than a Control Plane looks very similar. To demonstrate this, let's stop depending on the ID Plane for a moment, and instead just depend on the foo
method from the Methods Plugin.
The operative change is, once again, on line 4:
With this change in place, just like when we depended on a Control Plane, we are now able to call learnCard.invoke.foo
on line 5. We also now require consumers of this plugin to pass in a LearnCard with a plugin that implements the foo
method. For example, all of the following code will throw errors:
Adding Type Safety to The Implicit LearnCard
Thus far, when adding dependency requirements, we have only added type safety to the argument of our constructor function. This works quite well, but does not provide type safety to the Implicit LearnCard passed into every method. To add this type safety, we use the fourth and fifth generic arguments of the Plugin
type
Let's demonstrate this by first depending on both the Control Plane Plugin and the Methods Plugin, then adding some logic that uses the Implicit LearnCard to take advantage of that dependency:
Because we haven't added our dependencies to the DependencePlugin
type itself, TS has no way of knowing that the Implicit LearnCard implements the ID Plane and the foo
method! We can easily fix this by updating the DependencePlugin
type:
With these in place, TS will know to add the ID Plane and the foo
method to the Implicit LearnCard, and the above errors will go away!
Private Fields
Sometimes it is important for a Plugin to keep private state/data. This can be done using the lexical scope of the constructor function described in Depending on Plugins!
To demonstrate this, let's build a quick secret message plugin that gates a string behind a password. This plugin will use a constructor function that takes in a message and a password, exposing a getMessage
method that will return the message if the correct password is passed in and changePassword
/changeMessage
methods that allow updating the password/message.
This plugin can be used like so:
Last updated
Was this helpful?