@swarmion/serverless-contracts
Version:
Generate and use type-safe contracts between your Serverless services.
1,513 lines (1,468 loc) • 53.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
ApiGatewayContract: () => ApiGatewayContract,
CloudFormationContract: () => CloudFormationContract,
EventBridgeContract: () => EventBridgeContract,
HttpStatusCodes: () => HttpStatusCodes,
InfiniteThroughput: () => InfiniteThroughput,
SQSContract: () => SQSContract,
SwarmionRouter: () => SwarmionRouter,
buildPutEvent: () => buildPutEvent,
buildPutEvents: () => buildPutEvents,
buildSendMessage: () => buildSendMessage,
buildSendMessages: () => buildSendMessages,
getApiGatewayHandler: () => getApiGatewayHandler,
getApiGatewayTrigger: () => getApiGatewayTrigger,
getAxiosRequest: () => getAxiosRequest,
getContractFullSchema: () => getContractFullSchema,
getEventBridgeHandler: () => getEventBridgeHandler,
getEventBridgeTrigger: () => getEventBridgeTrigger,
getFetchRequest: () => getFetchRequest,
getHandler: () => getHandler,
getLambdaHandler: () => getLambdaHandler,
getMultipleEventBridgeHandler: () => getMultipleEventBridgeHandler,
getOpenApiDocumentation: () => getOpenApiDocumentation,
getRequestParameters: () => getRequestParameters,
getSQSHandler: () => getSQSHandler,
getTrigger: () => getTrigger,
handle: () => handle
});
module.exports = __toCommonJS(index_exports);
// src/contracts/cloudFormation/cloudFormationContract.ts
var CloudFormationContract = class {
contractType = "cloudFormation";
id;
name;
/**
* Builds a new ApiGateway contract
*
* @param id a unique id to identify the contract among stacks. Beware uniqueness!
* @param name the name of the export
* See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html
*/
constructor({
id,
name
}) {
this.id = id;
this.name = name;
}
/**
* @returns the CloudFormation { Fn::ImportValue } function corresponding to the name
*/
get importValue() {
return { "Fn::ImportValue": this.name };
}
/**
* @returns the CloudFormation import name to use with Fn.importValue in CDK
*/
get cdkImportValue() {
return this.name;
}
/**
* @param description the description used in CloudFormation for this value
* @param value the CloudFormation function passed to the export
* For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html
* @returns the CloudFormation Export function
*/
exportValue({
description,
value
}) {
return {
Description: description,
Value: value,
Export: { Name: this.name }
};
}
};
// src/contracts/apiGateway/apiGatewayContract.ts
var import_isUndefined = __toESM(require("lodash/isUndefined.js"), 1);
var import_omitBy = __toESM(require("lodash/omitBy.js"), 1);
var ApiGatewayContract = class {
contractType = "apiGateway";
id;
path;
method;
integrationType;
authorizerType;
pathParametersSchema;
queryStringParametersSchema;
headersSchema;
requestContextSchema;
bodySchema;
outputSchemas;
inputSchema;
/**
* Builds a new ApiGateway contract
*
* @param props - the contract properties
*/
constructor(props) {
this.id = props.id;
this.path = props.path;
this.method = props.method;
this.integrationType = props.integrationType;
this.authorizerType = props.authorizerType ?? void 0;
this.pathParametersSchema = props.pathParametersSchema ?? void 0;
this.queryStringParametersSchema = props.queryStringParametersSchema ?? void 0;
this.headersSchema = props.headersSchema ?? void 0;
this.requestContextSchema = props.requestContextSchema ?? void 0;
this.bodySchema = props.bodySchema ?? void 0;
this.outputSchemas = props.outputSchemas ?? {};
this.inputSchema = this.getInputSchema();
}
getInputSchema() {
const properties = (0, import_omitBy.default)(
{
pathParameters: this.pathParametersSchema,
queryStringParameters: this.queryStringParametersSchema,
headers: this.headersSchema,
requestContext: this.requestContextSchema,
body: this.bodySchema
},
import_isUndefined.default
);
return {
type: "object",
properties,
required: Object.keys(properties),
additionalProperties: true
};
}
};
// src/contracts/apiGateway/features/requestParameters.ts
var import_isUndefined2 = __toESM(require("lodash/isUndefined.js"), 1);
var import_omitBy2 = __toESM(require("lodash/omitBy.js"), 1);
// src/utils/fillPathTemplate.ts
var fillPathTemplate = (template, values) => values === void 0 ? template : Object.entries(values).reduce((accumulator, [key, value]) => {
const re = new RegExp(`{${key}}`, "g");
return accumulator.replace(re, value);
}, template);
// src/utils/isDefined.ts
var isDefined = (value) => value !== void 0;
// src/utils/chunkEntriesBatch.ts
var MaxSizeExceededError = class extends Error {
constructor(message) {
super(message);
this.name = "MaxSizeExceededError";
}
};
var chunkEntriesBatch = ({
entries,
computeEntrySize,
maxBatchSize,
maxBatchLength
}) => entries.reduce(
(chunkedEntriesAccumulator, entry, index) => {
const { chunkedEntries, lastChunkSize, lastChunkLength } = chunkedEntriesAccumulator;
const entrySize = computeEntrySize(entry);
if (entrySize > maxBatchSize) {
throw new MaxSizeExceededError(
`Entry ${index} size is ${entrySize}b exceeds the maximum batch size of ${maxBatchSize}b.
The whole operation has been cancelled.`
);
}
if (lastChunkSize + entrySize > maxBatchSize || lastChunkLength === maxBatchLength) {
return {
chunkedEntries: [...chunkedEntries, [entry]],
lastChunkSize: entrySize,
lastChunkLength: 1
};
}
const lastChunk = chunkedEntries.pop() ?? [];
return {
chunkedEntries: [...chunkedEntries, [...lastChunk, entry]],
lastChunkSize: lastChunkSize + entrySize,
lastChunkLength: lastChunkLength + 1
};
},
{
chunkedEntries: [],
lastChunkSize: 0,
lastChunkLength: 0
}
).chunkedEntries;
// src/contracts/apiGateway/features/requestParameters.ts
var getRequestParameters = (contract, requestArguments) => {
const { pathParameters, queryStringParameters, headers, body } = requestArguments;
const path = typeof pathParameters !== "undefined" ? fillPathTemplate(contract.path, pathParameters) : contract.path;
return (0, import_omitBy2.default)(
{
method: contract.method,
path,
body,
queryStringParameters: (0, import_omitBy2.default)(
queryStringParameters,
import_isUndefined2.default
),
headers: { ...headers, "Content-Type": "application/json" }
},
import_isUndefined2.default
);
};
// src/contracts/apiGateway/features/axiosRequest.ts
var getAxiosRequest = async (contract, axiosClient, requestArguments) => {
const { path, method, queryStringParameters, body, headers } = getRequestParameters(contract, requestArguments);
return axiosClient.request({
method,
url: path,
headers,
data: body,
params: queryStringParameters
});
};
// src/contracts/apiGateway/utils/apiGatewayProxyTransformers.ts
var proxyEventToHandlerEvent = ({
requestContext,
body: proxyEventBody = null,
headers,
pathParameters,
queryStringParameters
}) => {
return {
requestContext,
body: proxyEventBody !== null ? JSON.parse(proxyEventBody) : void 0,
headers,
pathParameters,
queryStringParameters: queryStringParameters ?? {}
};
};
var handlerResponseToProxyResult = ({
statusCode,
headers,
body
}) => {
const contentTypeHeader = body !== void 0 ? { "Content-Type": "application/json" } : void 0;
return {
statusCode,
body: body !== void 0 ? JSON.stringify(body) : "",
headers: {
...contentTypeHeader,
...headers
}
};
};
// src/contracts/apiGateway/utils/convertJsonSchemaToValidOAS3.ts
var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"), 1);
var import_mapValues = __toESM(require("lodash/mapValues.js"), 1);
var isArrayOfString = (array) => Array.isArray(array) && array.every((element) => typeof element === "string");
var convertNullableSchema = (schema) => {
if (schema.type?.includes("null")) {
if (isArrayOfString(schema.type)) {
const filteredSchemaType = schema.type.filter((type) => type !== "null");
if (filteredSchemaType && filteredSchemaType.length === 1) {
schema.type = filteredSchemaType[0];
}
} else {
delete schema.type;
}
schema.nullable = true;
}
};
var convertArraySchemaPropertiesToValidOAS3 = (schema) => {
if (schema.items) {
if (Array.isArray(schema.items)) {
schema.items = schema.items.map(convertJsonSchemaToValidOAS3);
} else {
schema.items = convertJsonSchemaToValidOAS3(schema.items);
}
}
};
var convertObjectSchemaPropertiesToValidOAS3 = (schema) => {
if (schema.properties) {
schema.properties = (0, import_mapValues.default)(
schema.properties,
convertJsonSchemaToValidOAS3
);
}
if (schema.additionalProperties) {
schema.additionalProperties = convertJsonSchemaToValidOAS3(
// @ts-expect-error problem with recursively typing, fully unit tested
schema.additionalProperties
);
}
if (schema.patternProperties) {
schema.patternProperties = (0, import_mapValues.default)(
// @ts-expect-error problem with recursively typing, fully unit tested
schema.patternProperties,
convertJsonSchemaToValidOAS3
);
}
};
var convertJsonSchemaToValidOAS3 = (initialSchema) => {
if (typeof initialSchema === "boolean") {
return initialSchema;
}
const schema = (0, import_cloneDeep.default)(initialSchema);
if (schema.examples && schema.examples[0] !== void 0) {
schema.example = schema.examples[0];
}
delete schema.examples;
if (schema.const) {
schema.enum = [schema.const];
delete schema.const;
}
convertNullableSchema(schema);
convertArraySchemaPropertiesToValidOAS3(schema);
convertObjectSchemaPropertiesToValidOAS3(schema);
if (schema.anyOf) {
schema.anyOf = schema.anyOf.map(convertJsonSchemaToValidOAS3);
}
if (schema.oneOf) {
schema.oneOf = schema.oneOf.map(convertJsonSchemaToValidOAS3);
}
if (schema.allOf) {
schema.allOf = schema.allOf.map(convertJsonSchemaToValidOAS3);
}
if (schema.not) {
schema.not = convertJsonSchemaToValidOAS3(schema.not);
}
if (schema.definitions) {
schema.definitions = (0, import_mapValues.default)(
// @ts-expect-error problem with recursively typing, fully unit tested
schema.definitions,
convertJsonSchemaToValidOAS3
);
}
return schema;
};
// src/contracts/apiGateway/utils/combineUrls.ts
var combineUrls = (path, baseUrl) => {
const stringBaseUrl = baseUrl instanceof URL ? baseUrl.toString() : baseUrl;
const pathWithoutLeadingSlash = path.replace(/^\/+/, "");
const baseUrlWithTrailingSlash = stringBaseUrl.replace(/\/+$/, "") + "/";
return new URL(pathWithoutLeadingSlash, baseUrlWithTrailingSlash);
};
// src/contracts/apiGateway/features/fetchRequest.ts
var getFetchRequest = async (contract, fetchFunction, options) => {
const { path, method, queryStringParameters, body, headers } = getRequestParameters(contract, options);
let url;
const searchString = new URLSearchParams(queryStringParameters).toString();
if (options.baseUrl !== void 0) {
url = combineUrls(path, options.baseUrl);
url.search = searchString;
} else {
url = `${path}?${searchString}`;
}
const response = await fetchFunction(url, {
method,
headers,
body: JSON.stringify(body)
});
return {
statusCode: response.status,
body: await response.json()
};
};
// src/contracts/apiGateway/features/fullContractSchema.ts
var import_isUndefined3 = __toESM(require("lodash/isUndefined.js"), 1);
var import_omitBy3 = __toESM(require("lodash/omitBy.js"), 1);
var getFullContractSchema = (contract) => {
const properties = {
contractId: { const: contract.id },
contractType: { const: contract.integrationType },
path: { const: contract.path },
method: { const: contract.method },
...(0, import_omitBy3.default)(
{
pathParameters: contract.pathParametersSchema,
queryStringParameters: contract.queryStringParametersSchema,
headers: contract.headersSchema,
body: contract.bodySchema,
outputs: {
type: "object",
properties: contract.outputSchemas,
required: Object.keys(contract.outputSchemas)
}
},
import_isUndefined3.default
)
};
return {
type: "object",
// @ts-ignore type inference does not work here
properties,
// @ts-ignore type inference does not work here
required: Object.keys(properties),
additionalProperties: false
};
};
// src/contracts/apiGateway/features/handleRouter.ts
var handle = (router, options) => {
return async (event, ...otherArgs) => {
if (options?.logger === true) {
console.log("event", event);
}
const matchedRoute = router.match(event);
if (matchedRoute === null) {
return Promise.resolve({
statusCode: 404,
body: "Not found"
});
}
const [handler, newEvent] = matchedRoute;
return handler(newEvent, ...otherArgs);
};
};
// src/contracts/apiGateway/features/lambdaHandler.ts
var import_http_errors = __toESM(require("http-errors"), 1);
// src/types/http.ts
var HttpStatusCodes = /* @__PURE__ */ ((HttpStatusCodes2) => {
HttpStatusCodes2[HttpStatusCodes2["ACCEPTED"] = 202] = "ACCEPTED";
HttpStatusCodes2[HttpStatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
HttpStatusCodes2[HttpStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
HttpStatusCodes2[HttpStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
HttpStatusCodes2[HttpStatusCodes2["CONTINUE"] = 100] = "CONTINUE";
HttpStatusCodes2[HttpStatusCodes2["CREATED"] = 201] = "CREATED";
HttpStatusCodes2[HttpStatusCodes2["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED";
HttpStatusCodes2[HttpStatusCodes2["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY";
HttpStatusCodes2[HttpStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
HttpStatusCodes2[HttpStatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
HttpStatusCodes2[HttpStatusCodes2["GONE"] = 410] = "GONE";
HttpStatusCodes2[HttpStatusCodes2["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED";
HttpStatusCodes2[HttpStatusCodes2["IM_A_TEAPOT"] = 418] = "IM_A_TEAPOT";
HttpStatusCodes2[HttpStatusCodes2["INSUFFICIENT_SPACE_ON_RESOURCE"] = 419] = "INSUFFICIENT_SPACE_ON_RESOURCE";
HttpStatusCodes2[HttpStatusCodes2["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE";
HttpStatusCodes2[HttpStatusCodes2["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
HttpStatusCodes2[HttpStatusCodes2["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED";
HttpStatusCodes2[HttpStatusCodes2["LOCKED"] = 423] = "LOCKED";
HttpStatusCodes2[HttpStatusCodes2["METHOD_FAILURE"] = 420] = "METHOD_FAILURE";
HttpStatusCodes2[HttpStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
HttpStatusCodes2[HttpStatusCodes2["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
HttpStatusCodes2[HttpStatusCodes2["MOVED_TEMPORARILY"] = 302] = "MOVED_TEMPORARILY";
HttpStatusCodes2[HttpStatusCodes2["MULTI_STATUS"] = 207] = "MULTI_STATUS";
HttpStatusCodes2[HttpStatusCodes2["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES";
HttpStatusCodes2[HttpStatusCodes2["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED";
HttpStatusCodes2[HttpStatusCodes2["NO_CONTENT"] = 204] = "NO_CONTENT";
HttpStatusCodes2[HttpStatusCodes2["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION";
HttpStatusCodes2[HttpStatusCodes2["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE";
HttpStatusCodes2[HttpStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
HttpStatusCodes2[HttpStatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
HttpStatusCodes2[HttpStatusCodes2["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
HttpStatusCodes2[HttpStatusCodes2["OK"] = 200] = "OK";
HttpStatusCodes2[HttpStatusCodes2["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT";
HttpStatusCodes2[HttpStatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
HttpStatusCodes2[HttpStatusCodes2["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT";
HttpStatusCodes2[HttpStatusCodes2["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
HttpStatusCodes2[HttpStatusCodes2["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
HttpStatusCodes2[HttpStatusCodes2["PROCESSING"] = 102] = "PROCESSING";
HttpStatusCodes2[HttpStatusCodes2["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED";
HttpStatusCodes2[HttpStatusCodes2["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE";
HttpStatusCodes2[HttpStatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
HttpStatusCodes2[HttpStatusCodes2["REQUEST_TOO_LONG"] = 413] = "REQUEST_TOO_LONG";
HttpStatusCodes2[HttpStatusCodes2["REQUEST_URI_TOO_LONG"] = 414] = "REQUEST_URI_TOO_LONG";
HttpStatusCodes2[HttpStatusCodes2["REQUESTED_RANGE_NOT_SATISFIABLE"] = 416] = "REQUESTED_RANGE_NOT_SATISFIABLE";
HttpStatusCodes2[HttpStatusCodes2["RESET_CONTENT"] = 205] = "RESET_CONTENT";
HttpStatusCodes2[HttpStatusCodes2["SEE_OTHER"] = 303] = "SEE_OTHER";
HttpStatusCodes2[HttpStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
HttpStatusCodes2[HttpStatusCodes2["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
HttpStatusCodes2[HttpStatusCodes2["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT";
HttpStatusCodes2[HttpStatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
HttpStatusCodes2[HttpStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
HttpStatusCodes2[HttpStatusCodes2["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS";
HttpStatusCodes2[HttpStatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
HttpStatusCodes2[HttpStatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
HttpStatusCodes2[HttpStatusCodes2["USE_PROXY"] = 305] = "USE_PROXY";
HttpStatusCodes2[HttpStatusCodes2["MISDIRECTED_REQUEST"] = 421] = "MISDIRECTED_REQUEST";
return HttpStatusCodes2;
})(HttpStatusCodes || {});
// src/contracts/apiGateway/features/lambdaHandler.ts
var defaultOptions = {
validateInput: true,
validateOutput: true
};
var getApiGatewayHandler = (contract, options) => (handler) => {
const { validateInput, validateOutput, ajv } = {
...defaultOptions,
...options
};
const { inputSchema, outputSchemas } = contract;
let inputValidator = void 0;
const outputValidators = {};
if (validateInput || validateOutput) {
if (validateInput) {
inputValidator = ajv.compile(inputSchema);
}
if (validateOutput) {
Object.keys(outputSchemas).forEach((statusCode) => {
const outputSchema = outputSchemas[statusCode];
outputValidators[statusCode] = outputSchema !== void 0 ? ajv.compile(outputSchema) : void 0;
});
}
}
return async (event, context, callback, ...additionalArgs) => {
try {
const parsedEvent = proxyEventToHandlerEvent(event);
if (inputValidator !== void 0) {
if (!inputValidator(parsedEvent)) {
console.error("Error: Invalid input");
console.error(JSON.stringify(inputValidator.errors, null, 2));
if (options.returnValidationErrors === true) {
throw (0, import_http_errors.default)(
400,
JSON.stringify({
message: "Invalid input",
errors: inputValidator.errors
})
);
}
throw (0, import_http_errors.default)(400, "Invalid input");
}
}
const handlerResponse = await handler(
parsedEvent,
context,
callback,
...additionalArgs
);
const outputValidator = outputValidators[handlerResponse.statusCode];
if (outputValidator !== void 0) {
if (!outputValidator(handlerResponse.body)) {
console.error("Error: Invalid output");
console.error(JSON.stringify(outputValidator.errors, null, 2));
throw (0, import_http_errors.default)(400, "Invalid output");
}
}
return handlerResponseToProxyResult(
handlerResponse
);
} catch (error) {
console.error(error);
if ((0, import_http_errors.isHttpError)(error) && error.expose) {
return {
headers: error.headers,
statusCode: error.statusCode,
body: error.message
};
}
return {
statusCode: 500 /* INTERNAL_SERVER_ERROR */,
body: "Internal server error"
};
}
};
};
var getLambdaHandler = (_contract) => (handler) => handler;
// src/contracts/apiGateway/features/lambdaTrigger.ts
var getApiGatewayTrigger = (contract, ...[additionalConfig]) => {
const key = contract.integrationType === "httpApi" ? "httpApi" : "http";
return {
[key]: {
...additionalConfig,
path: contract.path,
method: contract.method
}
};
};
// src/contracts/apiGateway/features/openApiDocumentation.ts
var import_isUndefined4 = __toESM(require("lodash/isUndefined.js"), 1);
var import_omitBy4 = __toESM(require("lodash/omitBy.js"), 1);
var getContractDocumentation = (contract) => {
const initialDocumentation = {
responses: {}
};
const definedOutputSchema = (0, import_omitBy4.default)(contract.outputSchemas, import_isUndefined4.default);
const contractDocumentation = Object.keys(definedOutputSchema).reduce(
(config, responseCode) => {
const schema = definedOutputSchema[responseCode];
if (schema === void 0) {
return config;
}
const openApiSchema = convertJsonSchemaToValidOAS3(schema);
return {
...config,
responses: {
...config.responses,
[responseCode]: {
description: `Response: ${responseCode}`,
content: {
"application/json": {
schema: openApiSchema
}
}
}
}
};
},
initialDocumentation
);
if (contract.pathParametersSchema?.properties !== void 0) {
contractDocumentation.parameters = [
...Object.entries(contract.pathParametersSchema.properties).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: "path",
schema: convertJsonSchemaToValidOAS3(variableDefinition),
required: contract.pathParametersSchema?.required?.includes(variableName) ?? false
})
),
...contractDocumentation.parameters ?? []
];
}
if (contract.queryStringParametersSchema?.properties !== void 0) {
contractDocumentation.parameters = [
...Object.entries(contract.queryStringParametersSchema.properties).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: "query",
schema: convertJsonSchemaToValidOAS3(variableDefinition),
required: contract.queryStringParametersSchema?.required?.includes(
variableName
) ?? false
})
),
...contractDocumentation.parameters ?? []
];
}
if (contract.headersSchema?.properties !== void 0) {
contractDocumentation.parameters = [
...Object.entries(contract.headersSchema.properties).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: "header",
schema: convertJsonSchemaToValidOAS3(variableDefinition),
required: contract.headersSchema?.required?.includes(variableName) ?? false
})
),
...contractDocumentation.parameters ?? []
];
}
if (contract.bodySchema !== void 0) {
contractDocumentation.requestBody = {
content: {
"application/json": {
schema: convertJsonSchemaToValidOAS3(contract.bodySchema)
}
}
};
}
return {
path: contract.path,
method: contract.method.toLowerCase(),
documentation: contractDocumentation
};
};
// src/contracts/apiGateway/features/router.ts
var import_ajv = __toESM(require("ajv"), 1);
var import_get = __toESM(require("lodash/get.js"), 1);
var import_path_to_regexp = require("path-to-regexp");
var SwarmionRouter = class {
consumers = [];
ajv = new import_ajv.default();
constructor({ ajv } = {}) {
if (ajv) {
this.ajv = ajv;
}
}
add(contract, options) {
return (handler) => {
this.consumers.push([
contract,
// @ts-expect-error - This is a valid handler
getApiGatewayHandler(contract, { ...options, ajv: this.ajv })(handler)
]);
};
}
match(event) {
if (!isValidHttpApiGatewayEvent(event) && !isValidRestApiGatewayEvent(event)) {
return null;
}
for (const [contract, handler] of this.consumers) {
const routerMatch = matchApiGatewayContract(contract, event);
if (routerMatch === false) {
continue;
}
return [handler, routerMatch];
}
return null;
}
};
var matchApiGatewayContract = (contract, event) => {
if (isValidRestApiGatewayEvent(event)) {
return matchRestApiGatewayContract(contract, event);
}
if (isValidHttpApiGatewayEvent(event)) {
return matchHttpApiGatewayContract(contract, event);
}
return false;
};
var matchHttpApiGatewayContract = (contract, event) => {
if (contract.integrationType !== "httpApi") {
return false;
}
return matchHttpRoute({
contract,
method: event.requestContext.http.method,
path: event.rawPath,
event
});
};
var matchRestApiGatewayContract = (contract, event) => {
if (contract.integrationType !== "restApi") {
return false;
}
return matchHttpRoute({
contract,
method: event.requestContext.httpMethod,
path: event.path,
event
});
};
var matchHttpRoute = ({
contract,
method,
path,
event
}) => {
if (method !== contract.method) {
return false;
}
const urlMatch = (0, import_path_to_regexp.match)(
contract.path.replaceAll("{", ":").replaceAll("}", "")
)(path);
if (urlMatch === false) {
return false;
}
event.pathParameters = urlMatch.params;
return event;
};
var isValidHttpApiGatewayEvent = (event) => {
return (0, import_get.default)(event, "requestContext.http") !== void 0;
};
var isValidRestApiGatewayEvent = (event) => {
return (0, import_get.default)(event, "requestContext.httpMethod") !== void 0;
};
// src/contracts/eventBridge/eventBridgeContract.ts
var EventBridgeContract = class {
id;
contractType = "eventBridge";
sources;
eventType;
payloadSchema;
/**
* a native event pattern:
* ```ts
* {
* source: string[];
* 'detail-type': string[];
* }
* ```
*/
nativePattern;
/**
* an event pattern accepted by the CDK:
* ```ts
* {
* source: string[];
* detailType: string[];
* }
* ```
*/
pattern;
/**
* Builds a new EventBridgeContract contract
*/
constructor({
id,
sources,
eventType,
payloadSchema
}) {
this.id = id;
this.sources = sources;
this.eventType = eventType;
this.payloadSchema = payloadSchema;
this.nativePattern = {
// @ts-expect-error it does not matter that sources are readonly
source: sources,
"detail-type": [eventType]
};
this.pattern = {
// @ts-expect-error it does not matter that sources are readonly
source: sources,
detailType: [eventType]
};
}
};
// src/contracts/eventBridge/features/lambdaTrigger.ts
var getEventBridgeTrigger = (contract, additionalTriggerArgs) => ({
eventBridge: {
pattern: {
source: contract.sources,
"detail-type": [contract.eventType]
},
...additionalTriggerArgs
}
});
// src/contracts/eventBridge/features/fullContractSchema.ts
var getFullContractSchema2 = (contract) => {
const properties = {
id: { const: contract.id },
contractType: { const: contract.contractType },
sources: { enum: contract.sources },
eventType: { const: contract.eventType },
payloadSchema: contract.payloadSchema
};
return {
type: "object",
properties,
required: ["id", "contractType", "sources", "eventType", "payloadSchema"],
additionalProperties: false
};
};
// src/contracts/eventBridge/features/lambdaHandler.ts
var defaultOptions2 = {
validatePayload: true
};
var getEventBridgeHandler = (contract, options) => (handler) => {
const { validatePayload, ajv } = { ...defaultOptions2, ...options };
let payloadValidator = void 0;
if (validatePayload) {
payloadValidator = ajv.compile(contract.payloadSchema);
}
return async (event, context, callback, ...additionalArgs) => {
if (payloadValidator !== void 0) {
if (!payloadValidator(event.detail)) {
console.error("Error: Invalid payload");
console.error(JSON.stringify(payloadValidator.errors, null, 2));
throw new Error("Invalid payload");
}
}
const handlerResponse = await handler(
event,
context,
callback,
...additionalArgs
);
return handlerResponse;
};
};
// src/contracts/eventBridge/features/putEvent.ts
var import_client_eventbridge = require("@aws-sdk/client-eventbridge");
var buildPutEvent = (contract, {
eventBusName,
source,
eventBridgeClient,
throwOnFailure = true
}) => async (payload) => {
const event = {
Detail: JSON.stringify(payload),
DetailType: contract.eventType,
Source: source,
EventBusName: typeof eventBusName === "string" ? eventBusName : await eventBusName()
};
const command = new import_client_eventbridge.PutEventsCommand({
Entries: [event]
});
const { FailedEntryCount, Entries } = await eventBridgeClient.send(command);
if ((FailedEntryCount ?? 0) > 0 && throwOnFailure) {
const failedEntry = Entries?.[0];
console.error(
`Failed to send this event: ${JSON.stringify(event, null, 2)}`
);
throw new Error(
`Failed to send event. Error: ${failedEntry?.ErrorMessage} (${failedEntry?.ErrorCode})`
);
}
return { failedEntryCount: FailedEntryCount, entry: Entries?.[0] };
};
// src/contracts/eventBridge/features/putEvents.ts
var import_client_eventbridge2 = require("@aws-sdk/client-eventbridge");
var import_p_queue = __toESM(require("p-queue"), 1);
// src/utils/fixESMInteropIssue.ts
var fixESMInteropIssue = (object) => {
if (object && object.default) {
return object.default;
}
return object;
};
// src/contracts/eventBridge/features/putEvents.ts
var PQueue = fixESMInteropIssue(import_p_queue.default);
var MAX_BATCH_SIZE = 256e3;
var MAX_BATCH_LENGTH = 10;
var defaultOptions3 = {
throughputCallsPerSecond: 400,
// Minimal default throughput. Some regions can have up to 10_000. See: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-quota.html
throwOnFailure: true
};
var OneSecondInMillis = 1e3;
var getThroughputQueueConfig = (throughputCallsPerSecond) => ({
intervalCap: throughputCallsPerSecond,
interval: OneSecondInMillis
});
var computeEventSize = (event) => {
let size = 0;
if (event.Time) {
size += 14;
}
if (event.Detail) {
size += Buffer.byteLength(event.Detail, "utf8");
}
if (event.DetailType) {
size += Buffer.byteLength(event.DetailType, "utf8");
}
if (event.Source) {
size += Buffer.byteLength(event.Source, "utf8");
}
if (event.Resources) {
event.Resources.forEach((resource) => Buffer.byteLength(resource, "utf8"));
}
return size;
};
var sendAllEventsBatchedWithControlledThroughput = async (events, options) => {
const { eventBridgeClient, throughputCallsPerSecond } = options;
const queue = new PQueue(getThroughputQueueConfig(throughputCallsPerSecond));
const batches = chunkEntriesBatch({
entries: events,
computeEntrySize: computeEventSize,
maxBatchSize: MAX_BATCH_SIZE,
maxBatchLength: MAX_BATCH_LENGTH
});
const results = await queue.addAll(
batches.map(
(batch) => async () => await eventBridgeClient.send(
new import_client_eventbridge2.PutEventsCommand({
Entries: batch
})
)
)
);
return {
failedEntryCount: results.reduce(
(sum, { FailedEntryCount }) => sum + (FailedEntryCount ?? 0),
0
),
entries: results.map(({ Entries }) => Entries ?? []).flat()
};
};
var buildPutEvents = (contract, args) => async (payloads) => {
const argsWithDefaults = {
...defaultOptions3,
...args
};
const {
source,
eventBusName: eventBusNameGetter,
throwOnFailure
} = argsWithDefaults;
const eventBusName = typeof eventBusNameGetter === "string" ? eventBusNameGetter : await eventBusNameGetter();
const events = payloads.map((payload) => ({
Detail: JSON.stringify(payload),
DetailType: contract.eventType,
Source: source,
EventBusName: eventBusName
}));
const { failedEntryCount, entries } = await sendAllEventsBatchedWithControlledThroughput(
events,
argsWithDefaults
);
if (failedEntryCount > 0 && throwOnFailure) {
entries.forEach((entry) => {
if (entry.ErrorCode) {
console.error(
`Failed to send event. Error: ${entry.ErrorMessage} (${entry.ErrorCode})`
);
}
});
throw new Error(`Failed to send ${failedEntryCount} events.`);
}
return { failedEntryCount, entries };
};
// src/contracts/eventBridge/features/multipleLambdaHandler.ts
var defaultOptions4 = {
validatePayload: true
};
var getMultipleEventBridgeHandler = (contracts, options) => (handler) => {
const { validatePayload, ajv } = { ...defaultOptions4, ...options };
let payloadValidator = void 0;
if (validatePayload) {
payloadValidator = ajv.compile({
anyOf: contracts.map((contract) => contract.payloadSchema)
});
}
return async (event, context, callback, ...additionalArgs) => {
if (payloadValidator !== void 0) {
if (!payloadValidator(event.detail)) {
console.error("Error: Invalid payload");
console.error(JSON.stringify(payloadValidator.errors, null, 2));
throw new Error("Invalid payload");
}
}
const handlerResponse = await handler(
event,
context,
callback,
...additionalArgs
);
return handlerResponse;
};
};
// src/contracts/SQS/sqsContract.ts
var SQSContract = class {
id;
contractType = "SQS";
messageBodySchema;
messageAttributesSchema;
/**
* Builds a new SQSContract contract
*/
constructor({
id,
messageBodySchema,
messageAttributesSchema
}) {
this.id = id;
this.messageBodySchema = messageBodySchema;
this.messageAttributesSchema = messageAttributesSchema ?? {};
}
};
// src/contracts/SQS/features/fullContractSchema.ts
var getFullContractSchema3 = (contract) => ({
type: "object",
properties: {
id: { const: contract.id },
contractType: { const: contract.contractType },
messageBodySchema: contract.messageBodySchema,
...Object.keys(contract.messageAttributesSchema).length > 0 ? { messageAttributesSchema: contract.messageAttributesSchema } : {}
},
required: ["id", "contractType", "messageBodySchema"],
additionalProperties: false
});
// src/contracts/SQS/utils/parseMessageAttributes.ts
var parseDataType = (dataType) => dataType.split(".")[0];
var parseMessageAttribute = (attribute) => {
const dataType = parseDataType(attribute.dataType);
switch (dataType) {
case "Binary":
return attribute.binaryValue;
case "String":
return attribute.stringValue;
case "Number":
return Number(attribute.stringValue);
}
};
var parseMessageAttributes = (messageAttributes) => Object.entries(messageAttributes).reduce(
(acc, [attributeName, attribute]) => {
return {
...acc,
[attributeName]: parseMessageAttribute(attribute)
};
},
{}
);
// src/contracts/SQS/utils/parseRecord.ts
var parseRecord = ({
bodyParser
}) => ({ body, messageAttributes, ...rest }, index) => {
try {
return {
...rest,
body: bodyParser !== void 0 ? bodyParser(body) : body,
// Validation is done after
messageAttributes: parseMessageAttributes(
messageAttributes
)
// Validation is done after
};
} catch (e) {
console.error(
`Error while parsing SQS Record at index ${index}. Please use the \`logRawEvent\` option to debug`
);
throw e;
}
};
// src/contracts/SQS/utils/getRecordsValidator.ts
var getSchema = (contract, { validateBody, validateAttributes }) => ({
type: "array",
items: {
type: "object",
properties: {
...validateBody === true && { body: contract.messageBodySchema },
...validateAttributes === true && {
messageAttributes: contract.messageAttributesSchema
}
},
required: [
...validateBody !== void 0 ? ["body"] : [],
...validateAttributes !== void 0 ? ["messageAttributes"] : []
],
additionalProperties: true
}
});
var getRecordsValidationSchema = (contract, options) => {
const { validateBody, validateAttributes } = options;
if ((validateBody === void 0 || !validateBody) && (validateAttributes === void 0 || !validateAttributes)) {
return void 0;
}
return getSchema(contract, options);
};
var getRecordsValidator = (contract, options) => {
const recordsValidationSchema = getRecordsValidationSchema(contract, options);
return recordsValidationSchema !== void 0 ? options.ajv?.compile(recordsValidationSchema) : void 0;
};
var getBodyValidator = (contract, options) => {
if (options.validateMessage === false) {
return;
}
return options.ajv.compile(contract.messageBodySchema);
};
var getMessageAttributesValidator = (contract, options) => {
if (options.validateMessage === false || Object.keys(contract.messageAttributesSchema).length === 0) {
return;
}
return options.ajv.compile(contract.messageAttributesSchema);
};
// src/contracts/SQS/utils/allRecordsHandler.ts
var getAllRecordsHandler = (handleRecord, context, callback, additionalArgs) => async (event) => {
const { records } = event;
const handleRecordsResults = await Promise.allSettled(
records.map(
(record) => handleRecord(record, context, callback, ...additionalArgs)
)
);
const recordsErrors = handleRecordsResults.map((recordResult, index) => ({
...recordResult,
record: records[index]
})).filter(
(record) => record.status === "rejected"
);
recordsErrors.forEach(({ record, reason }) => {
console.error(
`Error during the processing of the message ${record.messageId}`,
{ record },
reason
);
console.error(
`Message ${record.messageId} will be marked as failed and be retried according to the SQS retry configuration`
);
});
return {
batchItemFailures: recordsErrors.map(({ record: { messageId } }) => ({
itemIdentifier: messageId
}))
};
};
// src/contracts/SQS/utils/serializeMessageAttributes.ts
var serializeMessageAttribute = (attributeValue, attributeSchema) => {
if (typeof attributeSchema === "boolean") {
throw new Error("Invalid contract messageAttributesSchema");
}
const { type } = attributeSchema;
switch (type) {
case "string":
return { StringValue: attributeValue, DataType: "String" };
case "number":
return {
StringValue: attributeValue.toString(),
DataType: "Number"
};
default:
throw new Error(
"Invalid messageAttributesSchema. Only string and number are currently supported"
);
}
};
var serializeMessageAttributes = (messageAttributes, contract) => {
const { properties } = contract.messageAttributesSchema;
if (properties === void 0) {
throw new Error("Invalid contract messageAttributesSchema");
}
return Object.entries(properties).reduce(
(acc, [attributeName, attributeSchema]) => {
if (messageAttributes[attributeName] === void 0) {
return acc;
}
return {
...acc,
[attributeName]: serializeMessageAttribute(
messageAttributes[attributeName],
attributeSchema
)
};
},
{}
);
};
// src/contracts/SQS/utils/messagesValidation.ts
var validateMessage = ({
contract,
message,
options,
index
}) => {
const { body, messageAttributes } = message;
const bodyValidator = getBodyValidator(contract, options);
if (bodyValidator !== void 0 && !bodyValidator(body)) {
console.error(
`Error: Invalid message body ${index !== void 0 ? `at index ${index}` : ""}`,
JSON.stringify(bodyValidator.errors, null, 2)
);
throw new Error("Error: Invalid message body");
}
if (messageAttributes === void 0) {
return;
}
const messageAttributesValidator = getMessageAttributesValidator(
contract,
options
);
if (messageAttributesValidator !== void 0 && !messageAttributesValidator(messageAttributes)) {
console.error(
`Error: Invalid message attributes ${index !== void 0 ? `at index ${index}` : ""}`,
JSON.stringify(messageAttributesValidator.errors, null, 2)
);
throw new Error("Error: Invalid message attributes");
}
};
var validateMessages = ({
contract,
messages,
options
}) => {
const errors = [];
messages.forEach((message, index) => {
try {
validateMessage({ contract, message, options, index });
} catch (error) {
errors.push(error);
}
});
if (errors.length > 0) {
throw new Error("Error: Invalid message");
}
};
// src/contracts/SQS/utils/serializeMessage.ts
var import_ulid = require("ulid");
var serializeMessage = ({
contract,
bodySerializer
}) => ({
body,
messageAttributes,
...restMessage
}) => ({
Id: (0, import_ulid.ulid)(),
MessageBody: bodySerializer !== void 0 ? bodySerializer(body) : body,
...messageAttributes !== void 0 && messageAttributes !== null ? {
MessageAttributes: serializeMessageAttributes(
messageAttributes,
// messageAttributes generic infered type has {} as type. I don't know why
contract
)
} : {},
...restMessage
});
// src/contracts/SQS/utils/delays.ts
var wait = async (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
var getExponentialBackoffDelay = (retryCount, baseDelayMs = 1) => baseDelayMs * 10 ** retryCount;
// src/contracts/SQS/features/lambdaHandler.ts
var defaultOptions5 = {
bodyParser: (body) => JSON.parse(body),
validateBody: true,
validateAttributes: true,
handleBatchedRecords: true,
logRawEvent: false
};
var getSQSHandler = (contract, options) => (handler) => {
const internalOptions = {
...defaultOptions5,
...options
};
const recordsValidator = getRecordsValidator(contract, internalOptions);
return async (event, context, callback, ...additionalArgs) => {
if (internalOptions.logRawEvent) {
console.debug("Raw event:", JSON.stringify(event, null, 2));
}
const { Records } = event;
if (Records === void 0) {
throw new Error(
"The provided event is not a valid SQS event. It has no Records attribute. Please use `logRawEvent` option to debug this"
);
}
const parsedRecords = Records.map(
parseRecord(internalOptions)
);
if (recordsValidator !== void 0 && !recordsValidator(parsedRecords)) {
console.error("Error: Invalid records");
console.error(JSON.stringify(recordsValidator.errors, null, 2));
throw new Error("Invalid records");
}
if (!internalOptions.handleBatchedRecords) {
return handler({ records: parsedRecords }, context, callback, ...additionalArgs);
}
const allRecordsHandler = getAllRecordsHandler(
// handleRecords is true, handler is SwarmionSQSHandler. Typescript is not able to infer this
handler,
context,
callback,
additionalArgs
);
return allRecordsHandler({ records: parsedRecords });
};
};
// src/contracts/SQS/features/sendMessage.ts
var import_client_sqs = require("@aws-sdk/client-sqs");
var import_omit = __toESM(require("lodash/omit.js"), 1);
var defaultOptions6 = {
validateMessage: true,
bodySerializer: JSON.stringify
};
var buildSendMessage = (contract, options) => async (message) => {
const internalOptions = {
...defaultOptions6,
...options
};
const { queueUrl, sqsClient, bodySerializer } = internalOptions;
validateMessage({ contract, message, options: internalOptions });
const command = new import_client_sqs.SendMessageCommand({
QueueUrl: typeof queueUrl === "string" ? queueUrl : await queueUrl(),
...(0, import_omit.default)(
serializeMessage({ contract, bodySerializer })(message),
"Id"
)
});
return sqsClient.send(command);
};
// src/contracts/SQS/features/sendMessages.ts
var import_client_sqs2 = require("@aws-sdk/client-sqs");
var import_p_queue2 = __toESM(require("p-queue"), 1);
var PQueue2 = fixESMInteropIssue(import_p_queue2.default);
var InfiniteThroughput = 0;
var MAX_BATCH_SIZE2 = 256e3;
var MAX_BATCH_LENGTH2 = 10;
var defaultOptions7 = {
validateMessage: true,
bodySerializer: JSON.stringify,
maxRetries: 3,
baseDelay: 100,
throwOnFailedBatch: true,
throughputCallsPerSecond: InfiniteThroughput
};
var getThroughputQueueConfig2 = (throughputCallsPerSecond) => throughputCallsPerSecond === InfiniteThroughput ? {} : {
intervalCap: throughputCallsPerSecond,
interval: 1e3
};
var computeMessageSize = (message) => Buffer.byteLength(JSON.stringify(message), "utf8");
var sendAllMessagesBatchedWithControlledThroughput = async (messages, options) => {
const { sqsClient, queueUrl, throughputCallsPerSecond } = options;
const queue = new PQueue2(getThroughputQueueConfig2(throughputCallsPerSecond));
const batches = chunkEntriesBatch({
entries: messages,
computeEntrySize: computeMessageSize,
maxBatchSize: MAX_BATCH_SIZE2,
maxBatchLength: MAX_BATCH_LENGTH2
});
const results = await queue.addAll(
batches.map(
(batch) => async () => await sqsClient.send(
new import_client_sqs2.SendMessageBatchCommand({
QueueUrl: queueUrl,
Entries: batch
})
)
)
);
return { failedItems: results.map(({ Failed }) => Failed ?? []).flat() };
};
var sendBatchedMessages = async ({
messages,
options
}) => {
const {
queueUrl: queueUrlOrGetter,
baseDelay,
maxRetries,
throwOnFailedBatch
} = options;
const queueUrl = typeof queueUrlOrGetter === "string" ? queueUrlOrGetter : await queueUrlOrGetter();
let unprocessedMessages = messages;
let failedItems = [];
let attempts = 0;
do {
({ failedItems } = await sendAllMessagesBatchedWithControlledThroughput(
unprocessedMessages,
{
...options,
queueUrl
}
));
if (failedItems.length > 0) {
const failedIds = failedItems.map(({ Id }) => Id);
unprocessedMessages = messages.filter(({ Id }) => failedIds.includes(Id));
attempts++;
console.warn(
`Attempt ${attempts}: Failed to process ${failedItems.length} items. Retrying after delay...`
);
if (attempts < maxRetries) {
const delayDuration = getExponentialBackoffDelay(
attempts - 1,
baseDelay
);
console.info(`Delaying for ${delayDuration} ms...`);
await wait(delayDuration);
}
}
} while (failedItems.length > 0 && attempts < maxRetries);
if (failedItems.length > 0 && throwOnFailedBatch) {
console.error("Failed items:", JSON.stringify(failedItems, null, 2));
throw new Error(
`Failed to send ${failedItems.length} items to SQS after ${maxRetries} attempts`
);
}
return { failedItems };
};
var buildSendMessages = (contract, options) => async (messages) => {
const internalOptions = {
...defaultOptions7,
...options
};
const { bodySerializer } = internalOptions;
validateMessages({
contract,
messages,
options: internalOptions
});
return sendBatchedMessages({
messages: messages.map(
serializeMessage({ contract, bodySerializer })
),
options: internalOptions
});
};
// src/contracts/cloudFormation/features/fullContractSchema.ts
var getFullContractSchema4 = (contract) => {
return {
type: "object",
properties: {
contractId: { const: contract.id },
contractType: { const: "cloudFormation" },