nestjs-google-pubsub-microservice
Version:
NestJS Google Cloud Pub/Sub Microservice Transport
182 lines • 7.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GCPubSubServer = void 0;
const pubsub_1 = require("@google-cloud/pubsub");
const microservices_1 = require("@nestjs/microservices");
const common_1 = require("@nestjs/common");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const gc_pubsub_constants_1 = require("./gc-pubsub.constants");
const gc_pubsub_context_1 = require("./gc-pubsub.context");
const gc_pubsub_utils_1 = require("./gc-pubsub.utils");
const constants_1 = require("@nestjs/microservices/constants");
class GCPubSubServer extends microservices_1.Server {
constructor(options) {
super();
this.options = options;
this.logger = new common_1.Logger(GCPubSubServer.name);
this.pendingEventListeners = [];
this.client = null;
this.subscription = null;
this.clientConfig = this.options.client || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_CLIENT_CONFIG;
this.scopedEnvKey = this.options.scopedEnvKey ?? '';
this.topicName = this.options.topic || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_TOPIC;
this.topicName = `${this.scopedEnvKey}${this.topicName}`;
this.subscriptionName =
this.options.subscription || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_SUBSCRIPTION;
this.subscriptionName = `${this.scopedEnvKey}${this.subscriptionName}`;
this.subscriberConfig =
this.options.subscriber || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_SUBSCRIBER_CONFIG;
this.publisherConfig =
this.options.publisher || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_PUBLISHER_CONFIG;
this.noAck = this.options.noAck ?? gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_NO_ACK;
this.init = this.options.init ?? gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_INIT;
this.checkExistence =
this.options.checkExistence ?? gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_CHECK_EXISTENCE;
this.subscriptionFilter = this.options.subscriptionFilter;
this.replyTopics = new Set();
this.initializeSerializer(options);
this.initializeDeserializer(options);
}
async listen(callback) {
this.client = this.createClient();
const topic = this.client.topic(this.topicName);
if (this.init) {
await this.createIfNotExists(topic.create.bind(topic));
}
else if (this.checkExistence) {
const [exists] = await topic.exists();
if (!exists) {
const message = `PubSub server is not started: topic ${this.topicName} does not exist`;
this.logger.error(message);
throw new Error(message);
}
}
this.subscription = topic.subscription(this.subscriptionName, this.subscriberConfig);
if (this.init) {
await this.createIfNotExists(this.subscriptionFilter
? () => this.client.createSubscription(this.topicName, this.subscriptionName, { filter: this.subscriptionFilter })
: this.subscription.create.bind(this.subscription));
}
else if (this.checkExistence) {
const [exists] = await this.subscription.exists();
if (!exists) {
const message = `PubSub server is not started: subscription ${this.subscriptionName} does not exist`;
this.logger.error(message);
throw new Error(message);
}
}
this.pendingEventListeners.forEach(({ event, callback }) => {
this.subscription.on(event, callback);
});
this.pendingEventListeners = [];
this.subscription
.on('message', async (message) => {
try {
await this.handleMessage(message);
if (this.noAck) {
message.ack();
}
}
catch (err) {
if (this.noAck) {
message.nack();
}
this.logger.error(err);
}
})
.on('error', (err) => this.logger.error(err));
callback();
}
async close() {
await (0, gc_pubsub_utils_1.closeSubscription)(this.subscription);
await Promise.all(Array.from(this.replyTopics.values()).map((replyTopic) => {
return (0, gc_pubsub_utils_1.flushTopic)(this.client.topic(replyTopic));
}));
this.replyTopics.clear();
await (0, gc_pubsub_utils_1.closePubSub)(this.client);
this.pendingEventListeners = [];
}
async handleMessage(message) {
const { data, attributes } = message;
let rawMessage;
try {
rawMessage = JSON.parse(data.toString());
}
catch (error) {
this.logger.error(`Unsupported JSON message data format for message '${message.id}'`);
return;
}
let packet;
if (attributes.pattern) {
packet = this.deserializer.deserialize({
data: rawMessage,
id: attributes.id,
pattern: attributes.pattern,
});
}
else {
packet = this.deserializer.deserialize(rawMessage);
}
const pattern = (0, shared_utils_1.isString)(packet.pattern)
? packet.pattern
: JSON.stringify(packet.pattern);
const correlationId = packet.id;
const context = new gc_pubsub_context_1.GCPubSubContext([message, pattern]);
if ((0, shared_utils_1.isUndefined)(correlationId)) {
return this.handleEvent(pattern, packet, context);
}
const handler = this.getHandlerByPattern(pattern);
if (!handler) {
if (!attributes.replyTo) {
return;
}
const status = 'error';
const noHandlerPacket = {
id: correlationId,
status,
err: constants_1.NO_MESSAGE_HANDLER,
};
return this.sendMessage(noHandlerPacket, attributes.replyTo, correlationId);
}
const response$ = this.transformToObservable(await handler(packet.data, context));
const publish = (data) => this.sendMessage(data, attributes.replyTo, correlationId);
response$ && this.send(response$, publish);
}
async sendMessage(message, replyTo, id) {
Object.assign(message, { id });
const outgoingResponse = this.serializer.serialize(message);
this.replyTopics.add(replyTo);
await this.client
.topic(replyTo, this.publisherConfig)
.publishMessage({ json: outgoingResponse, attributes: { id } });
}
async createIfNotExists(create) {
try {
await create();
}
catch (error) {
if (error.code !== gc_pubsub_constants_1.ALREADY_EXISTS) {
throw error;
}
}
}
createClient() {
return new pubsub_1.PubSub(this.clientConfig);
}
on(event, callback) {
if (this.subscription) {
this.subscription.on(event, callback);
}
else {
this.pendingEventListeners.push({ event, callback });
}
}
unwrap() {
if (!this.client) {
throw new Error('Client is not initialized.');
}
return this.client;
}
}
exports.GCPubSubServer = GCPubSubServer;
//# sourceMappingURL=gc-pubsub.server.js.map