@zimic/http
Version:
Next-gen TypeScript-first HTTP utilities
1,216 lines (1,204 loc) • 52.6 kB
JavaScript
import path from 'path';
import ts3 from 'typescript';
import color3 from 'picocolors';
import fs from 'fs';
// ../zimic-utils/dist/data.mjs
function isDefined(value) {
return value !== void 0 && value !== null;
}
var isDefined_default = isDefined;
function isNonEmpty(value) {
return isDefined_default(value) && value !== "";
}
var isNonEmpty_default = isNonEmpty;
function isNeverType(type) {
return type.kind === ts3.SyntaxKind.NeverKeyword;
}
function isUnknownType(type) {
return type.kind === ts3.SyntaxKind.UnknownKeyword;
}
function isNullType(type) {
return type.kind === ts3.SyntaxKind.NullKeyword;
}
function createBlobType() {
return ts3.factory.createTypeReferenceNode("Blob");
}
function createNullType() {
return ts3.factory.createLiteralTypeNode(ts3.factory.createNull());
}
function createImportSpecifier(importName) {
return ts3.factory.createImportSpecifier(false, void 0, ts3.factory.createIdentifier(importName));
}
function createImportDeclaration(importSpecifiers, moduleName, options) {
return ts3.factory.createImportDeclaration(
void 0,
ts3.factory.createImportClause(
ts3.SyntaxKind.TypeKeyword ,
void 0,
ts3.factory.createNamedImports(importSpecifiers)
),
ts3.factory.createStringLiteral(moduleName)
);
}
// src/types/schema.ts
var HTTP_METHODS = Object.freeze(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
// ../zimic-utils/dist/logging.mjs
var Logger = class _Logger {
prefix;
raw;
constructor(options = {}) {
const { prefix } = options;
this.prefix = prefix;
this.raw = prefix ? new _Logger({ ...options, prefix: void 0 }) : this;
}
logWithLevel(level, ...messages) {
if (this.prefix) {
console[level](this.prefix, ...messages);
} else {
console[level](...messages);
}
}
info(...messages) {
this.logWithLevel("log", ...messages);
}
warn(...messages) {
this.logWithLevel("warn", ...messages);
}
error(...messages) {
this.logWithLevel("error", ...messages);
}
table(headers, rows) {
const columnLengths = headers.map((header) => {
let maxValueLength = header.title.length;
for (const row of rows) {
const value = row[header.property];
if (value.length > maxValueLength) {
maxValueLength = value.length;
}
}
return maxValueLength;
});
const formattedRows = [];
const horizontalLine = columnLengths.map((length) => "\u2500".repeat(length));
formattedRows.push(horizontalLine, []);
for (let headerIndex = 0; headerIndex < headers.length; headerIndex++) {
const header = headers[headerIndex];
const columnLength = columnLengths[headerIndex];
const value = header.title;
formattedRows.at(-1)?.push(value.padEnd(columnLength, " "));
}
formattedRows.push(horizontalLine);
for (const row of rows) {
formattedRows.push([]);
for (let headerIndex = 0; headerIndex < headers.length; headerIndex++) {
const header = headers[headerIndex];
const columnLength = columnLengths[headerIndex];
const value = row[header.property];
formattedRows.at(-1)?.push(value.padEnd(columnLength, " "));
}
}
formattedRows.push(horizontalLine);
const formattedTable = formattedRows.map((row, index) => {
const isFirstLine = index === 0;
if (isFirstLine) {
return `\u250C\u2500${row.join("\u2500\u252C\u2500")}\u2500\u2510`;
}
const isLineAfterHeaders = index === 2;
if (isLineAfterHeaders) {
return `\u251C\u2500${row.join("\u2500\u253C\u2500")}\u2500\u2524`;
}
const isLastLine = index === formattedRows.length - 1;
if (isLastLine) {
return `\u2514\u2500${row.join("\u2500\u2534\u2500")}\u2500\u2518`;
}
return `\u2502 ${row.join(" \u2502 ")} \u2502`;
}).join("\n");
this.logWithLevel("log", formattedTable);
}
};
var Logger_default = Logger;
var logger = new Logger_default({
prefix: color3.cyan("[@zimic/http]")
});
function createOperationsIdentifierText(serviceName) {
return `${serviceName}Operations`;
}
function createOperationsIdentifier(serviceName) {
return ts3.factory.createIdentifier(createOperationsIdentifierText(serviceName));
}
function isOperationsDeclaration(node) {
return node !== void 0 && ts3.isInterfaceDeclaration(node) && node.name.text === "operations";
}
function isOperation(node) {
return ts3.isPropertySignature(node) && (ts3.isIdentifier(node.name) || ts3.isStringLiteral(node.name)) && node.type !== void 0 && ts3.isTypeLiteralNode(node.type);
}
function normalizeOperation(operation, context) {
if (!isOperation(operation)) {
return void 0;
}
const newType = normalizeTypeLiteralMethodType(operation.type, context);
return ts3.factory.updatePropertySignature(
operation,
operation.modifiers,
operation.name,
operation.questionToken,
newType
);
}
function normalizeOperations(operations, context) {
const newIdentifier = createOperationsIdentifier(context.serviceName);
const newMembers = operations.members.map((operation) => normalizeOperation(operation, context)).filter(isDefined_default);
return ts3.factory.updateInterfaceDeclaration(
operations,
operations.modifiers,
newIdentifier,
operations.typeParameters,
operations.heritageClauses,
newMembers
);
}
function removeOperationIfUnreferenced(operation, context) {
if (!isOperation(operation)) {
return void 0;
}
const operationName = operation.name.text;
const isReferenced = context.referencedTypes.operations.has(operationName);
if (isReferenced) {
context.referencedTypes.operations.delete(operationName);
return operation;
}
return void 0;
}
function removeUnreferencedOperations(operations, context) {
const newMembers = operations.members.map((operation) => removeOperationIfUnreferenced(operation, context)).filter(isDefined_default);
context.referencedTypes.operations.clear();
if (newMembers.length === 0) {
return void 0;
}
return ts3.factory.updateInterfaceDeclaration(
operations,
operations.modifiers,
operations.name,
operations.typeParameters,
operations.heritageClauses,
newMembers
);
}
// src/typegen/openapi/transform/methods.ts
function isMethod(node) {
return ts3.isPropertySignature(node) && ts3.isIdentifier(node.name) && node.type !== void 0 && (ts3.isTypeLiteralNode(node.type) || ts3.isIndexedAccessTypeNode(node.type));
}
function isMethodMember(node) {
return ts3.isPropertySignature(node) && ts3.isIdentifier(node.name) && node.type !== void 0 && (ts3.isTypeLiteralNode(node.type) || ts3.isIndexedAccessTypeNode(node.type) || isNeverType(node.type));
}
function isRequestMember(node) {
return ts3.isPropertySignature(node) && ts3.isIdentifier(node.name) && node.type !== void 0 && !isNeverType(node.type);
}
function isRequestHeaders(node) {
return isRequestMember(node) && node.name.text === "headers" && ts3.isTypeLiteralNode(node.type);
}
function isNormalizedRequestHeaders(node) {
return isRequestMember(node) && node.name.text === "headers" && ts3.isTypeLiteralNode(node.type);
}
function isRequestParameters(node) {
return isRequestMember(node) && node.name.text === "parameters" && ts3.isTypeLiteralNode(node.type);
}
function isContentPropertySignature(node) {
return isRequestMember(node) && node.name.text === "content" && ts3.isTypeLiteralNode(node.type);
}
function isContentMember(node) {
return ts3.isPropertySignature(node) && (ts3.isIdentifier(node.name) || ts3.isStringLiteral(node.name)) && node.type !== void 0 && !isNeverType(node.type);
}
function isResponse(node) {
return ts3.isPropertySignature(node) && (ts3.isIdentifier(node.name) || ts3.isStringLiteral(node.name) || ts3.isNumericLiteral(node.name)) && node.type !== void 0;
}
function removeRedundantNullUnionIfNecessary(type) {
const containsRedundantNullUnion = ts3.isUnionTypeNode(type) && type.types.some((type2) => {
const isNull = ts3.isLiteralTypeNode(type2) && isNullType(type2.literal);
return isNull;
}) && type.types.some((type2) => {
return ts3.isParenthesizedTypeNode(type2) && ts3.isUnionTypeNode(type2.type) && type2.type.types.some((subType) => {
const isNull = ts3.isLiteralTypeNode(subType) && isNullType(subType.literal);
return isNull;
});
});
if (!containsRedundantNullUnion) {
return type;
}
const typesWithoutRedundantNullUnion = type.types.filter((type2) => {
const isNull = ts3.isLiteralTypeNode(type2) && isNullType(type2.literal);
return !isNull;
}).flatMap((type2) => {
if (ts3.isParenthesizedTypeNode(type2) && ts3.isUnionTypeNode(type2.type)) {
return type2.type.types;
}
return [type2];
});
return ts3.factory.createUnionTypeNode(typesWithoutRedundantNullUnion);
}
function wrapFormDataContentType(type, context) {
context.typeImports.http.add("HttpFormData");
return ts3.factory.createTypeReferenceNode(ts3.factory.createIdentifier("HttpFormData"), [
renameComponentReferences(type, context)
]);
}
function wrapURLEncodedContentType(type, context) {
context.typeImports.http.add("HttpSearchParams");
return ts3.factory.createTypeReferenceNode(ts3.factory.createIdentifier("HttpSearchParams"), [
renameComponentReferences(type, context)
]);
}
function normalizeRequestBodyMember(requestBodyMember, context, options) {
if (!isContentMember(requestBodyMember)) {
return void 0;
}
const newIdentifier = ts3.factory.createIdentifier("body");
const contentType = requestBodyMember.name.text;
let newType = removeRedundantNullUnionIfNecessary(renameComponentReferences(requestBodyMember.type, context));
if (contentType === "multipart/form-data") {
newType = wrapFormDataContentType(newType, context);
} else if (contentType === "x-www-form-urlencoded") {
newType = wrapURLEncodedContentType(newType, context);
}
return {
contentTypeName: contentType,
propertySignature: ts3.factory.updatePropertySignature(
requestBodyMember,
requestBodyMember.modifiers,
newIdentifier,
options.questionToken,
newType
)
};
}
function normalizeHeaders(headers) {
const newHeaderMembers = headers.members.filter((header) => {
if (ts3.isIndexSignatureDeclaration(header)) {
return false;
}
if (ts3.isPropertySignature(header)) {
return header.type !== void 0 && !isUnknownType(header.type);
}
return true;
});
if (newHeaderMembers.length === 0) {
return void 0;
}
return ts3.factory.updateTypeLiteralNode(headers, ts3.factory.createNodeArray(newHeaderMembers));
}
function normalizeRequestHeaders(requestHeader) {
if (!isRequestHeaders(requestHeader)) {
return void 0;
}
const newType = normalizeHeaders(requestHeader.type);
if (!newType) {
return void 0;
}
return ts3.factory.updatePropertySignature(
requestHeader,
requestHeader.modifiers,
requestHeader.name,
requestHeader.questionToken,
newType
);
}
function createHeaderForUnionByContentType(existingHeader, contentTypeName) {
const existingHeaderMembers = existingHeader?.type.members ?? [];
const contentTypeIdentifier = ts3.factory.createIdentifier('"content-type"');
const contentTypeValue = ts3.factory.createLiteralTypeNode(ts3.factory.createStringLiteral(contentTypeName));
const newHeaderType = ts3.factory.createTypeLiteralNode([
ts3.factory.createPropertySignature(void 0, contentTypeIdentifier, void 0, contentTypeValue),
...existingHeaderMembers
]);
return ts3.factory.createPropertySignature(
existingHeader?.modifiers,
ts3.factory.createIdentifier("headers"),
void 0,
newHeaderType
);
}
function normalizeContentType(contentType, context, options) {
const { bodyQuestionToken } = options;
if (ts3.isIndexedAccessTypeNode(contentType)) {
return renameComponentReferences(contentType, context);
}
if (!ts3.isTypeLiteralNode(contentType)) {
return contentType;
}
const newHeader = contentType.members.map(normalizeRequestHeaders).find(isDefined_default);
const newBodyMembers = contentType.members.flatMap((body) => {
if (isContentPropertySignature(body)) {
return body.type.members.map((member) => normalizeRequestBodyMember(member, context, { questionToken: bodyQuestionToken })).filter(isDefined_default);
}
return [];
});
if (newBodyMembers.length === 0) {
const newMembers = [newHeader].filter(isDefined_default);
return ts3.factory.updateTypeLiteralNode(contentType, ts3.factory.createNodeArray(newMembers));
} else {
const bodyMemberUnionTypes = newBodyMembers.map((bodyMember) => {
const headerMember = createHeaderForUnionByContentType(newHeader, bodyMember.contentTypeName);
return ts3.factory.createTypeLiteralNode([headerMember, bodyMember.propertySignature]);
});
return ts3.factory.createUnionTypeNode(bodyMemberUnionTypes);
}
}
function normalizeRequest(request, context) {
const newIdentifier = ts3.factory.createIdentifier("request");
const newType = normalizeContentType(request.type, context, {
bodyQuestionToken: request.questionToken
});
if (request.questionToken && ts3.isIndexedAccessTypeNode(request.type) && ts3.isLiteralTypeNode(request.type.indexType) && (ts3.isIdentifier(request.type.indexType.literal) || ts3.isStringLiteral(request.type.indexType.literal))) {
const referencedComponentName = request.type.indexType.literal.text;
context.pendingActions.components.requests.toMarkBodyAsOptional.add(referencedComponentName);
}
return ts3.factory.updatePropertySignature(request, request.modifiers, newIdentifier, void 0, newType);
}
function normalizeResponseType(responseType, context, options) {
const { questionToken } = options;
if (!ts3.isTypeLiteralNode(responseType)) {
return responseType;
}
return normalizeContentType(responseType, context, { bodyQuestionToken: questionToken });
}
var NON_NUMERIC_RESPONSE_STATUS_TO_MAPPED_TYPE = {
default: "HttpStatusCode",
"1xx": "HttpStatusCode.Information",
"2xx": "HttpStatusCode.Success",
"3xx": "HttpStatusCode.Redirection",
"4xx": "HttpStatusCode.ClientError",
"5xx": "HttpStatusCode.ServerError"
};
function normalizeResponse(response, context, options = {}) {
const { isComponent: isComponent2 = false } = options;
if (!isResponse(response)) {
return void 0;
}
const newType = normalizeResponseType(response.type, context, {
questionToken: response.questionToken
});
const statusCodeOrComponentName = response.name.text;
const isNumericStatusCode = /^\d+$/.test(statusCodeOrComponentName);
const shouldReuseIdentifier = isComponent2 || isNumericStatusCode;
let newSignature;
if (shouldReuseIdentifier) {
newSignature = ts3.factory.updatePropertySignature(
response,
response.modifiers,
response.name,
response.questionToken,
newType
);
} else {
const statusCode = statusCodeOrComponentName.toLowerCase();
const mappedType = NON_NUMERIC_RESPONSE_STATUS_TO_MAPPED_TYPE[statusCode];
if (!mappedType) {
logger.warn(
`Warning: Response has a non-standard status code: ${color3.yellow(response.name.text)}. Consider replacing it with a number (e.g. '200'), a pattern ('1xx', '2xx', '3xx', '4xx', or '5xx'), or 'default'.`
);
return void 0;
}
context.typeImports.http.add("HttpStatusCode");
const newIdentifier = ts3.factory.createIdentifier(`[StatusCode in ${mappedType}]`);
newSignature = ts3.factory.updatePropertySignature(
response,
response.modifiers,
newIdentifier,
response.questionToken,
newType
);
}
return {
newSignature,
statusCode: {
value: statusCodeOrComponentName,
isNumeric: isNumericStatusCode
}
};
}
function normalizeResponses(responses, context) {
if (isNeverType(responses.type) || !ts3.isTypeLiteralNode(responses.type)) {
return void 0;
}
const newIdentifier = ts3.factory.createIdentifier("response");
const newQuestionToken = void 0;
const newMembers = responses.type.members.map((response) => normalizeResponse(response, context), context).filter(isDefined_default);
const sortedNewMembers = Array.from(newMembers).sort((response, otherResponse) => {
return response.statusCode.value.localeCompare(otherResponse.statusCode.value);
});
const isEveryStatusCodeNumeric = sortedNewMembers.every((response) => response.statusCode.isNumeric);
let newType;
if (isEveryStatusCodeNumeric) {
newType = ts3.factory.updateTypeLiteralNode(
responses.type,
ts3.factory.createNodeArray(sortedNewMembers.map((response) => response.newSignature))
);
} else {
context.typeImports.http.add("MergeHttpResponsesByStatusCode");
const typeMembersToMerge = sortedNewMembers.reduce(
(members, response) => {
if (response.statusCode.isNumeric) {
members.numeric.push(response.newSignature);
} else {
members.nonNumeric.push(response.newSignature);
}
return members;
},
{ numeric: [], nonNumeric: [] }
);
const numericTypeLiteral = ts3.factory.createTypeLiteralNode(typeMembersToMerge.numeric);
const nonNumericTypeLiterals = typeMembersToMerge.nonNumeric.map(
(response) => ts3.factory.createTypeLiteralNode([response])
);
const mergeWrapper = ts3.factory.createIdentifier("MergeHttpResponsesByStatusCode");
newType = ts3.factory.createTypeReferenceNode(mergeWrapper, [
ts3.factory.createTupleTypeNode([numericTypeLiteral, ...nonNumericTypeLiterals])
]);
}
return ts3.factory.updatePropertySignature(
responses,
responses.modifiers,
newIdentifier,
newQuestionToken,
renameComponentReferences(newType, context)
);
}
function normalizeMethodMember(methodMember, context) {
if (isMethodMember(methodMember)) {
if (methodMember.name.text === "requestBody") {
return normalizeRequest(methodMember, context);
}
if (methodMember.name.text === "responses") {
return normalizeResponses(methodMember, context);
}
return methodMember;
}
return void 0;
}
function normalizeRequestQueryWithParameters(requestMember, context) {
const newIdentifier = ts3.factory.createIdentifier("searchParams");
const newQuestionToken = void 0;
const newType = renameComponentReferences(requestMember.type, context);
return ts3.factory.updatePropertySignature(
requestMember,
requestMember.modifiers,
newIdentifier,
newQuestionToken,
newType
);
}
function normalizeRequestHeadersWithParameters(requestMember, context) {
const newIdentifier = ts3.factory.createIdentifier("headers");
const newQuestionToken = void 0;
const newType = renameComponentReferences(requestMember.type, context);
return ts3.factory.updatePropertySignature(
requestMember,
requestMember.modifiers,
newIdentifier,
newQuestionToken,
newType
);
}
function normalizeRequestMemberWithParameters(requestMember, context) {
if (!isRequestMember(requestMember) || requestMember.name.text === "path") {
return void 0;
}
if (requestMember.name.text === "query") {
return normalizeRequestQueryWithParameters(requestMember, context);
}
if (requestMember.name.text === "header") {
return normalizeRequestHeadersWithParameters(requestMember, context);
}
return requestMember;
}
function mergeRequestHeadersMember(headers, otherHeaders) {
const newType = ts3.factory.updateTypeLiteralNode(
headers.type,
ts3.factory.createNodeArray([...otherHeaders.type.members, ...headers.type.members])
);
return ts3.factory.updatePropertySignature(
headers,
headers.modifiers,
headers.name,
headers.questionToken,
newType
);
}
function mergeRequestHeadersMembers(members) {
let mergedHeaders;
let firstHeadersIndex;
const mergedHeadersMembers = members.map((member, index) => {
if (!member || !isNormalizedRequestHeaders(member)) {
return member;
}
if (firstHeadersIndex === void 0 || !mergedHeaders) {
firstHeadersIndex = index;
mergedHeaders = member;
return member;
}
mergedHeaders = mergeRequestHeadersMember(mergedHeaders, member);
return void 0;
});
if (firstHeadersIndex !== void 0) {
mergedHeadersMembers[firstHeadersIndex] = mergedHeaders;
}
return mergedHeadersMembers.filter(isDefined_default);
}
function mergeRequestAndParameterTypes(requestType, methodMembers, context) {
const parameters = methodMembers.find(isRequestParameters);
const parametersMembers = parameters ? parameters.type.members : [];
const requestMembers = ts3.isTypeLiteralNode(requestType) ? requestType.members : [];
const newMembers = mergeRequestHeadersMembers(
[...parametersMembers, ...requestMembers].map((member) => {
return normalizeRequestMemberWithParameters(member, context);
})
);
if (newMembers.length === 0) {
return void 0;
}
return ts3.factory.createTypeLiteralNode(newMembers);
}
function normalizeRequestTypeWithParameters(requestType, methodMembers, context) {
if (ts3.isUnionTypeNode(requestType)) {
const newTypes = requestType.types.map((type) => normalizeRequestTypeWithParameters(type, methodMembers, context)).filter(isDefined_default);
return ts3.factory.updateUnionTypeNode(requestType, ts3.factory.createNodeArray(newTypes));
}
if (ts3.isIndexedAccessTypeNode(requestType)) {
const newType = normalizeRequestTypeWithParameters(ts3.factory.createTypeLiteralNode([]), methodMembers, context);
return ts3.factory.createIntersectionTypeNode([requestType, newType].filter(isDefined_default));
}
return mergeRequestAndParameterTypes(requestType, methodMembers, context);
}
function normalizeMethodMemberWithParameters(methodMember, methodMembers, context) {
if (!ts3.isIdentifier(methodMember.name) || !methodMember.type) {
return void 0;
}
if (methodMember.name.text === "request") {
const newType = normalizeRequestTypeWithParameters(methodMember.type, methodMembers, context);
if (!newType) {
return void 0;
}
return ts3.factory.updatePropertySignature(
methodMember,
methodMember.modifiers,
methodMember.name,
void 0,
newType
);
}
if (methodMember.name.text === "response") {
return methodMember;
}
return void 0;
}
function normalizeTypeLiteralMethodType(methodType, context) {
const newMembers = methodType.members.map((member) => normalizeMethodMember(member, context)).filter(isDefined_default).map((member, _index, partialMembers) => normalizeMethodMemberWithParameters(member, partialMembers, context)).filter(isDefined_default);
return ts3.factory.updateTypeLiteralNode(methodType, ts3.factory.createNodeArray(newMembers));
}
function normalizeIndexedAccessMethodType(methodType, context) {
const isOperationsReference = ts3.isTypeReferenceNode(methodType.objectType) && ts3.isIdentifier(methodType.objectType.typeName) && methodType.objectType.typeName.text === "operations";
if (!isOperationsReference) {
return methodType;
}
const newIdentifier = createOperationsIdentifier(context.serviceName);
const newObjectType = ts3.factory.createTypeReferenceNode(newIdentifier, methodType.objectType.typeArguments);
const hasIndexTypeName = ts3.isLiteralTypeNode(methodType.indexType) && (ts3.isIdentifier(methodType.indexType.literal) || ts3.isStringLiteral(methodType.indexType.literal));
if (hasIndexTypeName) {
const operationName = methodType.indexType.literal.text;
context.referencedTypes.operations.add(operationName);
}
return ts3.factory.updateIndexedAccessTypeNode(methodType, newObjectType, methodType.indexType);
}
function normalizeMethod(method, context, options) {
if (!isMethod(method)) {
return void 0;
}
const methodName = method.name.text.toUpperCase();
if (!HTTP_METHODS.includes(methodName)) {
return void 0;
}
const matchesPositiveFilters = context.filters.paths.positive.length === 0 || context.filters.paths.positive.some(
(filter) => filter.methodRegex.test(methodName) && filter.pathRegex.test(options.pathName)
);
if (!matchesPositiveFilters) {
return void 0;
}
const matchesNegativeFilters = context.filters.paths.negative.length > 0 && context.filters.paths.negative.some(
(filter) => filter.methodRegex.test(methodName) && filter.pathRegex.test(options.pathName)
);
if (matchesNegativeFilters) {
return void 0;
}
const newIdentifier = ts3.factory.createIdentifier(methodName);
const newType = ts3.isTypeLiteralNode(method.type) ? normalizeTypeLiteralMethodType(method.type, context) : normalizeIndexedAccessMethodType(method.type, context);
return ts3.factory.updatePropertySignature(method, method.modifiers, newIdentifier, method.questionToken, newType);
}
function createPathsIdentifier(serviceName) {
return ts3.factory.createIdentifier(`${serviceName}Schema`);
}
function isPathsDeclaration(node) {
return node !== void 0 && (ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node)) && node.name.text === "paths";
}
function isPath(node) {
return ts3.isPropertySignature(node) && (ts3.isIdentifier(node.name) || ts3.isStringLiteral(node.name)) && node.type !== void 0 && (ts3.isTypeLiteralNode(node.type) || ts3.isIndexedAccessTypeNode(node.type));
}
function normalizePathNameWithParameters(pathName) {
return pathName.replace(/{([^}]+)}/g, ":$1");
}
function normalizePath(path3, context, options = {}) {
const { isComponent: isComponent2 = false } = options;
if (!isPath(path3)) {
return void 0;
}
const newPathName = isComponent2 ? path3.name.text : normalizePathNameWithParameters(path3.name.text);
const newIdentifier = isComponent2 ? path3.name : ts3.factory.createStringLiteral(newPathName);
let newType;
if (ts3.isTypeLiteralNode(path3.type)) {
const newMethods = path3.type.members.map((method) => normalizeMethod(method, context, { pathName: newPathName })).filter(isDefined_default);
if (newMethods.length === 0) {
return void 0;
}
newType = ts3.factory.updateTypeLiteralNode(path3.type, ts3.factory.createNodeArray(newMethods));
} else {
newType = renameComponentReferences(path3.type, context);
}
return ts3.factory.updatePropertySignature(path3, path3.modifiers, newIdentifier, path3.questionToken, newType);
}
function wrapPathsType(type, context) {
context.typeImports.http.add("HttpSchema");
const httpSchemaPathsWrapper = ts3.factory.createIdentifier("HttpSchema");
return ts3.factory.createTypeReferenceNode(httpSchemaPathsWrapper, [type]);
}
function normalizePaths(pathsOrTypeAlias, context) {
const newIdentifier = createPathsIdentifier(context.serviceName);
const paths = ts3.isTypeAliasDeclaration(pathsOrTypeAlias) ? ts3.factory.createInterfaceDeclaration(pathsOrTypeAlias.modifiers, pathsOrTypeAlias.name, void 0, void 0, []) : pathsOrTypeAlias;
const newMembers = paths.members.map((path3) => normalizePath(path3, context)).filter(isDefined_default);
const newType = ts3.factory.createTypeLiteralNode(newMembers);
return ts3.factory.createTypeAliasDeclaration(
paths.modifiers,
newIdentifier,
paths.typeParameters,
wrapPathsType(newType, context)
);
}
// src/typegen/openapi/transform/components.ts
function createComponentsIdentifierText(serviceName) {
return `${serviceName}Components`;
}
function createComponentsIdentifier(serviceName) {
return ts3.factory.createIdentifier(createComponentsIdentifierText(serviceName));
}
function isComponentsDeclaration(node, context) {
const componentIdentifiers = ["components", createComponentsIdentifierText(context.serviceName)];
return node !== void 0 && ts3.isInterfaceDeclaration(node) && componentIdentifiers.includes(node.name.text);
}
function isComponentGroup(node) {
return ts3.isPropertySignature(node) && node.type !== void 0 && ts3.isTypeLiteralNode(node.type) && ts3.isIdentifier(node.name);
}
function isComponent(node) {
return ts3.isPropertySignature(node) && node.type !== void 0 && !isNeverType(node.type) && (ts3.isIdentifier(node.name) || ts3.isStringLiteral(node.name));
}
function isRequestComponent(node) {
return ts3.isTypeLiteralNode(node.type);
}
function unchangedIndexedAccessTypeNode(node) {
return node;
}
function visitComponentReferences(node, context, options) {
const { onComponentReference, renameComponentReference = unchangedIndexedAccessTypeNode } = options;
if (isUnknownType(node)) {
return ts3.factory.createKeywordTypeNode(ts3.SyntaxKind.AnyKeyword);
}
if (ts3.isTypeReferenceNode(node)) {
const newTypeArguments = node.typeArguments?.map((type) => visitComponentReferences(type, context, options));
return ts3.factory.updateTypeReferenceNode(node, node.typeName, ts3.factory.createNodeArray(newTypeArguments));
}
if (ts3.isArrayTypeNode(node)) {
const newElementType = visitComponentReferences(node.elementType, context, options);
return ts3.factory.updateArrayTypeNode(node, newElementType);
}
if (ts3.isTupleTypeNode(node)) {
const newElements = node.elements.map((element) => visitComponentReferences(element, context, options));
return ts3.factory.updateTupleTypeNode(node, ts3.factory.createNodeArray(newElements));
}
if (ts3.isUnionTypeNode(node)) {
const newTypes = node.types.map((type) => visitComponentReferences(type, context, options));
return ts3.factory.updateUnionTypeNode(node, ts3.factory.createNodeArray(newTypes));
}
if (ts3.isIntersectionTypeNode(node)) {
const newTypes = node.types.map((type) => visitComponentReferences(type, context, options));
return ts3.factory.updateIntersectionTypeNode(node, ts3.factory.createNodeArray(newTypes));
}
if (ts3.isParenthesizedTypeNode(node)) {
const newType = visitComponentReferences(node.type, context, options);
return ts3.factory.updateParenthesizedType(node, newType);
}
if (ts3.isTypeLiteralNode(node)) {
const newMembers = node.members.map((member) => {
if (ts3.isPropertySignature(member) && member.type) {
const newType = visitComponentReferences(member.type, context, options);
return ts3.factory.updatePropertySignature(member, member.modifiers, member.name, member.questionToken, newType);
}
if (ts3.isIndexSignatureDeclaration(member)) {
const newType = visitComponentReferences(member.type, context, options);
return ts3.factory.updateIndexSignature(member, member.modifiers, member.parameters, newType);
}
return member;
});
return ts3.factory.updateTypeLiteralNode(node, ts3.factory.createNodeArray(newMembers));
}
if (ts3.isIndexedAccessTypeNode(node)) {
const isRootIndexedAccess = context.isComponentIndexedAccess ?? true;
if (ts3.isIndexedAccessTypeNode(node.objectType)) {
const childContext = { ...context, isComponentIndexedAccess: false };
const newObjectType = visitComponentReferences(node.objectType, childContext, options);
const newNode = ts3.factory.updateIndexedAccessTypeNode(node, newObjectType, node.indexType);
if (childContext.partialComponentPath && childContext.partialComponentPath.length > 0) {
const hasIndexTypeName = ts3.isLiteralTypeNode(node.indexType) && (ts3.isIdentifier(node.indexType.literal) || ts3.isStringLiteral(node.indexType.literal));
if (hasIndexTypeName) {
const componentName = node.indexType.literal.text;
childContext.partialComponentPath.push(componentName);
}
if (isRootIndexedAccess) {
const componentGroupName = childContext.partialComponentPath[0];
const componentName = childContext.partialComponentPath.slice(1).join(".");
const componentPath = `${componentGroupName}.${componentName}`;
onComponentReference(newNode, componentPath);
}
}
return newNode;
}
const componentIdentifiers = ["components", createComponentsIdentifierText(context.serviceName)];
const isComponentIndexedAccess = ts3.isTypeReferenceNode(node.objectType) && ts3.isIdentifier(node.objectType.typeName) && componentIdentifiers.includes(node.objectType.typeName.text) && ts3.isLiteralTypeNode(node.indexType) && (ts3.isIdentifier(node.indexType.literal) || ts3.isStringLiteral(node.indexType.literal));
if (isComponentIndexedAccess) {
const isRawComponent = node.objectType.typeName.text === "components";
const componentGroupName = node.indexType.literal.text;
const newNode = isRawComponent ? renameComponentReference(node, {
objectType: node.objectType,
indexType: node.indexType,
componentGroupName
}) : node;
const newNodeHasComponentGroupName = ts3.isLiteralTypeNode(newNode.indexType) && (ts3.isIdentifier(newNode.indexType.literal) || ts3.isStringLiteral(newNode.indexType.literal));
if (newNodeHasComponentGroupName) {
const newComponentGroupName = newNode.indexType.literal.text;
context.partialComponentPath = [newComponentGroupName];
}
return newNode;
}
}
return node;
}
function normalizeComponentGroupName(rawComponentGroupName) {
if (rawComponentGroupName === "requestBodies") {
return "requests";
}
return rawComponentGroupName;
}
function renameComponentReferences(node, context) {
return visitComponentReferences(node, context, {
onComponentReference(_node, componentPath) {
context.referencedTypes.components.add(componentPath);
},
renameComponentReference(node2, { indexType, objectType, componentGroupName }) {
const newIdentifier = createComponentsIdentifier(context.serviceName);
const newObjectType = ts3.factory.updateTypeReferenceNode(objectType, newIdentifier, objectType.typeArguments);
const newComponentGroupName = normalizeComponentGroupName(componentGroupName);
const newIndexType = ts3.factory.updateLiteralTypeNode(
indexType,
ts3.factory.createStringLiteral(newComponentGroupName)
);
return ts3.factory.updateIndexedAccessTypeNode(node2, newObjectType, newIndexType);
}
});
}
function processPendingRequestComponentActions(component, context) {
const pendingRequestActions = context.pendingActions.components.requests;
const componentName = component.name.text;
const shouldBeMarkedAsOptional = pendingRequestActions.toMarkBodyAsOptional.has(componentName);
const bodyQuestionToken = shouldBeMarkedAsOptional ? ts3.factory.createToken(ts3.SyntaxKind.QuestionToken) : component.questionToken;
pendingRequestActions.toMarkBodyAsOptional.delete(componentName);
return { bodyQuestionToken };
}
function normalizeRequestComponent(component, context) {
if (!isRequestComponent(component)) {
return void 0;
}
const { bodyQuestionToken } = processPendingRequestComponentActions(component, context);
const newType = normalizeContentType(component.type, context, { bodyQuestionToken });
return ts3.factory.updatePropertySignature(
component,
component.modifiers,
component.name,
component.questionToken,
newType
);
}
function normalizeComponent(component, componentGroupName, context) {
if (!isComponent(component)) {
return void 0;
}
if (componentGroupName === "requests") {
return normalizeRequestComponent(component, context);
}
if (componentGroupName === "responses") {
const responseComponent = normalizeResponse(component, context, { isComponent: true });
return responseComponent?.newSignature;
}
if (componentGroupName === "pathItems") {
return normalizePath(component, context, { isComponent: true });
}
return ts3.factory.updatePropertySignature(
component,
component.modifiers,
component.name,
component.questionToken,
renameComponentReferences(component.type, context)
);
}
function normalizeComponentGroup(componentGroup, context) {
if (!isComponentGroup(componentGroup)) {
return void 0;
}
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
const newIdentifier = ts3.factory.createIdentifier(componentGroupName);
const newComponents = componentGroup.type.members.map((component) => normalizeComponent(component, componentGroupName, context)).filter(isDefined_default);
const newType = ts3.factory.updateTypeLiteralNode(componentGroup.type, ts3.factory.createNodeArray(newComponents));
return ts3.factory.updatePropertySignature(
componentGroup,
componentGroup.modifiers,
newIdentifier,
componentGroup.questionToken,
newType
);
}
function normalizeComponents(components, context) {
const newIdentifier = createComponentsIdentifier(context.serviceName);
const newMembers = components.members.map((componentGroup) => normalizeComponentGroup(componentGroup, context)).filter(isDefined_default);
return ts3.factory.updateInterfaceDeclaration(
components,
components.modifiers,
newIdentifier,
components.typeParameters,
components.heritageClauses,
newMembers
);
}
function populateReferencedComponents(components, context) {
const pathsToVisit = new Set(context.referencedTypes.components);
while (pathsToVisit.size > 0) {
const previousPathsToVisit = new Set(pathsToVisit);
pathsToVisit.clear();
for (const componentGroup of components.members) {
if (!isComponentGroup(componentGroup)) {
continue;
}
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
for (const component of componentGroup.type.members) {
if (!isComponent(component)) {
continue;
}
const componentName = component.name.text;
const componentPath = `${componentGroupName}.${componentName}`;
const isComponentToVisit = previousPathsToVisit.has(componentPath);
if (!isComponentToVisit) {
continue;
}
context.referencedTypes.components.add(componentPath);
visitComponentReferences(component.type, context, {
onComponentReference(_node, componentPath2) {
const isKnownReferencedComponent = context.referencedTypes.components.has(componentPath2);
if (!isKnownReferencedComponent) {
pathsToVisit.add(componentPath2);
}
}
});
}
}
}
}
function removeComponentIfUnreferenced(component, componentGroupName, context) {
if (!isComponent(component)) {
return void 0;
}
const componentName = component.name.text;
const componentPath = `${componentGroupName}.${componentName}`;
if (context.referencedTypes.components.has(componentPath)) {
context.referencedTypes.components.delete(componentPath);
return component;
}
return void 0;
}
function removeUnreferencedComponentsInGroup(componentGroup, context) {
if (!isComponentGroup(componentGroup)) {
return void 0;
}
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
const newComponents = componentGroup.type.members.map((component) => removeComponentIfUnreferenced(component, componentGroupName, context)).filter(isDefined_default);
if (newComponents.length === 0) {
return void 0;
}
return ts3.factory.updatePropertySignature(
componentGroup,
componentGroup.modifiers,
componentGroup.name,
componentGroup.questionToken,
ts3.factory.updateTypeLiteralNode(componentGroup.type, ts3.factory.createNodeArray(newComponents))
);
}
function removeUnreferencedComponents(components, context) {
const newComponentGroups = components.members.map((componentGroup) => removeUnreferencedComponentsInGroup(componentGroup, context)).filter(isDefined_default);
context.referencedTypes.components.clear();
if (newComponentGroups.length === 0) {
return void 0;
}
return ts3.factory.updateInterfaceDeclaration(
components,
components.modifiers,
components.name,
components.typeParameters,
components.heritageClauses,
newComponentGroups
);
}
// src/utils/strings.ts
function convertToPascalCase(value) {
return value.replace(/(?:^|[^A-Za-z\d])([A-Za-z\d])/g, (_match, letter) => letter.toUpperCase());
}
// ../zimic-utils/dist/url.mjs
function createPathCharactersToEscapeRegex() {
return /([.(){}+$])/g;
}
function preparePathForRegex(path3) {
const pathURLPrefix = `data:${path3.startsWith("/") ? "" : "/"}`;
const pathAsURL = new URL(`${pathURLPrefix}${path3}`);
const encodedPath = pathAsURL.href.replace(pathURLPrefix, "");
return encodedPath.replace(/^\/+/g, "").replace(/\/+$/g, "").replace(createPathCharactersToEscapeRegex(), "\\$1");
}
function getSingleWildcardPathRegex() {
return /(?<wildcardPrefix>^|[^*])(?<escape>\\)?\*(?<wildcardSuffix>[^*]|$)/g;
}
function getDoubleWildcardPathRegex() {
return /(?<escape>\\)?\*\*/g;
}
function getTripleWildcardPathRegex() {
return /(?<escape>\\)?\*\*\/\*(?<wildcardSuffix>[^*]|$)/g;
}
function createWildcardRegexFromPath(path3) {
const pathRegexContent = preparePathForRegex(path3).replace(getTripleWildcardPathRegex(), (_match, escape, wildcardSuffix) => {
return escape === "\\" ? `\\*\\*/\\*${wildcardSuffix}` : `**${wildcardSuffix}`;
}).replace(
getSingleWildcardPathRegex(),
(_match, wildcardPrefix, escape, wildcardSuffix) => {
return escape === "\\" ? `${wildcardPrefix}\\*${wildcardSuffix}` : `${wildcardPrefix}[^/]*${wildcardSuffix}`;
}
).replace(getDoubleWildcardPathRegex(), (_match, escape) => {
return escape === "\\" ? "\\*\\*" : ".*";
});
return new RegExp(`^/?${pathRegexContent}/?$`);
}
var createWildcardRegexFromPath_default = createWildcardRegexFromPath;
// ../zimic-utils/dist/chunk-W66SQPEM.mjs
function createCachedDynamicImport(importModuleDynamically) {
let cachedImportResult;
return async function importModuleDynamicallyWithCache() {
cachedImportResult ??= await importModuleDynamically();
return cachedImportResult;
};
}
var createCachedDynamicImport_default = createCachedDynamicImport;
async function ensurePathExists(path3, options) {
try {
await fs.promises.access(path3, fs.constants.R_OK);
} catch (accessError) {
const error = new Error(options.errorMessage);
error.cause = accessError;
throw error;
}
}
// src/typegen/openapi/transform/filters.ts
var HTTP_METHOD_OPTIONS = HTTP_METHODS.join("|");
var MODIFIER_GROUP = "(?<modifier>!?)";
var METHOD_FILTER_GROUP = `(?<method>(?:\\*|(?:${HTTP_METHOD_OPTIONS})(?:,\\s*(?:${HTTP_METHOD_OPTIONS}))*))`;
var PATH_FILTER_GROUP = "(?<path>.+)";
var FILTER_REGEX = new RegExp(`^${MODIFIER_GROUP}\\s*${METHOD_FILTER_GROUP}\\s+${PATH_FILTER_GROUP}$`, "i");
function parseRawFilter(rawFilter) {
const filterMatch = rawFilter.match(FILTER_REGEX);
const { modifier: filterModifier, method: filteredMethodsOrWildcard, path: filteredPath } = filterMatch?.groups ?? {};
const isValidFilter = !filteredMethodsOrWildcard || !filteredPath;
if (isValidFilter) {
logger.warn(`Warning: Filter could not be parsed and was ignored: ${color3.yellow(rawFilter)}`);
return void 0;
}
return {
methodRegex: new RegExp(`(?:${filteredMethodsOrWildcard.toUpperCase().replace(/,/g, "|").replace(/\*/g, ".*")})`),
pathRegex: createWildcardRegexFromPath_default(filteredPath),
isNegativeMatch: filterModifier === "!"
};
}
function groupParsedFiltersByMatch(parsedFilters) {
return parsedFilters.reduce(
(groupedFilters, filter) => {
if (filter) {
if (filter.isNegativeMatch) {
groupedFilters.negative.push(filter);
} else {
groupedFilters.positive.push(filter);
}
}
return groupedFilters;
},
{ positive: [], negative: [] }
);
}
async function readPathFiltersFromFile(filePath) {
await ensurePathExists(filePath, {
errorMessage: `Could not read filter file: ${color3.yellow(filePath)}`
});
const fileContent = await fs.promises.readFile(filePath, "utf-8");
const fileContentWithoutComments = fileContent.replace(/#.*$/gm, "");
const filters = fileContentWithoutComments.split("\n");
return filters;
}
function ignoreEmptyFilters(filters) {
return filters.map((line) => line.trim()).filter(isNonEmpty_default);
}
// src/typegen/openapi/transform/context.ts
function createTypeTransformationContext(serviceName, rawFilters) {
const parsedFilters = rawFilters.map(parseRawFilter);
return {
serviceName: convertToPascalCase(serviceName),
filters: {
paths: groupParsedFiltersByMatch(parsedFilters)
},
typeImports: {
http: /* @__PURE__ */ new Set()
},
referencedTypes: {
operations: /* @__PURE__ */ new Set(),
components: /* @__PURE__ */ new Set()
},
pendingActions: {
components: {
requests: { toMarkBodyAsOptional: /* @__PURE__ */ new Set() }
}
}
};
}
// src/typegen/openapi/transform/imports.ts
var TYPEGEN_HTTP_IMPORT_MODULE = "@zimic/http";
function createImportDeclarations(context) {
const httpTypeImports = Array.from(context.typeImports.http).sort().map(createImportSpecifier);
const httpImportDeclaration = createImportDeclaration(httpTypeImports, TYPEGEN_HTTP_IMPORT_MODULE);
return [httpImportDeclaration];
}
var importOpenapiTypeScript = createCachedDynamicImport_default(() => import('openapi-typescript'));
function transformSchemaObject(schemaObject) {
if (schemaObject.format === "binary") {
const blobType = createBlobType();
if (schemaObject.nullable) {
const nullType = createNullType();
return ts3.factory.createUnionTypeNode([blobType, nullType]);
}
return blobType;
}
}
function convertFilePathOrURLToURL(filePathOrURL) {
try {
return new URL(filePathOrURL);
} catch {
return new URL(`file://${path.resolve(filePathOrURL)}`);
}
}
async function importTypesFromOpenAPI(filePathOrURL) {
const schemaURL = convertFilePathOrURLToURL(filePathOrURL);
const { default: generateTypesFromOpenAPI2 } = await importOpenapiTypeScript();
const rawNodes = await generateTypesFromOpenAPI2(schemaURL, {
alphabetize: false,
additionalProperties: false,
excludeDeprecated: false,
propertiesRequiredByDefault: false,
defaultNonNullable: true,
pathParamsAsTypes: false,
emptyObjectsUnknown: true,
exportType: false,
arrayLength: false,
immutable: false,
enumValues: false,
enum: false,
silent: true,
transform: transformSchemaObject
});
return rawNodes;
}
async function convertTypesToString(nodes, options) {
const { astToString: convertTypeASTToString } = await importOpenapiTypeScript();
const typegenResult = convertTypeASTToString(nodes, {
formatOptions: { removeComments: !options.includeComments }
});
return typegenResult;
}
function formatTypegenResultToOutput(typegenResult) {
const formattedResult = typegenResult.replace(/^export (\w+)/gm, "\nexport $1").replace(/^( {4})+/gm, (match) => match.replace(/ {4}/g, " "));
return [
"// Auto-generated by @zimic/http.",
"// NOTE: Do not manually edit this file. Changes will be overridden.\n",
formattedResult
].join("\n");
}
async function writeTypegenResultToStandardOutput(typegenResult) {
await new Promise((resolve) => {
process.stdout.write(typegenResult, "utf-8", resolve);
});
}
async function writeTypegenResultToFile(outputFilePath, formattedResult) {
const outputDirectory = path.dirname(outputFilePath);
await fs.promises.mkdir(outputDirectory, { recursive: true });
await fs.promises.writeFile(outputFilePath, formattedResult);
}
// src/typegen/openapi/generate.ts
var RESOURCES_TO_REMOVE_IF_NOT_NORMALIZED = ["paths", "webhooks", "operations", "components", "$defs"];
function removeUnknownResources(node) {
const isUnknownResource = !node || (ts3.isTypeAliasDeclaration(node) || ts3.isInterfaceDeclaration(node)) && RESOURCES_TO_REMOVE_IF_NOT_NORMALIZED.includes(node.name.text);
if (isUnknownResource) {
return void 0;
}
return node;
}
function normalizeRawNodes(rawNodes, context, options) {
let normalizedNodes = rawNodes.map((node) => isPathsDeclaration(node) ? normalizePaths(node, context) : node);
if (options.prune) {
normalizedNodes = normalizedNodes.map((node) => isOperationsDeclaration(node) ? removeUnreferencedOperations(node, context) : node).filter(isDefined_default);
}
normalizedNodes = normalizedNodes.map((node) => isOperationsDeclaration(node) ? normalizeOperations(node, context) : node).filter(isDefined_default);
if (options.prune) {
for (const node of normalizedNodes) {
if (isComponentsDeclaration(node, context)) {
populateReferencedComponents(node, context);
}
}
normalizedNodes = normalizedNodes.map((node) => isComponentsDeclaration(node, context) ? removeUnreferencedComponents(node, context) : node).filter(isDefined_default);
}
normalizedNodes = normalizedNodes.map((node) => isComponentsDeclaration(node, context) ? normalizeComponents(node, context) : node).map(removeUnknownResources).filter(isDefined_default);
return normalizedNodes;
}
async function generateTypesFromOpenAPI({
input: inputFilePathOrURL,
output: outputFilePath,
serviceName,
includeComments,
prune,
filters: filtersFromArguments = [],
filterFile
}) {
const filtersFromFile = filterFile ? await readPathFiltersFromFile(path.resolve(filterFile)) : [];
const filters = ignoreEmptyFilters([...filtersFromFile, ...filtersFromArguments]);
const rawNodes = await importTypesFromOpenAPI(inputFilePathOrURL);
const context = createTypeTransformationContext(serviceName, filters);
const nodes = normalizeRawNodes(rawNodes, context, { prune });
const importDeclarations = createImportDeclarations(context);
for (const declaration of importDeclarations) {
nodes.unshift(declaration);
}
const typegenResult = await convertTypesToString(nodes, { includeComments });
const formattedTypegenResult = formatTypegenResultToOutput(typegenResult);
con