Refactor event queue to allow multiple types of events

This commit is contained in:
Dominic Ferrando
2026-07-12 05:21:33 -04:00
parent 4d1b3bc3ef
commit 4c2acfaede
5 changed files with 88 additions and 60 deletions
+30 -22
View File
@@ -1,13 +1,13 @@
import { PrintfulClient, ProductSyncer, WebflowClient } from "@blade-and-brawn/commerce";
import { env, log } from "../util";
import { db } from "../database/db";
import { EventQueueService, type EventType } from "./event-queue";
import { EventQueue, type EventSpec } from "./event-queue";
import { Type as t } from "@sinclair/typebox";
import type { Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { sql } from "kysely";
const ProductSyncPayloadSchema = t.Object({
const ProductSyncUpdatePayloadSchema = t.Object({
session: t.Object({
name: t.String(),
id: t.String()
@@ -18,22 +18,27 @@ const ProductSyncPayloadSchema = t.Object({
})
)
});
type ProductSyncPayload = Static<typeof ProductSyncPayloadSchema>;
type ProductSyncUpdatePayload = Static<typeof ProductSyncUpdatePayloadSchema>;
const ProductSyncStateSchema = t.Union([
const ProductSyncUpdateStateSchema = t.Union([
t.Object({
pProductIds: t.Optional(t.Array(t.Number()))
}),
t.Null()
]);
type ProductSyncState = Static<typeof ProductSyncStateSchema>;
type ProductSyncUpdateState = Static<typeof ProductSyncUpdateStateSchema>;
export type ProductSyncEvents = {
product_sync_update: EventSpec<ProductSyncUpdatePayload, ProductSyncUpdateState>
// TODO: product_sync_delete, once its schema/processor are written
};
export class CommerceService {
public readonly Printful = new PrintfulClient({
readonly Printful = new PrintfulClient({
token: env.PRINTFUL_AUTH,
storeId: env.PRINTFUL_STORE_ID,
});
public readonly Webflow = new WebflowClient({
readonly Webflow = new WebflowClient({
siteId: env.WEBFLOW_SITE_ID,
collectionsId: env.WEBFLOW_COLLECTION_ID,
token: env.WEBFLOW_AUTH,
@@ -43,29 +48,32 @@ export class CommerceService {
}
class ProductSyncService {
static readonly EVENT_TYPE: EventType = "product_sync";
readonly Queue: EventQueueService<ProductSyncPayload, ProductSyncState>;
static readonly EVENT_GROUP = "product_sync";
readonly Queue: EventQueue<"product_sync", ProductSyncEvents>;
private readonly ProductSyncer: ProductSyncer;
// TODO: make each service have their own logger, so we can enforce .child({component: <ServiceName>})
constructor(private readonly Printful: PrintfulClient, private readonly Webflow: WebflowClient) {
this.ProductSyncer = new ProductSyncer(this.Printful, this.Webflow, log);
this.Queue = new EventQueueService({
type: ProductSyncService.EVENT_TYPE,
schemas: { payload: ProductSyncPayloadSchema, state: ProductSyncStateSchema },
this.Queue = new EventQueue<"product_sync", ProductSyncEvents>({
group: ProductSyncService.EVENT_GROUP,
cfg: {
retry: { limit: 3, delayMs: 10000 },
concurrency: { limit: 1, timeoutMs: 600000 }
},
processor: async (id, payload, setState) => {
await this.ProductSyncer.run({
filter: payload.filter,
beforeStep: async (pProductIds: number[]) => {
await setState({ pProductIds });
types: {
"product_sync_update": {
schemas: { payload: ProductSyncUpdatePayloadSchema, state: ProductSyncUpdateStateSchema },
processor: async (id, payload, setState) => {
await this.ProductSyncer.syncApparel({
filter: payload.filter,
beforeStep: async (pProductIds: number[]) => {
await setState({ pProductIds });
}
});
}
});
}
}
});
}
@@ -73,7 +81,7 @@ class ProductSyncService {
async getLatestSyncState(sessionId: string) {
const latestSync = await db.selectFrom("events")
.select(["started_at", "ended_at", "has_failed", "state"])
.where("type", "=", ProductSyncService.EVENT_TYPE)
.where("group", "=", ProductSyncService.EVENT_GROUP)
.where(sql<string>`payload->'session'->>'id'`, "=", sessionId)
.orderBy(sql`(started_at is not null and ended_at is null)`, "desc")
.orderBy("created_at", "desc")
@@ -81,11 +89,11 @@ class ProductSyncService {
.executeTakeFirst();
if (!latestSync) return;
Value.Assert(ProductSyncStateSchema, latestSync.state);
Value.Assert(ProductSyncUpdateStateSchema, latestSync.state);
return {
startDate: latestSync.started_at,
status: EventQueueService.status(latestSync),
status: EventQueue.status(latestSync),
syncingPProductIds: latestSync.state?.pProductIds ?? []
};
}