LearnCard Developer Docs
  • 🚀Get Started
    • 👋Welcome
    • ⭐Who are you?
      • Learners & Employees
      • Traditional Educator
      • Non-Traditional Educator
      • Assessment Provider
      • Employer
      • App Developer & EdTech
      • DAO & Communities
      • Content Creators
      • Research Institutions
      • NGOs & Governments
      • Plugfest Partner
        • Guide for Interop Issuers
          • 🤽Creating an Interop Issuer
        • Guide for Interop Wallets
    • Protocol Overview
      • The Internet of Education
      • The Learning Economy
      • Learner & Employee Privacy
      • 22nd Century Education
      • The Open Credential Network
      • PVCs
  • 🔰LearnCard SDK
    • What is LearnCard?
      • Why a Universal Wallet?
      • Architectural Patterns
      • Production Deployment Guide
      • Troubleshooting Guide
    • LearnCard Core
      • Quick Start
        • Create New Credentials
          • Creating Verifiable Credentials for LearnCard
          • Achievement Types and Categories
          • Custom Types
          • Understanding Boosts
          • Creating Boost Credentials
        • Sign & Send Credentials
        • Accept & Verify Credentials
        • Share & Present Credentials
      • Construction
        • Managing Seed Phrases
        • initLearnCard
        • DIDKit
        • learnCardFromSeed
        • emptyLearnCard
        • IDX Config
      • Control Planes
        • ID
        • Read
        • Store
        • Index
        • Cache
        • Context
      • Plugins
        • Adding Plugins
        • Official Plugins
          • Dynamic Loader
          • Crypto
          • DIDKit
          • DID Key
          • VC
            • Expiration Sub-Plugin
          • VC Resolution
          • VC-Templates
          • VC-API
          • Ceramic
          • IDX
          • VPQR
          • Ethereum
          • CHAPI
          • LearnCard Network
          • LearnCloud
          • LearnCard
          • Claimable Boosts
        • Writing Plugins
          • The Simplest Plugin
          • The Plugin Type
          • The LearnCard Type
          • Implementing Control Planes
          • Implementing Methods
          • The Implicit LearnCard
          • Depending on Plugins
          • Private Fields
          • Publishing a Plugin to NPM
      • URIs
      • CHAPI
        • ⭐CHAPI Wallet Setup Guide
        • ↔️Translating to CHAPI documentation
        • 🖥️Demo Application
        • 🔰Using LearnCard to Interact with a CHAPI Wallet
        • 📝Cheat Sheets
          • Issuers
          • Wallets
      • LearnCard UX
        • Quick Start
        • Components
          • Verifiable Credentials
            • VC Thumbnail
            • VC Thumbnail, Mini
          • LearnCards
            • LearnCard Front
            • LearnCard Back
        • API
      • LearnCard Bridge
      • API
      • Migration Guide
    • LearnCard Network
      • LearnCard Network API
        • Authentication
        • Auth Grants and API Tokens
        • Profile
        • Credentials
        • Boosts
        • Presentations
        • Storage
        • Signing Authorities
        • Notifications
        • API Docs
        • Launch Your Own Network
      • 🔌Connect Your Application
    • ConsentFlow
      • Setting Up ConsentFlow with an Independent Network
    • GameFlow
      • Sending xAPI Statements
        • xAPI URIs
      • Reading xAPI Statements
        • Advanced xAPI Statement Queries
      • Consentful "Claim Later" Flow
  • 🚀Applications
    • LearnCard
    • SuperSkills!
      • SuperSkills! SDK
        • Digital Wallets
        • Issuing into SuperSkills!
        • 🦸Creating a SuperSkills! Issuer
    • Metaversity
    • Admin Dashboard
  • 🔗Resources
    • Github
    • Community
    • 💅Custom Development
    • Contact Our Team
    • Learning Economy
  • 🤖LearnCard Services
    • LearnCard CLI
    • Discord Bot
    • Metamask Snap
  • 💸LearnBank SDK
    • Why LearnBank?
  • 📊LearnGraph SDK
    • Why LearnGraph?
Powered by GitBook
On this page
  • Boilerplate Plugins
  • Base Plugin
  • Control Plane Plugin
  • Methods Plugin
  • Dependence Convention
  • Adding dependency requirements
  • Depending on Planes
  • Depending on Methods
  • Adding Type Safety to The Implicit LearnCard

Was this helpful?

  1. LearnCard SDK
  2. LearnCard Core
  3. Plugins
  4. Writing Plugins

Depending on Plugins

Standing on the Shoulders of Giants

PreviousThe Implicit LearnCardNextPrivate Fields

Last updated 2 years ago

Was this helpful?

While it is useful for 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

  • Depending on one or more .

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

src/dependence/types.ts
import { Plugin } from '@learncard/core';

export type DependencePluginType = Plugin<'Dependence', any, { bar: () => 'baz' }>;
src/dependence/index.ts
import { DependencePluginType } from './types';

export const DependencePlugin: DependencePluginType = {
    name: 'Dependence',
    methods: { bar: () => 'baz' },
};

Control Plane Plugin

src/controlplane/types.ts
import { Plugin } from '@learncard/core';

export type ControlPlanePluginType = Plugin<'Control Plane', 'id'>;
src/controlplane/index.ts
import { ControlPlanePluginType } from './types';

export const ControlPlanePlugin: ControlPlanePluginType = {
    name: 'Control Plane',
    id: {
        did: () => { throw new Error('TODO'); },
        keypair: () => { throw new Error('TODO'); },
    },
    methods: {},
}

Methods Plugin

src/methods/types.ts
import { Plugin } from '@learncard/core';

export type MethodsPluginMethods = {
    foo: () => 'bar';
};

export type MethodsPluginType = Plugin<'Methods', any, MethodsPluginMethods>;
src/methods/index.ts
import { MethodsPluginType } from './types';

export const MethodsPlugin: MethodsPluginType = {
    name: 'Methods',
    methods: { foo: () => 'bar' },
}

Dependence Convention

Let's update our base plugin to see what that looks like:

src/dependence/types.ts
import { Plugin } from '@learncard/core';

export type DependencePlugin = Plugin<'Dependence', any, { bar: () => 'baz' }>;
src/dependence/index.ts
import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

export const getDependencePlugin: (learnCard: LearnCard<any>): DependencePluginType => ({
    name: 'Dependence',
    methods: { bar: () => 'baz' },
});

With this change, LearnCards that would like to add our plugin will now look slightly different:

// Old
const withPlugin = await learnCard.addPlugin(DependencePlugin);

// New
const withPlugin = await learnCard.addPlugin(getDependencePlugin(learnCard));

Adding dependency requirements

Depending on Planes

Note: The LearnCard SDK does not support depending on literal plugins. It only supports depending on what plugins implement. In practice, this makes plugins much more flexible and easy to work with, allowing dependent plugins to be hot-swapped easily as long as they implement the same dependent methods/planes.

src/dependence/index.ts
import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

export const getDependencePlugin: (learnCard: LearnCard<any, 'id'>): DependencePluginType => {
    console.log('Successfully depended on a Control Plane!', learnCard.id.did());
    
    return {
        name: 'Dependence',
        methods: { bar: () => 'baz' },
    };
};

The operative change is right here on line 4:

//                                                           VVVV
export const getDependencePlugin: (learnCard: LearnCard<any, 'id'>): DependencePluginType => {
//                                                           ^^^^
import { initLearnCard } from '@learncard/core';

const learnCard = await initLearnCard({ custom: true });

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'id' is missing
import { initLearnCard } from '@learncard/core';

const learnCard = await initLearnCard();

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'id' is missing
// --snip--
export const getDependencePlugin: (learnCard: LearnCard<any>): DependencePluginType => {
    console.log(learnCard.id.did());
    // TS Error: Property 'id' does not exist

Depending on Methods

src/dependence/index.ts
import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

export const getDependencePlugin: (learnCard: LearnCard<any, any, { foo: () => 'bar' }>): DependencePluginType => {
    console.log('Successfully depended on a Method!', learnCard.invoke.foo());
    
    return {
        name: 'Dependence',
        methods: { bar: () => 'baz' },
    };
};

The operative change is, once again, on line 4:

//                                                                VVVVVVVVVVVVVVVVVVVV
export const getDependencePlugin: (learnCard: LearnCard<any, any, { foo: () => 'bar' }>): DependencePluginType => {
//                                                                ^^^^^^^^^^^^^^^^^^^^
import { initLearnCard } from '@learncard/core';

const learnCard = await initLearnCard({ custom: true });

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'foo' is missing
import { initLearnCard } from '@learncard/core';

const learnCard = await initLearnCard();

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'foo' is missing
// --snip--
export const getDependencePlugin: (learnCard: LearnCard<any>): DependencePluginType => {
    console.log(learnCard.invoke.foo());
    // TS Error: Property 'foo' does not exist

Adding Type Safety to The Implicit LearnCard

src/dependence/index.ts
import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

export const getDependencePlugin: (learnCard: LearnCard<any, 'id', { foo: () => 'bar' }>): DependencePluginType => {
    return {
        name: 'Dependence',
        methods: { 
            bar: _learnCard => {
                 // these two calls with throw TS errors!
                 console.log('Did is:', _learnCard.id.did());
                 console.log('Foo is:', _learnCard.invoke.foo());
            
                return 'baz';
            } 
        },
    };
};

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:

src/dependence/types.ts
import { Plugin } from '@learncard/core';

//                                                                             VVVVVVVVVVVVVVVVVVVVVVVVVV
export type DependencePlugin = Plugin<'Dependence', any, { bar: () => 'baz' }, 'id', { foo: () => 'bar' }>;
//                                                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^

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!

As a convention, plugins will often be wrapped inside of a constructor function that requires a of a certain type be passed in.

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 .

This change allows us to call learnCard.id.did on line 5, and requires consumers of this plugin to pass in a LearnCard that the plane when adding this plugin. For example, all of the following code will throw errors:

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 .

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 the foo method. For example, all of the following code will throw errors:

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 passed into every method. To add this type safety, we use the fourth and fifth generic arguments of

Let's demonstrate this by first depending on both the and the , then adding some logic that uses the Implicit LearnCard to take advantage of that dependency:

🔰
The Simplest Plugin
Control Plane
methods
LearnCard
implements
ID
implements
Implicit LearnCard
the Plugin type
Control Plane Plugin
Methods Plugin
Control Plane Plugin
Methods Plugin