Skip to content

Events and Webhooks utilities and documentation

License

Notifications You must be signed in to change notification settings

MyUnisoft/events

Repository files navigation

Events

MyUnisoft Events validator, schemas and types (useful to work with Webhooks).

npm version license size

🚧 Requirements

  • Node.js version 18 or higher
  • Docker (for running tests).

πŸš€ Getting Started

This package is available in the Node Package Repository and can be easily installed with npm or yarn

$ npm i @myunisoft/events
# or
$ yarn add @myunisoft/events
Configure environment variables
variable description default
MYUNISOFT_EVENTS_LOGGER_MODE Set log level for the default logger info
Dispatcher
MYUNISOFT_DISPATCHER_IDLE_TIME Interval threshold when Dispatcher become idle 600_000
MYUNISOFT_DISPATCHER_CHECK_LAST_ACTIVITY_INTERVAL Dispatcher checking last activity interval 120_000
MYUNISOFT_DISPATCHER_BACKUP_TRANSACTION_STORE_NAME Default name for backup transaction store backup
MYUNISOFT_DISPATCHER_INIT_TIMEOUT Dispatcher initialisation timeout 3_500
MYUNISOFT_DISPATCHER_PING_INTERVAL Dispatcher ping interval 3_500
Incomer
MYUNISOFT_INCOMER_INIT_TIMEOUT Incomer initialisation timeout 3_500
MYUNISOFT_EVENTS_INIT_EXTERNAL Whenever Incomer should initialize an external Dispatcher false
MYUNISOFT_INCOMER_MAX_PING_INTERVAL Maximum ping interval 60_000
MYUNISOFT_INCOMER_PUBLISH_INTERVAL Publish interval 60_000
MYUNISOFT_INCOMER_IS_DISPATCHER Weither Incomer is a Dispatcher false

Some options takes the lead over environment variables. For instance with: new Incomer({ dispatcherInactivityOptions: { maxPingInterval: 900_000 }}) the max ping interval will be 900_000 even if MYUNISOFT_INCOMER_MAX_PING_INTERVAL variable is set.

πŸ“š Usage example

import * as Events, { type EventOptions } from "@myunisoft/events";

const event: EventOptions<"connector"> = {
  name: "connector",
  operation: "CREATE",
  scope: {
    schemaId: 1
  },
  metadata: {
    agent: "Node",
    origin: {
      endpoint: "http://localhost:12080/api/v1/my-custom-feature",
      method: "POST",
      requestId: crypto.randomUUID();
    },
    createdAt: Date.now()
  },
  data: {
    id: 1,
    code: "JFAC"
  }
};

Events.validate(event);

You can also use additional APIs to validate and narrow the data type depending on the operation:

if (Events.isCreateOperation(event.operation)) {
  // Do some code
}
else if (Events.isUpdateOperation(event.operation)) {
  // Do some code
}
else if (Events.isDeleteOperation(event.operation)) {
  // Do some code
}

Note

πŸ‘€ See here for the exhaustive list of Events.

πŸ’‘ What is an Event?

A fully constituted event is composed of a name, an operation, and multiple objects such as data, scope and metadata.

  • The name identifies the event.
  • The operation defines if it is a creation, update, or deletion.
  • Based on the name, we know the data and the different metadata.origin.method related to it.
  • The metadata object is used to determine various pieces of information, such as the entry point.
  • The scope defines the who.
export interface Scope {
  schemaId: number;
  firmId?: number | null;
  firmSIRET?: number | null;
  accountingFolderId?: number | null;
  accountingFolderSIRET?: number | null;
  accountingFolderRef?: string | null;
  persPhysiqueId?: number | null;
}

export interface Metadata {
  agent: string;
  origin?: {
    endpoint: string;
    method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | (string & {});
    requestId?: string;
  };
  createdAt: number;
}

API

validate< T extends keyof Events >(options: EventOptions): void

Throw an error if a given event is not recognized internally.

isCreateOperation< T extends keyof Events >(operation: EventOptions["operation"]): operation is Operation["create"]

isUpdateOperation< T extends keyof Events >(operation: EventOptions["operation"]): operation is Operation["update"]

isDeleteOperation< T extends keyof Events >(operation: EventOptions["operation"]): operation is Operation["delete"]


EventOptions is described by the following type:

export type EventOptions<K extends keyof EventsDefinition.Events> = {
  scope: Scope;
  metadata: Metadata;
} & EventsDefinition.Events[K];

Exploiting Webhooks

πŸ‘€ See the root example/fastify for an example of utilizing webhooks with an HTTP server.

In TypeScript, webhooks can be described using the WebhookResponse type:

import type { WebhookResponse } from "@myunisoft/events";

const response: WebhooksResponse<["connector", "accountingFolder"]> = [
  {
    name: "connector",
    operation: "CREATE",
    scope: {
      schemaId: 1
    },
    data: {
      id: 1,
      code: "JFAC"
    },
    webhookId: "1",
    createdAt: Date.now()
  },
  {
    name: "accountingFolder",
    operation: "CREATE",
    scope: {
      schemaId: 1
    },
    data: {
      id: 1
    },
    webhookId: "2",
    createdAt: Date.now()
  },
];
Webhook JSON Schema
{
  "description": "Webhook",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "scope": {
        "$ref": "Scope"
      },
      "webhookId": {
        "type": "string"
      },
      "createdAt": {
        "type": "number"
      },
      "name": {
        "type": "string",
        "description": "event related name"
      },
      "operation": {
        "type": "string",
        "description": "event related operation",
        "enum": ["CREATE", "UPDATE", "DELETE", "VOID"]
      },
      "data": {
        "type": "object",
        "description": "event related data",
        "properties": {
          "id": {
            "type": "string"
          },
          "required": ["id"],
          "additionalProperties": true
        }
      }
    },
    "required": ["scope", "webhookId", "createdAt", "name", "operation", "data"],
    "additionalProperties": false
  }
}

Contributors ✨

All Contributors

Thanks goes to these wonderful people (emoji key):

Nicolas Hallaert
Nicolas Hallaert

πŸ’» πŸ“– ⚠️
Yefis
Yefis

πŸ’» πŸ“–
Gentilhomme
Gentilhomme

πŸ“–
PierreDemailly
PierreDemailly

πŸ’» πŸ“–

License

MIT