Skip to content

kravetsone/webhook-openapi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

webhook-openapi

npm JSR JSR Score

This library is aimed at helping to implement a Webhook server.

Warning

This project is in the MVP state and the API may still change a lot. At the moment, the project fits the requirements of the project rather than general purpose

TODO:

  • Fix shit-code
  • Continue working and thinking about the API
  • Rewrite to general-purpose usage
  • Type-safe custom properties

Usage

import { Webhook } from "webhook-openapi";
import { Type } from "@sinclair/typebox";

const WEBHOOK_URL = "http://localhost:8943";

const webhook = new Webhook()
    .event(
        "some",
        (event) =>
            event
                .body(
                    Type.Object({
                        some: Type.String(),
                    })
                )
                .response(
                    Type.Object({
                        status: Type.Literal("ok"),
                    })
                ),
        {
            description: "Some description",
        }
    )
    .event("some2", (event) =>
        event
            .body(
                Type.Object({
                    some: Type.String(),
                })
            )
            .response(Type.Object({}))
    );

console.log(webhook.openapi); // get OpenAPI document with `webhooks` object

const response = await webhook.call(WEBHOOK_URL, "some", { some: "string" });
//      ^? const response: { status: "ok" }

Plugins

Retries on timers

This simple plugin is just retries when request failed (sendError or non-ok response). First argument is timeout ms (default to 30 * 1000).

import { retriesOnTimers } from "webhook-openapi/plugins/timers-retries";

const webhook = new Webhook()
    .extend(retriesOnTimers(60 * 1000))
    .event("test", (event) => event.body(Type.Object({ body: Type.String() })));

Store Drizzle

This plugin writes requests and responses to the database using drizzle

Warning

It is important to remember that when used together with the retries plugin, only the Response with the same RequestId is duplicated

import { store } from "webhook-openapi/plugins/store-drizzle";

export type HTTPMethods =
    | "GET"
    | "POST"
    | "PUT"
    | "PATCH"
    | "DELETE"
    | "OPTIONS"
    | "HEAD"
    | "TRACE";

export const requestTable = pgTable("requests", {
    id: serial("id").primaryKey(),
    data: jsonb("data"),
    method: text("method").$type<HTTPMethods>(),
    headers: jsonb("headers").$type<Record<string, string>>(),
    url: text("url"),
});

export const responseTable = pgTable("responses", {
    id: serial("id").primaryKey(),
    data: jsonb("data"),
    headers: jsonb("headers").$type<Record<string, string>>(),
    status: integer("status"),
    requestId: integer("request_id")
        .notNull()
        .references(() => requestTable.id),
    responseTime: real("response_time"),
});

const client = postgres(process.env.DATABASE_URL as string);
const db = drizzle(client);

const webhook = new Webhook()
    .extend(store(db, requestTable, responseTable))
    .event("test", (event) => event.body(Type.Object({ body: Type.String() })));

You own plugin

You can write your own plugin:

const retriesPlugin = new Webhook().onSendError(
    ({ data, request, event, webhook }) => {
        setTimeout(
            // @ts-expect-error
            async () => webhook.call(request.url, event, data),
            10 * 1000
        );
    }
);

const webhook = new Webhook().extend(retriesPlugin);

Hooks

  • sendError
const webhook = new Webhook().onSendError(
    ({ data, request, event, webhook }) => {
        setTimeout(
            // @ts-expect-error
            async () => webhook.call(request.url, event, data),
            10 * 1000
        );
    }
);
  • afterResponse
const webhook = new Webhook().onAfterResponse(
    ({ response, data, request, event, webhook }) => {
        console.log(response);
        if (!response.ok)
            setTimeout(
                // @ts-expect-error
                async () => webhook.call(request.url, event, data),
                10 * 1000
            );
    }
);
  • beforeRequest
const webhook = new Webhook().onBeforeRequest(({ request, data }) => {
    request.headers.append("x-length", JSON.stringify(data).length.toString());
    request.body = JSON.stringify(data);
});

mimeType

it so boring to talk about it... Please read this test

by default application/json and text/plain mimeTypes are handled

import { unpack, pack } from "msgpackr";

let answer = {};
const shouldBe = { some: { values: true } };
const mimeType = "application/x-msgpack";

using server = Bun.serve({
    port: 9888,
    fetch: () =>
        new Response(pack(shouldBe), {
            headers: {
                "content-type": mimeType,
            },
        }),
});

const webhook = new Webhook()
    .mimeType(mimeType, {
        serialization: (data) => pack(data),
        deserialization: async (response) =>
            unpack(Buffer.from(await response.arrayBuffer())),
    })
    .event("test", (event) => event.body(Type.Object({ body: Type.String() })))
    .onAfterResponse(({ response, data }) => {
        console.log(data, response);
        answer = data;
    });

await webhook.call(server.url.href, "test", { body: "test" });

expect(answer).toEqual(shouldBe);

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published