UNPKG

@codecompose/typed-pubsub

Version:

A type-safe pub/sub abstraction for Google Cloud and Firebase

128 lines (122 loc) 4.2 kB
import { onMessagePublished } from "firebase-functions/v2/pubsub"; import { got } from "get-or-throw"; import { PubSub } from "@google-cloud/pubsub"; //#region src/constants.ts const MINUTE_MS = 60 * 1e3; /** Default options for Pubsub handlers */ const defaultHandlerOptions = { retry: true, retryMaxAgeMinutes: void 0, memory: "512MiB", cpu: 1, timeoutSeconds: 20, maxInstances: 250, markEvent: false, vpcConnector: void 0 }; //#endregion //#region src/utils/should-drop-event.ts function shouldDropEvent(event, maxAgeMinutes) { if (maxAgeMinutes === void 0) return false; const maxAgeMs = maxAgeMinutes * 60 * 1e3; if (Date.now() - Date.parse(event.time) > maxAgeMs) { /** Dropping event due to max age reached */ console.error(/* @__PURE__ */ new Error(`Dropping event ${event.type} for ${event.source} because max age (${String(maxAgeMinutes)} minutes) was reached.`)); return true; } return false; } //#endregion //#region src/handler.ts /** * Creates a factory function for generating type-safe Pubsub handlers * * @param schemas - Zod schemas for each topic * @param region - GCP region * @param eventMarkingFunctions - Optional functions for tracking processed * events * @param defaultOptions - Default options for all handlers * @returns A factory function for creating handlers */ function createHandlerFactory(schemas, region, eventMarkingFunctions, defaultOptions = defaultHandlerOptions, onMessagePublished$1 = onMessagePublished) { return ({ topic, handler, options = {} }) => { const { retryMaxAgeMinutes, markEvent, ...mergedOptions } = { ...defaultOptions, ...options }; const canMarkEvents = markEvent && eventMarkingFunctions !== void 0; return onMessagePublished$1({ ...mergedOptions, topic, region }, async (event) => { if (shouldDropEvent(event, retryMaxAgeMinutes)) return; if (canMarkEvents && event.id) { if (await eventMarkingFunctions.isEventProcessed(event.id)) { /** * This should happen very rarely, so we log an error just to notice * how often it happens. */ console.error(/* @__PURE__ */ new Error(`(Not an error) Pubsub event ${event.id} was already processed`)); return; } } const schema = got(schemas, topic); const messageData = event.data.message.json; const result = schema.safeParse(messageData); if (!result.success) { console.error(/* @__PURE__ */ new Error(`Zod validation error for topic ${topic}`), result.error.flatten()); return; } await handler(result.data); if (canMarkEvents && event.id) await eventMarkingFunctions.markEventAsProcessed(event.id); }); }; } //#endregion //#region src/publisher.ts /** * Creates a factory function that produces type-safe publishers for specific * topics * * @param pubsubClient - Google Cloud Pubsub client * @returns A factory function for creating publishers */ function createPublisherFactory(pubsubClient) { return (topicName) => { /** * Publishes a message to the specified topic * * @param data - The data to publish, must conform to the topic's schema * @returns Promise that resolves when the message is published */ return async (data) => { await pubsubClient.topic(topicName).publishMessage({ json: data }); }; }; } //#endregion //#region src/factory.ts /** * Creates a type-safe Pubsub client for handling messages with schema * validation * * @param pubsubClient - Google Cloud Pubsub client instance * @param schemas - Zod schemas for validating messages for each topic * @param region - GCP region for the Pubsub functions * @param options - Global defaults for all handlers * @returns Type-safe Pubsub client with publisher and handler factories */ function createTypedPubsub({ client, schemas, region, options = {}, onMessagePublished: onMessagePublished$1 = onMessagePublished }) { const handlerOptions = { ...defaultHandlerOptions, ...options.defaultHandlerOptions }; return { createPublisher: createPublisherFactory(client), createHandler: createHandlerFactory(schemas, region, options.eventMarkingFunctions, handlerOptions, onMessagePublished$1) }; } //#endregion export { createTypedPubsub }; //# sourceMappingURL=index.mjs.map