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

@graphand/core

v1.4.9

Published

Cette librairie contient les classes et fonctions de base communes au client et au serveur de Graphand. Voici les concepts de base de cette librairie :

Downloads

1,029

Readme

@graphand/core

Cette librairie contient les classes et fonctions de base communes au client et au serveur de Graphand. Voici les concepts de base de cette librairie :

Modèles : classe Model

@graphand/core exporte les modèles utilisées dans Graphand, leurs champs ainsi que les validateurs de chacun. Chaque modèle (src/models/*.ts) est une classe qui étend la classe de base Model (qui contient elle même les méthodes de base nécessaires au fonctionnement de core telles que les actions de crud, getters, setters, etc.) Pour être utilisés correctement, les modèles ont besoin d'un adaptateur (classe Adapter) qui définit la manière dont le modèle interagit avec les données dans son contexte (fonctionnement différent sur le client et sur le serveur).

La définition d'un modèle (type ModelDefinition) est définie par le champ Model.definition et contient les attributs suivants :

  • keyField: le champ qui sert de clé primaire pour ce modèle (en plus de l'attribut _id qui est toujours présent)
  • fields: les champs de ce modèle (type FieldsDefinition)
  • validators: les validateurs de ce modèle (type ValidatorsDefinition)
  • single: si le modèle est un singleton (un seul élément de ce modèle peut exister)

Si le modèle est extensible (Model.extensible), Graphand cherchera un datamodel ayant le même slug pour lui associer sa définition lors de l'initialisation du modèle (Model.initialize). Les modèles extensibles sont Account, Media et Data. Il est donc possible de créer un datamodel ayant le slug accounts pour étendre le modèle Account et ajouter des champs à ce modèle. Pour créer un modèle custom, il faut créer un datamodel et définir une classe qui étend le modèle de base Data avec le slug du datamodel.

Exemple

await DataModel.create({
  slug: "list",
  definition: {
    keyField: "title",
    fields: {
      title: {
        type: FieldTypes.TEXT,
      },
      description: {
        type: FieldTypes.TEXT,
      },
    },
    validators: [
      {
        type: ValidatorTypes.REQUIRED,
        options: {
          field: "title",
        },
      },
    ],
  },
});

class ListModel extends Data {
  static slug = "list";
}

await ListModel.initialize(); // Charge la définition du datamodel "list" et l'associe au modèle

console.log(ListModel.fieldsMap.has("title")); // true

Champs

Les champs du modèle sont définis dans Model.definition.fields ... TODO

Validateurs

Les validateurs du modèle sont définis dans Model.definition.validators ... TODO

Scope du modèle

Chaque modèle est associé à un scope (global, project ou env). Le scope global est utilisé pour les modèles accessibles globalement dans graphand (User, Project, Organization, etc.) Les autres scopes sont liés à un projet: le scope project est utilisé pour les modèles accessibles sans différenciation dans tous les environnements du projet (Key, Media, etc.) alors que le scope env est utilisé pour les modèles déclinés en fonction de l'environnement (Account, DataModel, etc.). Ainsi, les médias (modèle Media) sont accessibles dans tout le projet, sans différenciation d'un environnement à l'autre (si un média est modifié ou supprimé, il le sera sur tous les environnements) alors que les comptes (modèle Account) seront liés à un environnement (si un compte est modifié ou supprimé sur un environnement, il ne sera pas modifié sur les autres et inversement).

Adaptateur : classe Adapter

Le rôle de cette librairie est donc de fixer les bases de la structure de Graphand. Ensuite, les actions dépendantes du contexte (serveur/client) doivent être paramétrées pour que core fonctionne correctement. Par exemple, le serveur lit et écrit dans une base de données, tandis que le client émet des appels HTTP vers le serveur pour y récupérer les données ou y effectuer des opérations de lecture/écriture.

C'est donc le rôle de l'adaptateur : une classe qui étend la classe Adapter et qui sert de paramétrage à core pour savoir comment interagir avec les données dans le contexte courant. Pour chaque modèle, core créé une instance de cette classe. Chaque instance de l'adaptateur a donc accès au modèle en question via l'attribut Adapter.prototype.model.

Pour fonctionner avec un adaptateur, les modèles doivent être appelé avec la méthode Model.extend, qui prend en paramètre la classe de l'adaptateur qui sera instanciée. C'est cette fonction qui est appelée under the hood par le client avec la méthode Client.prototype.getModel et par le serveur avec la méthode Controller.prototype.getModel (avec leurs adaptateurs respectifs).

class ClientAdapter extends Adapter {} // ClientAdapter décrit comment les modèles interagissent avec les données sur le client

const AccountModel = Account.extend({ adapterClass: ClientAdapter }); // maintenant AccountModel sait comment lire/écrire des données et est utilisable

AccountModel.getList("..."); // exécute la méthode getList de l'adaptateur ClientAdapter

Si un modèle n'est pas étendu avec la méthode Model.extend, un adapteur sera automatiquement instancié à partir de la classe Model.adapterClass du modèle en question. Par exemple, le code ci-dessus peut-être réécrit de la manière suivante :

class CustomAdapter extends Adapter {}

class CustomClass extends Data {
  static slug = "example";
  static adapterClass = CustomAdapter;
}

CustomClass.getList("...");

adapterClass est hérité par les classes enfants. Il est donc possible de définir un adapteur global à l'environnement avec Model.adapterClass = CustomAdapter. Ainsi, tous les modèles existants et futurs qiu héritent de Model utiliseront l'adaptateur CustomAdapter par défaut.

Voici les méthodes et attributs que l'adaptateur permet de définir :

Adapter.prototype.fetcher

fetcher est un object contenant plusieurs fonctions qui correspondent aux actions suivantes :

  • count : compte le nombre d'éléments de ce modèle
  • get : récupère un élément de ce modèle
  • getList : récupère une liste d'éléments de ce modèle
  • createOne : crée un élément de ce modèle
  • createMultiple : crée plusieurs éléments de ce modèle
  • updateOne : met à jour un élément de ce modèle
  • updateMultiple : met à jour plusieurs éléments de ce modèle
  • deleteOne : supprime un élément de ce modèle
  • deleteMultiple : supprime plusieurs éléments de ce modèle
  • getModelDefinition : récupère les informations sur ce modèle (champs, validateurs, etc.)

Chacune de ces fonctions sera appelée par le modèle via la méthode execute. L'appel de celle-ci exécutera les hooks before et after correspondants à l'action en question du fetcher. Par exemple, Model.get utilise la méthode Model.execute('get', ...args) qui exécutera la fonction get dans adapter.fetcher ainsi que les hooks before et after de l'action get.

Exemple

Model.hook("before", "get", function () {
  // sera appelé avant l'appel de la méthode get du fetcher
});

Model.hook("after", "get", function () {
  // sera appelé après l'appel de la méthode get du fetcher
});

const AdaptedModel = Model.extend({ adapterClass: MyAdapter }); // nécessaire pour que les actions de crud fonctionnent dans le contexte (= client.getClosestModel(Model) sur le client et context.getClosestModel(Model) sur le serveur)

AdaptedModel.get("..."); // exécute la methode get du fetcher de "MyAdapter" ainsi que les hooks du modèle

Ces hooks sont appelés avec les paramètres de la fonction en question et peuvent les modifier. En théorie, ces hooks peuvent permettrent d'étendre le fonctionnement du fetcher et de couvrir tous les cas de figure à la manière d'un plugin.

Les hooks executés sont ceux du modèle concerné ainsi que ceux des modèles parents. Ainsi, lorsqu'un hook est ajouté à la classe Model, il sera executé sur n'importe quel modèle (Account, Project, etc.)

Le payload envoyé aux hooks inclus le contexte de l'exécution (type TransactionCtx) qui contient des informations utiles telles que :

  • retryToken: Si ce symbol est émis par l'un des hooks (throw ctx.retryToken), alors l'opération sera relancée après l'exécution des hooks de la phase en cours (before ou after)
  • abortToken: Si ce symbol est émis par l'un des hooks (throw ctx.abortToken), alors l'opération sera immédiatement stoppée. Même l'exécution des hooks de la phase en cours sera arretée contrairement au retryToken.

Adapter.prototype.fieldsMap

fieldsMap est un objet qui lie chaque type champ existant sur graphand à la classe de son type. Les types de champs sont tous définis par l'enum FieldTypes et sont les suivants :

  • FieldTypes.ID
  • FieldTypes.ARRAY
  • FieldTypes.TEXT
  • FieldTypes.NUMBER
  • FieldTypes.BOOLEAN
  • FieldTypes.RELATION
  • FieldTypes.DATE
  • FieldTypes.NESTED
  • FieldTypes.IDENTITY

Chaque champ est donc une classe qui étend la classe de base Field et qui décrit la manière dont le type de champ en question encode et décode les données dans le contexte courant. Par exemple le champ _id est de type différent sur le client et sur le serveur : string sur le client et ObjectId sur le serveur. Tous les types de champs existent déjà dans @graphand/core et l'adaptateur peut en surcharger seulement certaines si besoin.

Exemple

class CustomFieldText extends Field<FieldTypes.TEXT> {
  serialize(value: string) {
    return value.toUpperCase();
  }
}

MyAdapter.prototype.fieldsMap = {
  [FieldTypes.TEXT]: CustomFieldText,
};

Adapter.prototype.validatorsMap

De la même manière que pour les champs, les validateurs sont définis dans le validatorsMap. Les types de champs sont tous dans l'enum ValidatorTypes :

  • ValidatorTypes.REQUIRED
  • ValidatorTypes.UNIQUE
  • ValidatorTypes.BOUNDARIES
  • ValidatorTypes.LENGTH
  • ValidatorTypes.REGEX
  • ValidatorTypes.SAMPLE
  • ValidatorTypes.KEY_FIELD
  • ValidatorTypes.DATAMODEL_SLUG
  • ValidatorTypes.DATAMODEL_DEFINITION

Les validateurs DATAMODEL_SLUG et DATAMODEL_DEFINITION_ sont des validateurs spéciaux qui sont utilisés seulement par le modèle DataModel pour vérifier que les champs slug et definition sont valides.

Adapter.prototype.runWriteValidators

runWriteValidators permet d'activer ou de désactiver les validateurs sur les actions de crud dans le contexte. Même si les validateurs sont désactivés via cette variable, ils peuvent toujours être exécutés via la méthode Model.validate.

Exemple

Ici, le serveur exécue systématiquement les validateurs lorsqu'un élément est ajouté ou modifié (D'où ServerAdapter.prototype.runWriteValidators = true). En revanche, le client n'exécute pas les validateurs car c'est le serveur qui gère cette partie (ClientAdapter.prototype.runWriteValidators = false). Le client peut tout de même exécuter les validateurs si besoin (avant l'envoi d'un formulaire par exemple) avec la méthode Model.validate.

Controleurs

Les controleurs sont décrits dans le fichier controllersMap.ts. Chaque controleur est décrit par les attributs suivants :

  • path: le chemin de l'endpoint pour accéder au controleur. Les paramètres sont définis avec :nomDuParametre et peuvent être optionnels avec :nomDuParametre?
  • methods: les méthodes HTTP autorisées pour cet endpoint
  • secured: si true, l'accès à cet endpoint nécessite une authentification
  • scope: le scope de l'endpoint (global ou project). Si global, l'endpoint est accessible via l'instance globale de graphand. Si project, l'endpoint est accessible seulement sur une instance de projet. Si scope est une fonction, celle-ci sera appelée avec le modèle de la requête en paramètre et doit retourner global ou project en fonction du scope du modèle.