@serverless-offline-queue/plugin-sqs
Version:
Serverless Offline plugin for Amazon SQS emulation
306 lines (303 loc) • 11.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/libs/ServerlessSqsPlugin.ts
import { createSqsServer } from "local-aws-sqs";
import { SQSClient, ReceiveMessageCommand, DeleteMessageBatchCommand } from "@aws-sdk/client-sqs";
var ServerlessSqsPlugin = class {
constructor(serverless) {
this.serverless = serverless;
this.pollingIntervals = /* @__PURE__ */ new Map();
this.hooks = {
"before:offline:start": () => __async(this, null, function* () {
yield this.startSqsServer();
this.setQueueEnvironmentVariables();
yield this.createLambda();
this.startPolling();
}),
"before:offline:start:init": () => __async(this, null, function* () {
yield this.startSqsServer();
this.setQueueEnvironmentVariables();
}),
"offline:start:init": () => __async(this, null, function* () {
yield this.createLambda();
}),
"offline:start:ready": () => __async(this, null, function* () {
this.startPolling();
}),
"after:offline:start:end": () => __async(this, null, function* () {
this.stopPolling();
this.stopSqsServer();
})
};
}
startSqsServer() {
return __async(this, null, function* () {
var _a, _b, _c;
const pluginConfig = this.getPluginConfig();
const port = (_a = pluginConfig.port) != null ? _a : 9324;
const queues = (_c = (_b = pluginConfig.queues) == null ? void 0 : _b.map((queueName) => ({
QueueName: queueName
}))) != null ? _c : [];
this.sqsServer = yield createSqsServer({
port,
queues
});
this.sqsClient = new SQSClient({
region: "us-east-1",
endpoint: `http://localhost:${port}`,
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
}
});
if (queues.length > 0) {
this.log(`SQS server started on port ${port}. Queues: ${queues.map((q) => q.QueueName).join(", ")}`);
}
});
}
createLambda() {
return __async(this, null, function* () {
try {
const { default: Lambda } = yield import("serverless-offline/lambda");
const lambdas = this.getLambdas();
this.lambda = new Lambda(this.serverless, this.getOfflineOptions());
this.lambda.create(lambdas);
this.log(`Lambda functions initialized: ${lambdas.map((l) => l.functionKey).join(", ")}`);
} catch (error) {
this.logError("Failed to create Lambda instance:", error);
}
});
}
getLambdas() {
const service = this.serverless.service;
const lambdas = [];
const functionKeys = service.getAllFunctions();
functionKeys.forEach((functionKey) => {
const functionDefinition = service.getFunction(functionKey);
lambdas.push({ functionKey, functionDefinition });
});
return lambdas;
}
getOfflineOptions() {
var _a;
const { service: { custom = {}, provider } } = this.serverless;
const offlineOptions = (_a = custom["serverless-offline"]) != null ? _a : {};
return __spreadValues(__spreadValues(__spreadValues({}, provider), offlineOptions), this.getPluginConfig());
}
setQueueEnvironmentVariables() {
var _a, _b;
const pluginConfig = this.getPluginConfig();
const port = (_a = pluginConfig.port) != null ? _a : 9324;
const baseUrl = `http://localhost:${port}`;
const accountId = "000000000000";
const queueUrls = /* @__PURE__ */ new Map();
(_b = pluginConfig.queues) == null ? void 0 : _b.forEach((queueName) => {
const queueUrl = `${baseUrl}/${accountId}/${queueName}`;
queueUrls.set(queueName, queueUrl);
});
const functions = this.serverless.service.functions || {};
Object.entries(functions).forEach(([functionName, functionConfig]) => {
var _a2;
(_a2 = functionConfig.environment) != null ? _a2 : functionConfig.environment = {};
Object.entries(functionConfig.environment).forEach(([envKey, envValue]) => {
if (typeof envValue === "object" && (envValue == null ? void 0 : envValue.Ref)) {
const refName = envValue.Ref;
const matchingQueue = this.findQueueByResourceName(refName, queueUrls);
if (matchingQueue) {
functionConfig.environment[envKey] = matchingQueue;
}
}
});
});
}
findQueueByResourceName(resourceName, queueUrls) {
var _a, _b, _c;
const resources = (_b = (_a = this.serverless.service.resources) == null ? void 0 : _a.Resources) != null ? _b : {};
const resource = resources[resourceName];
if ((resource == null ? void 0 : resource.Type) === "AWS::SQS::Queue") {
const queueName = (_c = resource.Properties) == null ? void 0 : _c.QueueName;
if (queueName && queueUrls.has(queueName)) {
return queueUrls.get(queueName);
}
}
return null;
}
startPolling() {
var _a;
const functions = (_a = this.serverless.service.functions) != null ? _a : {};
Object.entries(functions).forEach(([functionName, functionConfig]) => {
var _a2;
const events = (_a2 = functionConfig.events) != null ? _a2 : [];
events.forEach((event) => {
var _a3;
if (event.sqs) {
const queueArn = event.sqs.arn;
const queueUrl = this.resolveQueueUrl(queueArn);
const batchSize = (_a3 = event.sqs.batchSize) != null ? _a3 : 10;
if (queueUrl) {
const queueName = queueUrl.split("/").pop();
this.log(`Starting SQS polling: ${functionName} -> ${queueName}`);
const interval = setInterval(() => {
this.pollQueue(queueUrl, functionName, batchSize);
}, 1e3);
this.pollingIntervals.set(`${functionName}-${queueUrl}`, interval);
} else {
this.logError(`Unable to resolve queue URL for function: ${functionName}`);
}
}
});
});
}
resolveQueueUrl(arn) {
var _a, _b, _c, _d;
let resourceName = null;
if (typeof arn === "object") {
if (arn["!GetAtt"]) {
resourceName = arn["!GetAtt"][0];
} else if (arn["Fn::GetAtt"]) {
resourceName = arn["Fn::GetAtt"][0];
}
}
if (resourceName) {
const resources = (_b = (_a = this.serverless.service.resources) == null ? void 0 : _a.Resources) != null ? _b : {};
const resource = resources[resourceName];
if ((resource == null ? void 0 : resource.Type) === "AWS::SQS::Queue") {
const queueName = (_c = resource.Properties) == null ? void 0 : _c.QueueName;
if (queueName) {
const pluginConfig = this.getPluginConfig();
const port = (_d = pluginConfig.port) != null ? _d : 9324;
const accountId = "000000000000";
return `http://localhost:${port}/${accountId}/${queueName}`;
}
}
}
return null;
}
pollQueue(queueUrl, functionName, batchSize) {
return __async(this, null, function* () {
if (!this.sqsClient) {
return;
}
try {
const result = yield this.sqsClient.send(new ReceiveMessageCommand({
QueueUrl: queueUrl,
MaxNumberOfMessages: batchSize,
WaitTimeSeconds: 0,
VisibilityTimeout: 30
}));
if (result.Messages && result.Messages.length > 0) {
const queueName = queueUrl.split("/").pop();
const sqsEvent = {
Records: result.Messages.map((message) => ({
messageId: message.MessageId,
receiptHandle: message.ReceiptHandle,
body: message.Body,
attributes: message.Attributes || {},
messageAttributes: message.MessageAttributes || {},
md5OfBody: message.MD5OfBody,
eventSource: "aws:sqs",
eventSourceARN: `arn:aws:sqs:us-east-1:000000000000:${queueName}`,
awsRegion: "us-east-1"
}))
};
try {
yield this.invokeFunction(functionName, sqsEvent);
yield this.sqsClient.send(new DeleteMessageBatchCommand({
QueueUrl: queueUrl,
Entries: result.Messages.map((message) => ({
Id: message.MessageId,
ReceiptHandle: message.ReceiptHandle
}))
}));
} catch (error) {
this.logError(`Function execution error ${functionName}:`, error);
}
}
} catch (error) {
this.logError(`Queue polling error:`, error);
}
});
}
invokeFunction(functionName, event) {
return __async(this, null, function* () {
if (!this.lambda) {
this.logError(`Lambda instance not available for function: ${functionName}`);
return;
}
try {
const handler = this.lambda.get(functionName);
if (!handler) {
this.logError(`Handler not found for function: ${functionName}`);
return;
}
handler.setEvent(event);
const result = yield handler.runHandler();
return result;
} catch (error) {
this.logError(`Function invocation error ${functionName}:`, error);
throw error;
}
});
}
stopPolling() {
this.pollingIntervals.forEach((interval) => {
clearInterval(interval);
});
this.pollingIntervals.clear();
}
stopSqsServer() {
if (this.sqsServer) {
this.sqsServer.close();
this.sqsServer = void 0;
}
}
getPluginConfig() {
var _a;
return ((_a = this.serverless.service.custom) == null ? void 0 : _a["@serverless-offline-queue/plugin-sqs"]) || {};
}
log(message) {
console.log(`[@serverless-offline-queue/plugin-sqs] ${message}`);
}
logError(message, error) {
console.error(`[@serverless-offline-queue/plugin-sqs] ${message}`, error != null ? error : "");
}
};
// src/index.ts
var index_default = ServerlessSqsPlugin;
export {
index_default as default
};