npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cpn-console/hooks

v2.4.0

Published

## Présentation

Downloads

246

Readme

Plugin manager

Présentation

Le plugin manager ou communément appelés "hooks" est une interface permettant au serveur d'api (apps/server) de déclencher des hooks "déclencheurs / événements" pour que les plugins puissent piloter et configurer des outils.

Les plugins obligatoires de l'application sont souvent appelés corePlugins.

L'objectif est que le server soit totalement agnostique des actions des plugins et même de leur existence. De plus le serveur d'API essayera au démarrage de charger des plugins dans un dossier spécifique, on les distingue en les appelant externalPlugins.

Développement de plugin

Cette section est dédiée à tous ceux voulant améliorer les corePlugins ou en créer de nouveaux.

Pour commencer je vous conseille d'aller regarder le plugin vault

Interface

Tout paquet existant devrait avoir un export nommé plugin et de type Plugin.

Ce typage vous assurera de bien fournir toutes les clés nécessaires au bon fonctionnement du plugin :

// index.ts
import type { Plugin } from '@cpn-console/hooks'
import infos from './infos.js'
import monitor from './monitor.js'

export const plugin: Plugin = {
  infos,
  subscribedHooks: {},
  monitor,
}

Infos

Ce sont les infos de bases de votre application :

// infos.ts
import type { ServiceInfos } from '@cpn-console/hooks'

const infos: ServiceInfos = {
  name: 'my_plugin', // il serait bien que ça ne change jamais, imaginez que c'est un identifiant unique.
  to: ({ project, organization }) => `${process.env.MON_URL}/${organization}/${project}`,
  title: 'Mon super plugin',
  imgSrc: 'https://un_lien_vers/image_externe.svg/', // préférez le svg
  description: 'La description générale de mon plugin',
}

export default infos

La fonction to peux renvoyer une String ou un objet contenant une clé to et potentiellement d'autres informations ou encore un tableau de cet objet :

// Valid
const to1 = () => 'une url'
const to2 = () => ({ to: 'url', title: 'Un titre', description: 'description', imgSrc: 'url' })
function to3() {
  return [
    { to: 'url générale', title: 'Service générale', description: 'description générale' },
    { to: 'url1', title: 'Service 1', description: 'description 1' },
    { to: 'url2', title: 'Service 2', description: 'description 2' },
  ]
}
// Invalid
const to3 = () => ['url', 'url1', 'url2']

Monitoring

Pour offrir un (et un seul) service de monitoring sommaire de l'outil que le plugin manipule, vous pouvez créer une instance de la classe Monitor et l'initialiser avec une fonction dont vous aurez seul la maîtrise. Cette fonction sera éxécutée par un setInterval toutes les 5 min ou selon le temps en miliseconds que vous aurez fourni :

// monitor.ts
import { Monitor, type MonitorInfos, MonitorStatus } from '@cpn-console/shared'

async function monitor(instance: Monitor): Promise<MonitorInfos> {
  instance.lastStatus.lastUpdateTimestamp = (new Date()).getTime()
  // Votre fonction ne devrait jamais lever d'exception
  try {
    // faites des trucs
    // mettez à jour les clés en fonction de votre résultat:
    // instance.lastStatus.message = 'Tout va bien'
    // instance.lastStatus.status = MonitorStatus.OK
  } catch (error) {
    instance.lastStatus.message = 'Error lors la requete'
    instance.lastStatus.status = MonitorStatus.UNKNOW
    // la clé cause n'est pour l'instant jamais retourné à l'utilisateur ni stocké, ça sera pour une prochaine PR
    instance.lastStatus.cause = error
  }
  // c'est bien de le retourner mais on s'en fiche un peu
  return instance.lastStatus
}

export default new Monitor(monitor)

SubscribedHooks

Pour informer le plugin manager sur quels hooks vous voulez exécuter une fonction, vous devez fournir un objet qui aura cette structure :

  subscribedHooks: {
    createProject: {
      steps: {
        pre: createDsoProjectFirst,
        post: createDsoProjectLast,
      },
    },
    archiveProject: {
      steps: { post: archiveDsoProject },
    },
  },

Fonctions (StepCalls)

Pensez à typer vos fonctions comme suit pour que TS soit capable de vous notifier si vous avez associé un mauvais payload à un hook.

Vos fonctions ne doivent pas non plus lever d'exceptions non gérées. Et elles doivent toutes retourner un statut !

Vous pouvez aussi retourner des clés supplémentaires. Ces clés sont accessibles par tous les plugins dans payload.results[nom_du_plugin].

Attention, chaque fonction écrase le résultat de la step d'avant :

export const createDsoProjectFirst: StepCall<CreateProjectExecArgs> = async (payload) => {
  try {
    // fais des trucs
    // oh j'ai un payload typé !
    return {
      status: {
        result: 'OK',
        message: 'ça s\'est bien passé' // optionnel si OK
      },
      foo: {
        bar: 'complétement facultatif'
      }
    }
  } catch (error) {
    return {
      status: {
        result: 'KO',
        message: 'Ouille !' // Obligatoire pour explique ce qui n'a pas réussi'
      },
    }
  }
}

export const createDsoProjectLast: StepCall<CreateProjectExecArgs> = async (payload) => {
  try {
    // fais des trucs
    // oh j'ai un payload typé !
    return {
      status: {
        result: 'OK',
        message: `${payload.results.my_plugin.foo.bar} a vraiment bien été crée` // optionnel si OK
      },
      une_clé: {
        newProjectName: 'complétement facultatif'
      }
    }
  } catch (error) {
    return {
      status: {
        result: 'KO',
        message: 'Ouille !' // Obligatoire pour explique ce qui n'a pas réussi'
      },
    }
  }
}

Apis

Il n'existe pour l'instant qu'un seul plugin qui exporte une API pour les autres plugins, c'est Vault ! D'autres viendront aider à la séparation des responsabiltés plus tard, stay tuned !

Vous pouvez, comme lui, déclarer des apis sur des hooks. Pour l'uniformité, déclarez que c'est un extends de PluginApi (qui est vide) :

// api.ts
import { PluginApi } from '@cpn-console/hooks'

export class ClusterApi extends PluginApi {}
// index.ts
  subscribedHooks: {
    createCluster: {
      api: (args) => new ClusterApi(args.label, args.privacy),
      steps: { post: createCluster },
    },
    deleteCluster: {
      api: (args) => new ClusterApi(args.label),
    },
  },

Dépendances entre les plugins

La section précédente est bien sympathique mais en l'état, le plugin n'a aucune conscience des apis des autres plugins.

Disclaimer: Les plugins peuvent se parler entre eux mais attention ils ne s'importent jamais les uns les autres. C'est le Plugin Manager qui est en charge de fournir tous les objets nécessaires et de faire passe plat.

Pour y arriver il va falloir deux étapes

  1. Le plugin qui expose l'api doit faire un declare module :
// index.ts
declare module '@cpn-console/hooks' {
  interface HookPayloadApis<Args extends DefaultArgs> {
    vault: Args extends CreateClusterExecArgs | DeleteClusterExecArgs
      ? ClusterApi
      : undefined
  }
}
  1. Le module l'utilisant doit importer les types :
// package.json
{
  "devDepencies": {
    "my_plugin": "1.2.3"
  }
}
// src/env.d.ts
/// <reference types="my_plugin/types/index.d.ts" />

Conclusion

N'hésitez pas à ouvrir des issues si ce n'est pas clair et bon développement !