@tawk.to/nestjs-google-pubsub-microservice
Version:
NestJS Google Cloud Pub/Sub Microservice Transport
197 lines • 8.66 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 constants_1 = require("@nestjs/microservices/constants");
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 gc_pubsub_parser_1 = require("./gc-pubsub.parser");
const gc_message_serializer_1 = require("./gc-message.serializer");
class GCPubSubServer extends microservices_1.Server {
constructor(options) {
var _a, _b, _c, _d, _e;
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.topicName = this.options.topic || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_TOPIC;
this.subscriptionName =
this.options.subscription || gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_SUBSCRIPTION;
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 = (_a = this.options.noAck) !== null && _a !== void 0 ? _a : gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_NO_ACK;
this.init = (_b = this.options.init) !== null && _b !== void 0 ? _b : gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_INIT;
this.checkExistence =
(_c = this.options.checkExistence) !== null && _c !== void 0 ? _c : gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_CHECK_EXISTENCE;
this.createSubscriptionOptions = this.options.createSubscriptionOptions;
this.replyTopics = new Set();
this.ackAfterResponse =
(_d = this.options.ackAfterResponse) !== null && _d !== void 0 ? _d : gc_pubsub_constants_1.GC_PUBSUB_DEFAULT_ACK_AFTER_RESPONSE;
this.initializeSerializer(options);
this.initializeDeserializer(options);
this.parser = (_e = options.parser) !== null && _e !== void 0 ? _e : new gc_pubsub_parser_1.GCPubSubParser();
}
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.subscription.create.bind(this.subscription, this.createSubscriptionOptions));
}
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) => {
await this.handleMessage(message);
if (this.noAck) {
message.ack();
}
})
.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 { attributes, publishTime } = message;
const rawMessage = await this.parser.parse(message);
const now = new Date();
const packet = (await this.deserializer.deserialize({
data: rawMessage,
id: attributes._id,
pattern: attributes._pattern,
}, { message }));
const pattern = (0, shared_utils_1.isString)(packet.pattern)
? packet.pattern
: JSON.stringify(packet.pattern);
const correlationId = packet.id;
const timeout = Number(attributes._timeout);
if (timeout && timeout > 0) {
if (now.getTime() - publishTime.getTime() >= timeout) {
const timeoutPacket = {
id: correlationId,
status: 'error',
err: 'Message Timeout',
};
this.sendMessage(timeoutPacket, attributes._replyTo, correlationId, attributes);
if (!this.noAck)
message.ack();
return;
}
}
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,
};
this.sendMessage(noHandlerPacket, attributes._replyTo, correlationId, attributes);
if (this.noAck)
message.ack();
return;
}
const response$ = this.transformToObservable(await handler(packet.data, context));
const publish = (data) => this.sendMessage(data, attributes._replyTo, correlationId, attributes);
response$ && this.send(response$, publish);
if (this.ackAfterResponse) {
message.ack();
}
}
async sendMessage(message, replyTo, id, attributes) {
Object.assign(message, { id });
const outgoingResponse = await this.serializer.serialize(message, {
message: {
data: message,
attributes,
},
});
this.replyTopics.add(replyTo);
await this.client.topic(replyTo, this.publisherConfig).publishMessage({
data: outgoingResponse.data,
attributes: Object.assign(Object.assign(Object.assign(Object.assign({ id }, attributes), (outgoingResponse.isDisposed ? { isDisposed: '1' } : {})), (outgoingResponse.err
? { err: JSON.stringify(outgoingResponse.err) }
: {})), (outgoingResponse.status ? { status: outgoingResponse.status } : {})),
});
}
initializeSerializer(options) {
var _a;
this.serializer = (_a = options === null || options === void 0 ? void 0 : options.serializer) !== null && _a !== void 0 ? _a : new gc_message_serializer_1.GCPubSubResponseSerializer();
}
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