UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

117 lines (99 loc) 3.14 kB
import { $atom, $hook, $inject, $state, Alepha, type Static, z } from "alepha"; import { $logger } from "alepha/logger"; import { RedisProvider, RedisSubscriberProvider } from "alepha/redis"; import { type SubscribeCallback, TopicProvider, type TopicPublishOptions, type TopicSubscribeOptions, type UnSubscribeFn, } from "alepha/topic"; // --------------------------------------------------------------------------------------------------------------------- /** * Redis topic configuration atom. */ export const redisTopicOptions = $atom({ name: "alepha.topic.redis.options", schema: z.object({ prefix: z.text({ default: "topic", description: "Prefix for all topic channels in Redis.", }), }), default: { prefix: "topic", }, }); export type RedisTopicOptions = Static<typeof redisTopicOptions.schema>; declare module "alepha" { interface State { [redisTopicOptions.key]: RedisTopicOptions; } } // --------------------------------------------------------------------------------------------------------------------- export class RedisTopicProvider extends TopicProvider { protected readonly options = $state(redisTopicOptions); protected readonly alepha = $inject(Alepha); protected readonly redisProvider = $inject(RedisProvider); protected readonly redisSubscriberProvider = $inject(RedisSubscriberProvider); protected readonly log = $logger(); protected readonly start = $hook({ on: "start", handler: async () => { const subscribers = this.subscribers(); if (subscribers.length) { await Promise.all(subscribers.map((fn) => fn())); for (const subscriber of subscribers) { this.log.debug(`Subscribed to topic '${subscriber.name}'`); } } }, }); protected override wildcardChar(): string { return "*"; } public prefix(queue: string): string { return `${this.options.prefix}:${queue}`; } /** * Publish a message to a topic. */ public async publish( topic: string, message: string, options?: TopicPublishOptions, ): Promise<void> { if (options?.retain) { await this.redisProvider.set(`${this.prefix(topic)}:retained`, message); } await this.redisProvider.publish(this.prefix(topic), message); } /** * Subscribe to a topic. */ public async subscribe( name: string, callback: SubscribeCallback, _options?: TopicSubscribeOptions, ): Promise<UnSubscribeFn> { const topic = this.prefix(name); // Subscribe first to avoid message loss window await this.redisSubscriberProvider.subscribe(topic, callback); // Then deliver retained message if exists const retained = await this.redisProvider.get(`${topic}:retained`); if (retained) { await callback(retained.toString()); } return () => this.unsubscribe(name, callback); } /** * Unsubscribe from a topic. */ public async unsubscribe( name: string, callback?: SubscribeCallback, ): Promise<void> { const topic = this.prefix(name); await this.redisSubscriberProvider.unsubscribe(topic, callback); } }