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
- Fix shit-code
- Continue working and thinking about the API
- Rewrite to general-purpose usage
- Type-safe custom properties
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" }
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() })));
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 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);
- 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);
});
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);