SlayQ Client
Creating the Client
In order to dispatch or receive events in whatever stack you are working with, you'll need to create a client. Here's an example:
import { SlayQClient } from "@slay-pics/slay-q";
import { SlayQSupabaseDriver } from "@slay-pics/slay-q-supabase";
import { sleepTestEvent, type SleepTestEvent } from "~/server/slay-q/testing/sleep-test";
import { cronTestEvent, type CronTestEvent } from "~/server/slay-q/testing/cron-test";
import { cancelOnTest, type CancelOnTestEvent } from "~/server/slay-q/testing/cancel-on-test";
export type TestingEvents = {
"testing/sleep-test": z.infer<typeof SleepTestEvent>;
"testing/cron-test": z.infer<typeof CronTestEvent>;
"testing/cancel-on-test": z.infer<typeof CancelOnTestEvent>;
};
export const TestingFunctions = [
sleepTestEvent,
cronTestEvent,
cancelOnTest,
];
export const defaultSlayQClient = new SlayQClient<TestingEvents>({
driver: new SlayQSupabaseDriver(),
endpoint: "http://localhost/api/slay-q",
functions: [
...TestingFunctions,
],
});
There's a bit to unpack here.
First we are creating a TestingEvents
type that maps an event to the event's data type (inferred from the event's data's schema).
This type is then passed to the SlayQClient so that any calls to sendEvent
are typed.
Second thing we are doing is creating a const
called Testing Functions
that is just an array of all of our testing functions. It
seems silly to do it for just 3 functions, but when this gets larger and you're pulling in functions for different parts of your app,
you'll want to keep this as organized and neat as possible. We have 180+ functions in use on Slay.
The endpoint
is where slay-q-server
can callback to when it has an event for your app to process. Obviously this should be
specified with configuration or environment variables.
The driver
is the interface with the database that the client will use. Slay Q provides three base packages with different drivers:
@slay-pics/slay-q-supabase
- Supabase driver that uses@supabase/supabase-js
to connect to supabase.@slay-pics/slay-q-postgres
- Driver that talks directly to Postgres viapg
package.@slay-pics/slay-q-rpc
- Driver that talks to aslay-q-ingest
instance. The Slay Q ingest is an HTTP RPC server. See the nuxt example app for an example of its use.
Dispatch Events
Once you've created the client, dispatching events is easy:
await defaultSlayQClient.sendEvent("products/video-product/unlocked", {
videoProductId: videoProduct.id,
profileId: profile.id,
userId: profile.user_id,
});
In this example, we're dispatching an unlocked event for a video product with the associated data needed for the event.
Receiving Events
This is where it will get trickier because this part is dependent entirely on whatever stack or backend you are using. For us, that is Nuxt.
We create a nitrojs handler at /api/slay-q/index.post.ts
that looks like:
import { useValidatedBody } from "h3-zod";
import { createError, H3Event, sendError } from "h3";
import { SlayQReceiveEventPayloadSchema } from "@slay-pics/slay-q";
import { defaultSlayQClient } from "~/lib/slay-q/default-slay-q-client";
export default defineEventHandler(async event => {
const sig = getHeader(event, "X-SlayQ-Signature");
if (!sig) {
sendError(event, createError({ statusCode: 400, statusMessage: 'Invalid signature' }));
return;
}
const body = await useValidatedBody(event, SlayQReceiveEventPayloadSchema);
await defaultSlayQClient.receiveEvent(sig, body);
return {
status: "ok",
};
});
First we get the X-SlayQ-Signature
header which contains the signature of the request. Note that you will need to have the
environment variable SLAY_Q_WORKER_SECRET
defined in your .env
or wherever you are specifying environment variables.
We then fetch the body of the request (as a parsed JSON object) with useValidatedBody
. We've passed in the
SlayQReceiveEventPayloadSchema
schema to validate the payload against.
We then call the receiveEvent
method on the SlayQClient instance which handles the rest.
This should be pretty easy to adapt other frameworks.