UNPKG

ng-openapi

Version:

Generate Angular services and TypeScript types from OpenAPI/Swagger specifications

1,481 lines (1,455 loc) 102 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __typeError = (msg) => { throw TypeError(msg); }; 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 __name = (target, value) => __defProp(target, "name", { value, configurable: true }); 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); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); 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/index.ts var index_exports = {}; __export(index_exports, { BASE_INTERCEPTOR_HEADER_COMMENT: () => BASE_INTERCEPTOR_HEADER_COMMENT, CONTENT_TYPES: () => CONTENT_TYPES, HTTP_RESOURCE_GENERATOR_HEADER_COMMENT: () => HTTP_RESOURCE_GENERATOR_HEADER_COMMENT, MAIN_INDEX_GENERATOR_HEADER_COMMENT: () => MAIN_INDEX_GENERATOR_HEADER_COMMENT, PROVIDER_GENERATOR_HEADER_COMMENT: () => PROVIDER_GENERATOR_HEADER_COMMENT, SERVICE_GENERATOR_HEADER_COMMENT: () => SERVICE_GENERATOR_HEADER_COMMENT, SERVICE_INDEX_GENERATOR_HEADER_COMMENT: () => SERVICE_INDEX_GENERATOR_HEADER_COMMENT, SwaggerParser: () => SwaggerParser, TYPE_GENERATOR_HEADER_COMMENT: () => TYPE_GENERATOR_HEADER_COMMENT, ZOD_PLUGIN_GENERATOR_HEADER_COMMENT: () => ZOD_PLUGIN_GENERATOR_HEADER_COMMENT, ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT: () => ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT, camelCase: () => camelCase, escapeString: () => escapeString, extractPaths: () => extractPaths, generateFromConfig: () => generateFromConfig, generateParseRequestTypeParams: () => generateParseRequestTypeParams, getBasePathTokenName: () => getBasePathTokenName, getClientContextTokenName: () => getClientContextTokenName, getInterceptorsTokenName: () => getInterceptorsTokenName, getRequestBodyType: () => getRequestBodyType, getResponseType: () => getResponseType, getResponseTypeFromResponse: () => getResponseTypeFromResponse, getTypeScriptType: () => getTypeScriptType, hasDuplicateFunctionNames: () => hasDuplicateFunctionNames, inferResponseTypeFromContentType: () => inferResponseTypeFromContentType, isDataTypeInterface: () => isDataTypeInterface, isPrimitiveType: () => isPrimitiveType, kebabCase: () => kebabCase, nullableType: () => nullableType, pascalCase: () => pascalCase, screamingSnakeCase: () => screamingSnakeCase, validateInput: () => validateInput }); module.exports = __toCommonJS(index_exports); // src/lib/core/generator.ts var import_ts_morph7 = require("ts-morph"); // ../shared/src/utils/string.utils.ts function camelCase(str) { return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toLowerCase()); } __name(camelCase, "camelCase"); function kebabCase(str) { return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase(); } __name(kebabCase, "kebabCase"); function pascalCase(str) { return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toUpperCase()); } __name(pascalCase, "pascalCase"); function screamingSnakeCase(str) { return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase(); } __name(screamingSnakeCase, "screamingSnakeCase"); // ../shared/src/utils/type.utils.ts function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, context = "type") { let schema; let nullable; if (typeof schemaOrType === "string" || schemaOrType === void 0) { schema = { type: schemaOrType, format: typeof formatOrNullable === "string" ? formatOrNullable : void 0 }; nullable = typeof formatOrNullable === "boolean" ? formatOrNullable : isNullable; } else { schema = schemaOrType; nullable = typeof formatOrNullable === "boolean" ? formatOrNullable : schema.nullable; } if (!schema) { return "any"; } if (schema.$ref) { const refName = schema.$ref.split("/").pop(); return nullableType(pascalCase(refName), nullable); } if (schema.type === "array") { const itemType = schema.items ? getTypeScriptType(schema.items, config, void 0, void 0, context) : "unknown"; return nullable ? `(Array<${itemType}> | null)` : `Array<${itemType}>`; } switch (schema.type) { case "string": if (schema.enum) { return schema.enum.map((value) => typeof value === "string" ? `'${escapeString(value)}'` : String(value)).join(" | "); } if (schema.format === "date" || schema.format === "date-time") { const dateType = config.options.dateType === "Date" ? "Date" : "string"; return nullableType(dateType, nullable); } if (schema.format === "binary") { const binaryType = context === "type" ? "Blob" : "File"; return nullableType(binaryType, nullable); } if (schema.format === "uuid" || schema.format === "email" || schema.format === "uri" || schema.format === "hostname" || schema.format === "ipv4" || schema.format === "ipv6") { return nullableType("string", nullable); } return nullableType("string", nullable); case "number": case "integer": return nullableType("number", nullable); case "boolean": return nullableType("boolean", nullable); case "object": return nullableType(context === "type" ? "Record<string, any>" : "any", nullable); case "null": return "null"; default: if (Array.isArray(schema.type)) { const types = schema.type.map((t) => getTypeScriptType(t, config, void 0, void 0, context)); return nullableType(types.join(" | "), nullable); } return nullableType("any", nullable); } } __name(getTypeScriptType, "getTypeScriptType"); function nullableType(type, isNullable) { return type + (isNullable ? " | null" : ""); } __name(nullableType, "nullableType"); function escapeString(str) { return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); } __name(escapeString, "escapeString"); // ../shared/src/utils/functions/token-names.ts function getClientContextTokenName(clientName = "default") { const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`; } __name(getClientContextTokenName, "getClientContextTokenName"); function getBasePathTokenName(clientName = "default") { const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `BASE_PATH_${clientSuffix}`; } __name(getBasePathTokenName, "getBasePathTokenName"); function getInterceptorsTokenName(clientName = "default") { const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `HTTP_INTERCEPTORS_${clientSuffix}`; } __name(getInterceptorsTokenName, "getInterceptorsTokenName"); // ../shared/src/utils/functions/duplicate-function-name.ts function hasDuplicateFunctionNames(arr) { return new Set(arr.map((fn) => fn.getName())).size !== arr.length; } __name(hasDuplicateFunctionNames, "hasDuplicateFunctionNames"); // ../shared/src/utils/functions/extract-paths.ts function extractPaths(swaggerPaths = {}, methods = [ "get", "post", "put", "patch", "delete", "options", "head" ]) { const paths = []; Object.entries(swaggerPaths).forEach(([path12, pathItem]) => { methods.forEach((method) => { if (pathItem[method]) { const operation = pathItem[method]; paths.push({ path: path12, method: method.toUpperCase(), operationId: operation.operationId, summary: operation.summary, description: operation.description, tags: operation.tags || [], parameters: parseParameters(operation.parameters || [], pathItem.parameters || []), requestBody: operation.requestBody, responses: operation.responses || {} }); } }); }); return paths; } __name(extractPaths, "extractPaths"); function parseParameters(operationParams, pathParams) { const allParams = [ ...pathParams, ...operationParams ]; return allParams.map((param) => ({ name: param.name, in: param.in, required: param.required || param.in === "path", schema: param.schema, type: param.type, format: param.format, description: param.description })); } __name(parseParameters, "parseParameters"); // ../shared/src/utils/functions/extract-swagger-response-type.ts function getResponseTypeFromResponse(response, responseTypeMapping) { var _a; const content = response.content || {}; if (Object.keys(content).length === 0) { return "json"; } const responseTypes = []; for (const [contentType, mediaType] of Object.entries(content)) { const schema = mediaType == null ? void 0 : mediaType.schema; const mapping = responseTypeMapping || {}; if (mapping[contentType]) { responseTypes.push({ type: mapping[contentType], priority: 1, contentType }); continue; } if ((schema == null ? void 0 : schema.format) === "binary" || (schema == null ? void 0 : schema.format) === "byte") { responseTypes.push({ type: "blob", priority: 2, contentType }); continue; } if ((schema == null ? void 0 : schema.type) === "string" && ((schema == null ? void 0 : schema.format) === "binary" || (schema == null ? void 0 : schema.format) === "byte")) { responseTypes.push({ type: "blob", priority: 2, contentType }); continue; } const isPrimitive = isPrimitiveType(schema); const inferredType = inferResponseTypeFromContentType(contentType); let priority = 3; let finalType = inferredType; if (inferredType === "json" && isPrimitive) { finalType = "text"; priority = 2; } else if (inferredType === "json") { priority = 2; } responseTypes.push({ type: finalType, priority, contentType, isPrimitive }); } responseTypes.sort((a, b) => a.priority - b.priority); return ((_a = responseTypes[0]) == null ? void 0 : _a.type) || "json"; } __name(getResponseTypeFromResponse, "getResponseTypeFromResponse"); function isPrimitiveType(schema) { if (!schema) return false; const primitiveTypes = [ "string", "number", "integer", "boolean" ]; if (primitiveTypes.includes(schema.type)) { return true; } if (schema.type === "array") { return false; } if (schema.type === "object" || schema.properties) { return false; } if (schema.$ref) { return false; } if (schema.allOf || schema.oneOf || schema.anyOf) { return false; } return false; } __name(isPrimitiveType, "isPrimitiveType"); function inferResponseTypeFromContentType(contentType) { const normalizedType = contentType.split(";")[0].trim().toLowerCase(); if (normalizedType.includes("json") || normalizedType === "application/ld+json" || normalizedType === "application/hal+json" || normalizedType === "application/vnd.api+json") { return "json"; } if (normalizedType.includes("xml") || normalizedType === "application/soap+xml" || normalizedType === "application/atom+xml" || normalizedType === "application/rss+xml") { return "text"; } if (normalizedType.startsWith("text/")) { const binaryTextTypes = [ "text/rtf", "text/cache-manifest", "text/vcard", "text/calendar" ]; if (binaryTextTypes.includes(normalizedType)) { return "blob"; } return "text"; } if (normalizedType === "CONTENT_TYPES.FORM_URLENCODED" || normalizedType === "multipart/form-data") { return "text"; } if (normalizedType === "application/javascript" || normalizedType === "application/typescript" || normalizedType === "application/css" || normalizedType === "application/yaml" || normalizedType === "application/x-yaml" || normalizedType === "application/toml") { return "text"; } if (normalizedType.startsWith("image/") || normalizedType.startsWith("audio/") || normalizedType.startsWith("video/") || normalizedType === "application/pdf" || normalizedType === "application/zip" || normalizedType.includes("octet-stream")) { return "arraybuffer"; } return "blob"; } __name(inferResponseTypeFromContentType, "inferResponseTypeFromContentType"); function getResponseType(response, config) { const responseType = getResponseTypeFromResponse(response); const content = response.content || {}; for (const [contentType, mediaType] of Object.entries(content)) { if (mediaType == null ? void 0 : mediaType.schema) { return getTypeScriptType(mediaType.schema, config, mediaType.schema.nullable); } } switch (responseType) { case "blob": return "Blob"; case "arraybuffer": return "ArrayBuffer"; case "text": return "string"; default: return "any"; } } __name(getResponseType, "getResponseType"); // ../shared/src/utils/content-types.constants.ts var CONTENT_TYPES = { MULTIPART: "multipart/form-data", FORM_URLENCODED: "application/x-www-form-urlencoded", JSON: "application/json" }; // ../shared/src/utils/functions/get-request-body-type.ts function getRequestBodyType(requestBody, config) { const content = requestBody.content || {}; const jsonContent = content[CONTENT_TYPES.JSON]; if (jsonContent == null ? void 0 : jsonContent.schema) { return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable); } return "any"; } __name(getRequestBodyType, "getRequestBodyType"); // ../shared/src/utils/functions/is-data-type-interface.ts function isDataTypeInterface(type) { const invalidTypes = [ "any", "File", "string", "number", "boolean", "object", "unknown", "[]", "Array" ]; return !invalidTypes.some((invalidType) => type.includes(invalidType)); } __name(isDataTypeInterface, "isDataTypeInterface"); // ../shared/src/utils/functions/generate-parse-request-type-params.ts function generateParseRequestTypeParams(params) { const bodyParam = params.find((param) => { return typeof param.type === "string" && isDataTypeInterface(param.type); }); if (bodyParam) { const optional = bodyParam.hasQuestionToken ? " | undefined" : ""; return `${bodyParam.type}${optional}`; } return ""; } __name(generateParseRequestTypeParams, "generateParseRequestTypeParams"); // ../shared/src/config/constants.ts var disableLinting = `/* @ts-nocheck */ /* eslint-disable */ /* @noformat */ /* @formatter:off */ `; var authorComment = `/** * Generated by ng-openapi `; var defaultHeaderComment = disableLinting + authorComment; var TYPE_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated TypeScript interfaces from Swagger specification * Do not edit this file manually */ `; var SERVICE_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated service exports * Do not edit this file manually */ `; var SERVICE_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((controllerName) => defaultHeaderComment + `* Generated Angular service for ${controllerName} controller * Do not edit this file manually */ `, "SERVICE_GENERATOR_HEADER_COMMENT"); var MAIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Entrypoint for the client * Do not edit this file manually */ `; var PROVIDER_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated provider functions for easy setup * Do not edit this file manually */ `; var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => defaultHeaderComment + `* Generated Base Interceptor for client ${clientName} * Do not edit this file manually */ `, "BASE_INTERCEPTOR_HEADER_COMMENT"); var HTTP_RESOURCE_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((resourceName) => defaultHeaderComment + `* \`httpResource\` is still an experimental feature - NOT PRODUCTION READY * Generated Angular service for ${resourceName} * Do not edit this file manually */ `, "HTTP_RESOURCE_GENERATOR_HEADER_COMMENT"); var ZOD_PLUGIN_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((validatorName) => defaultHeaderComment + `* Generated Zod Schemas for ${validatorName} * Do not edit this file manually */ `, "ZOD_PLUGIN_GENERATOR_HEADER_COMMENT"); var ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated Zod Schemas exports * Do not edit this file manually */ `; // ../shared/src/core/swagger-parser.ts var fs = __toESM(require("fs")); var path = __toESM(require("path")); var yaml = __toESM(require("js-yaml")); // ../shared/src/utils/functions/is-url.ts function isUrl(input) { try { const url = new URL(input); return [ "http:", "https:" ].includes(url.protocol); } catch (e) { return false; } } __name(isUrl, "isUrl"); // ../shared/src/core/swagger-parser.ts var _SwaggerParser = class _SwaggerParser { constructor(spec, config) { __publicField(this, "spec"); var _a, _b; const isInputValid = (_b = (_a = config.validateInput) == null ? void 0 : _a.call(config, spec)) != null ? _b : true; if (!isInputValid) { throw new Error("Swagger spec is not valid. Check your `validateInput` condition."); } this.spec = spec; } static create(swaggerPathOrUrl, config) { return __async(this, null, function* () { const swaggerContent = yield _SwaggerParser.loadContent(swaggerPathOrUrl); const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl); return new _SwaggerParser(spec, config); }); } static loadContent(pathOrUrl) { return __async(this, null, function* () { if (isUrl(pathOrUrl)) { return yield _SwaggerParser.fetchUrlContent(pathOrUrl); } else { return fs.readFileSync(pathOrUrl, "utf8"); } }); } static fetchUrlContent(url) { return __async(this, null, function* () { try { const response = yield fetch(url, { method: "GET", headers: { Accept: "application/json, application/yaml, text/yaml, text/plain, */*", "User-Agent": "ng-openapi" }, // 30 second timeout signal: AbortSignal.timeout(3e4) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const content = yield response.text(); if (!content || content.trim() === "") { throw new Error(`Empty response from URL: ${url}`); } return content; } catch (error) { let errorMessage = `Failed to fetch content from URL: ${url}`; if (error.name === "AbortError") { errorMessage += " - Request timeout (30s)"; } else if (error.message) { errorMessage += ` - ${error.message}`; } throw new Error(errorMessage); } }); } static parseSpecContent(content, pathOrUrl) { let format; if (isUrl(pathOrUrl)) { const urlPath = new URL(pathOrUrl).pathname.toLowerCase(); if (urlPath.endsWith(".json")) { format = "json"; } else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) { format = "yaml"; } else { format = _SwaggerParser.detectFormat(content); } } else { const extension = path.extname(pathOrUrl).toLowerCase(); switch (extension) { case ".json": format = "json"; break; case ".yaml": format = "yaml"; break; case ".yml": format = "yml"; break; default: format = _SwaggerParser.detectFormat(content); } } try { switch (format) { case "json": return JSON.parse(content); case "yaml": case "yml": return yaml.load(content); default: throw new Error(`Unable to determine format for: ${pathOrUrl}`); } } catch (error) { throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`); } } static detectFormat(content) { const trimmed = content.trim(); if (trimmed.startsWith("{") || trimmed.startsWith("[")) { return "json"; } if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) { return "yaml"; } return "json"; } getDefinitions() { var _a; return this.spec.definitions || ((_a = this.spec.components) == null ? void 0 : _a.schemas) || {}; } getDefinition(name) { const definitions = this.getDefinitions(); return definitions[name]; } resolveReference(ref) { const parts = ref.split("/"); const definitionName = parts[parts.length - 1]; return this.getDefinition(definitionName); } getAllDefinitionNames() { return Object.keys(this.getDefinitions()); } getSpec() { return this.spec; } getPaths() { return this.spec.paths || {}; } isValidSpec() { return !!(this.spec.swagger && this.spec.swagger.startsWith("2.") || this.spec.openapi && this.spec.openapi.startsWith("3.")); } getSpecVersion() { if (this.spec.swagger) { return { type: "swagger", version: this.spec.swagger }; } if (this.spec.openapi) { return { type: "openapi", version: this.spec.openapi }; } return null; } }; __name(_SwaggerParser, "SwaggerParser"); var SwaggerParser = _SwaggerParser; // src/lib/generators/type/type.generator.ts var import_ts_morph = require("ts-morph"); var _TypeGenerator = class _TypeGenerator { constructor(parser, project, config, outputRoot) { __publicField(this, "project"); __publicField(this, "parser"); __publicField(this, "sourceFile"); __publicField(this, "generatedTypes", /* @__PURE__ */ new Set()); __publicField(this, "config"); // Performance caches __publicField(this, "pascalCaseCache", /* @__PURE__ */ new Map()); __publicField(this, "sanitizedNameCache", /* @__PURE__ */ new Map()); __publicField(this, "typeResolutionCache", /* @__PURE__ */ new Map()); // Batch collection for AST operations __publicField(this, "statements", []); __publicField(this, "deferredTypes", /* @__PURE__ */ new Map()); this.config = config; this.project = project; this.parser = parser; const outputPath = outputRoot + "/models/index.ts"; this.sourceFile = this.project.createSourceFile(outputPath, "", { overwrite: true }); } generate() { return __async(this, null, function* () { try { const definitions = this.parser.getDefinitions(); if (!definitions || Object.keys(definitions).length === 0) { console.warn("No definitions found in swagger file"); return; } this.collectAllTypeStructures(definitions); this.collectSdkTypes(); this.applyBatchUpdates(); yield this.finalize(); } catch (error) { console.error("Error in generate():", error); throw new Error(`Failed to generate types: ${error instanceof Error ? error.message : "Unknown error"}`); } }); } collectAllTypeStructures(definitions) { Object.keys(definitions).forEach((name) => { const interfaceName = this.getCachedPascalCase(name); this.generatedTypes.add(interfaceName); }); Object.entries(definitions).forEach(([name, definition]) => { this.collectTypeStructure(name, definition); }); this.deferredTypes.forEach((definition, name) => { this.collectTypeStructure(name, definition); }); } collectTypeStructure(name, definition) { var _a; const interfaceName = (_a = this.getCachedPascalCase(name)) != null ? _a : ""; if (definition.enum) { this.collectEnumStructure(interfaceName, definition); } else if (definition.allOf) { this.collectCompositeTypeStructure(interfaceName, definition); } else if (definition.items) { this.collectArrayTypeStructure(interfaceName, definition); } else if (definition.properties) { this.collectInterfaceStructure(interfaceName, definition); } else { const propertyType = this.resolveSwaggerTypeCached(definition); this.statements.push({ kind: import_ts_morph.StructureKind.TypeAlias, name: interfaceName, isExported: true, docs: definition.description ? [ definition.description ] : void 0, type: propertyType }); } } collectEnumStructure(name, definition) { var _a; if (!((_a = definition.enum) == null ? void 0 : _a.length)) return; const docs = !this.config.options.generateEnumBasedOnDescription && definition.description ? [ definition.description ] : void 0; if (this.config.options.enumStyle === "enum") { const statement = this.buildEnumAsEnum(name, definition, docs); this.statements.push(...statement); } else { const statement = this.buildEnumAsUnion(name, definition, docs); this.statements.push(...statement); } } buildEnumAsEnum(name, definition, docs) { var _a; if (!((_a = definition.enum) == null ? void 0 : _a.length)) throw Error("Enum definition has no values"); const statements = []; const isStringEnum = definition.enum.some((value) => typeof value === "string"); if (isStringEnum) { const members = definition.enum.map((value) => ({ name: this.toEnumKey(value), value: `${String(value)}` })); statements.push({ kind: import_ts_morph.StructureKind.Enum, name, isExported: true, docs, members }); } else { const members = this.buildEnumMembers(definition); statements.push({ kind: import_ts_morph.StructureKind.Enum, name, isExported: true, docs, members }); } return statements; } buildEnumAsUnion(name, definition, docs) { var _a; if (!((_a = definition.enum) == null ? void 0 : _a.length)) throw Error("Enum definition has no values"); const statements = []; const objectProperties = []; const unionType = definition.enum.map((value) => { const key = this.toEnumKey(value); const val = typeof value === "string" ? `'${this.escapeString(value)}'` : isNaN(value) ? `'${value}'` : `${value}`; objectProperties.push(`${key}: ${val} as ${name}`); return val; }).join(" | "); statements.push({ kind: import_ts_morph.StructureKind.TypeAlias, name, type: unionType, isExported: true, docs }); statements.push({ kind: import_ts_morph.StructureKind.VariableStatement, declarationKind: import_ts_morph.VariableDeclarationKind.Const, isExported: true, declarations: [ { name, initializer: `{ ${objectProperties.join(",\n")} }` } ] }); return statements; } buildEnumMembers(definition) { var _a; if (definition.description && this.config.options.generateEnumBasedOnDescription) { try { const enumValueObjects = JSON.parse(definition.description); return enumValueObjects.map((obj) => ({ name: obj.Name, value: obj.Value })); } catch (e) { } } return (_a = definition.enum) == null ? void 0 : _a.map((value) => ({ name: this.toEnumKey(value), value })); } collectCompositeTypeStructure(name, definition) { let typeExpression = ""; if (definition.allOf) { const types = definition.allOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown"); typeExpression = types.length > 0 ? types.join(" & ") : "Record<string, unknown>"; } this.statements.push({ kind: import_ts_morph.StructureKind.TypeAlias, name, type: typeExpression, isExported: true, docs: definition.description ? [ definition.description ] : void 0 }); } collectArrayTypeStructure(name, definition) { const itemType = definition.items ? this.getArrayItemType(definition.items) : "unknown"; this.statements.push({ kind: import_ts_morph.StructureKind.TypeAlias, name, isExported: true, docs: definition.description ? [ definition.description ] : void 0, type: `Array<${itemType}>` }); } collectInterfaceStructure(name, definition) { const properties = this.buildInterfaceProperties(definition); this.statements.push({ kind: import_ts_morph.StructureKind.Interface, name, isExported: true, docs: definition.description ? [ definition.description ] : void 0, properties, indexSignatures: this.buildIndexSignatures(definition) }); } buildInterfaceProperties(definition) { if (!definition.properties) { return []; } return Object.entries(definition.properties).map(([propertyName, property]) => { var _a, _b; const isRequired = (_b = (_a = definition.required) == null ? void 0 : _a.includes(propertyName)) != null ? _b : false; const isReadOnly = property.readOnly; const propertyType = this.resolveSwaggerTypeCached(property); const sanitizedName = this.getCachedSanitizedName(propertyName); return { name: sanitizedName, type: propertyType, isReadonly: isReadOnly, hasQuestionToken: !isRequired, docs: property.description ? [ property.description ] : void 0 }; }); } buildIndexSignatures(definition) { if (!definition.properties && definition.additionalProperties === false) { return [ { keyName: "key", keyType: "string", returnType: "never" } ]; } if (!definition.properties && definition.additionalProperties === true) { return [ { keyName: "key", keyType: "string", returnType: "any" } ]; } if (!definition.properties) { return [ { keyName: "key", keyType: "string", returnType: "unknown" } ]; } return []; } resolveSwaggerTypeCached(schema) { const cacheKey = JSON.stringify(schema); if (this.typeResolutionCache.has(cacheKey)) { return this.typeResolutionCache.get(cacheKey); } const result = this.resolveSwaggerType(schema); this.typeResolutionCache.set(cacheKey, result); return result; } resolveSwaggerType(schema) { if (schema.$ref) { return this.resolveReference(schema.$ref); } if (schema.enum) { return schema.enum.map((value) => typeof value === "string" ? `'${this.escapeString(value)}'` : String(value)).join(" | "); } if (schema.allOf) { return schema.allOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown").join(" & ") || "Record<string, unknown>"; } if (schema.oneOf) { return schema.oneOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type, index, array) => type !== "any" && type !== "unknown" && array.indexOf(type) === index).join(" | ") || "unknown"; } if (schema.anyOf) { return schema.anyOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown").join(" | ") || "unknown"; } if (schema.type === "array") { const itemType = schema.items ? this.getArrayItemType(schema.items) : "unknown"; return `Array<${itemType}>`; } if (schema.type === "object") { if (schema.properties) { return this.generateInlineObjectType(schema); } if (schema.additionalProperties) { const valueType = typeof schema.additionalProperties === "object" ? this.resolveSwaggerTypeCached(schema.additionalProperties) : "unknown"; return `Record<string, ${valueType}>`; } return "Record<string, unknown>"; } return this.mapSwaggerTypeToTypeScript(schema.type, schema.format, schema.nullable); } generateInlineObjectType(definition) { if (!definition.properties) { if (definition.additionalProperties) { const additionalType = typeof definition.additionalProperties === "object" ? this.resolveSwaggerTypeCached(definition.additionalProperties) : "unknown"; return `Record<string, ${additionalType}>`; } return "Record<string, unknown>"; } const properties = Object.entries(definition.properties).map(([key, prop]) => { var _a, _b; const isRequired = (_b = (_a = definition.required) == null ? void 0 : _a.includes(key)) != null ? _b : false; const questionMark = isRequired ? "" : "?"; const sanitizedKey = this.getCachedSanitizedName(key); return `${sanitizedKey}${questionMark}: ${this.resolveSwaggerTypeCached(prop)}`; }).join("; "); return `{ ${properties} }`; } resolveReference(ref) { const refName = ref.split("/").pop(); if (!refName) { console.warn(`Invalid reference format: ${ref}`); return "unknown"; } return this.getCachedPascalCase(refName); } collectSdkTypes() { var _a; const { response } = (_a = this.config.options.validation) != null ? _a : {}; const typeParameters = [ "TResponseType extends 'arraybuffer' | 'blob' | 'json' | 'text'" ]; const properties = [ { name: "headers", type: "HttpHeaders", hasQuestionToken: true }, { name: "reportProgress", type: "boolean", hasQuestionToken: true }, { name: "responseType", type: "TResponseType", hasQuestionToken: true }, { name: "withCredentials", type: "boolean", hasQuestionToken: true }, { name: "context", type: "HttpContext", hasQuestionToken: true } ]; if (response) { properties.push({ name: "parse", type: "(response: unknown) => TReturnType", hasQuestionToken: true }); typeParameters.push("TReturnType"); } this.statements.push({ kind: import_ts_morph.StructureKind.Interface, name: "RequestOptions", isExported: true, typeParameters, properties, docs: [ "Request Options for Angular HttpClient requests" ] }); } applyBatchUpdates() { this.sourceFile.insertText(0, TYPE_GENERATOR_HEADER_COMMENT); this.sourceFile.addImportDeclarations([ { namedImports: [ "HttpContext", "HttpHeaders" ], moduleSpecifier: "@angular/common/http" } ]); this.sourceFile.addStatements(this.statements); } finalize() { return __async(this, null, function* () { this.sourceFile.formatText(); yield this.sourceFile.save(); }); } // Cached helper methods getCachedPascalCase(str) { if (!this.pascalCaseCache.has(str)) { this.pascalCaseCache.set(str, this.pascalCaseForEnums(str)); } return this.pascalCaseCache.get(str); } getCachedSanitizedName(name) { if (!this.sanitizedNameCache.has(name)) { this.sanitizedNameCache.set(name, this.sanitizePropertyName(name)); } return this.sanitizedNameCache.get(name); } // Original helper methods mapSwaggerTypeToTypeScript(type, format, isNullable) { switch (type) { case "string": if (format === "date" || format === "date-time") { const dateType = this.config.options.dateType === "Date" ? "Date" : "string"; return this.nullableType(dateType, isNullable); } if (format === "binary") return "Blob"; if (format === "uuid") return "string"; if (format === "email") return "string"; if (format === "uri") return "string"; return this.nullableType("string", isNullable); case "number": case "integer": return this.nullableType("number", isNullable); case "boolean": return this.nullableType("boolean", isNullable); case "array": return this.nullableType("any[]", isNullable); case "object": return this.nullableType("Record<string, unknown>", isNullable); case "null": return this.nullableType("null", isNullable); default: if (Array.isArray(type)) { const types = type.map((t) => this.mapSwaggerTypeToTypeScript(t, void 0, isNullable)); return this.nullableType(types.join(" | "), isNullable); } return this.nullableType("any", isNullable); } } nullableType(type, isNullable) { return type + (isNullable ? " | null" : ""); } pascalCaseForEnums(str) { return str.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1"); } sanitizePropertyName(name) { if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) { return `"${name}"`; } return name; } toEnumKey(value) { const str = value.toString(); const hasLeadingMinus = str.startsWith("-"); const pascalCased = pascalCase(str); return hasLeadingMinus ? pascalCased.replace(/^([0-9])/, "_n$1") : pascalCased.replace(/^([0-9])/, "_$1"); } getArrayItemType(items) { if (Array.isArray(items)) { const types = items.map((item) => this.resolveSwaggerTypeCached(item)); return `[${types.join(", ")}]`; } else { return this.resolveSwaggerTypeCached(items); } } escapeString(str) { return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); } }; __name(_TypeGenerator, "TypeGenerator"); var TypeGenerator = _TypeGenerator; // src/lib/generators/utility/token.generator.ts var import_ts_morph2 = require("ts-morph"); var path2 = __toESM(require("path")); var _TokenGenerator = class _TokenGenerator { constructor(project, clientName = "default") { __publicField(this, "project"); __publicField(this, "clientName"); this.project = project; this.clientName = clientName; } generate(outputDir) { const tokensDir = path2.join(outputDir, "tokens"); const filePath = path2.join(tokensDir, "index.ts"); const sourceFile = this.project.createSourceFile(filePath, "", { overwrite: true }); sourceFile.addImportDeclarations([ { namedImports: [ "InjectionToken" ], moduleSpecifier: "@angular/core" }, { namedImports: [ "HttpInterceptor", "HttpContextToken" ], moduleSpecifier: "@angular/common/http" } ]); const basePathTokenName = this.getBasePathTokenName(); const interceptorsTokenName = this.getInterceptorsTokenName(); const clientContextTokenName = this.getClientContextTokenName(); sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph2.VariableDeclarationKind.Const, declarations: [ { name: basePathTokenName, initializer: `new InjectionToken<string>('${basePathTokenName}', { providedIn: 'root', factory: () => '/api', // Default fallback })` } ], leadingTrivia: `/** * Injection token for the ${this.clientName} client base API path */ ` }); sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph2.VariableDeclarationKind.Const, declarations: [ { name: interceptorsTokenName, initializer: `new InjectionToken<HttpInterceptor[]>('${interceptorsTokenName}', { providedIn: 'root', factory: () => [], // Default empty array })` } ], leadingTrivia: `/** * Injection token for the ${this.clientName} client HTTP interceptor instances */ ` }); sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph2.VariableDeclarationKind.Const, declarations: [ { name: clientContextTokenName, initializer: `new HttpContextToken<string>(() => '${this.clientName}')` } ], leadingTrivia: `/** * HttpContext token to identify requests belonging to the ${this.clientName} client */ ` }); if (this.clientName === "default") { sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph2.VariableDeclarationKind.Const, declarations: [ { name: "BASE_PATH", initializer: basePathTokenName } ], leadingTrivia: `/** * @deprecated Use ${basePathTokenName} instead */ ` }); sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph2.VariableDeclarationKind.Const, declarations: [ { name: "CLIENT_CONTEXT_TOKEN", initializer: clientContextTokenName } ], leadingTrivia: `/** * @deprecated Use ${clientContextTokenName} instead */ ` }); } sourceFile.formatText(); sourceFile.saveSync(); } getBasePathTokenName() { const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `BASE_PATH_${clientSuffix}`; } getInterceptorsTokenName() { const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `HTTP_INTERCEPTORS_${clientSuffix}`; } getClientContextTokenName() { const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_"); return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`; } }; __name(_TokenGenerator, "TokenGenerator"); var TokenGenerator = _TokenGenerator; // src/lib/generators/utility/file-download.generator.ts var path3 = __toESM(require("path")); var _FileDownloadGenerator = class _FileDownloadGenerator { constructor(project) { __publicField(this, "project"); this.project = project; } generate(outputDir) { const utilsDir = path3.join(outputDir, "utils"); const filePath = path3.join(utilsDir, "file-download.ts"); const sourceFile = this.project.createSourceFile(filePath, "", { overwrite: true }); sourceFile.addImportDeclarations([ { namedImports: [ "Observable", "tap" ], moduleSpecifier: "rxjs" } ]); sourceFile.addFunction({ name: "downloadFile", isExported: true, parameters: [ { name: "blob", type: "Blob" }, { name: "filename", type: "string" }, { name: "mimeType", type: "string", hasQuestionToken: true } ], returnType: "void", statements: ` // Create a temporary URL for the blob const url = window.URL.createObjectURL(blob); // Create a temporary anchor element and trigger download const link = document.createElement('a'); link.href = url; link.download = filename; // Append to body, click, and remove document.body.appendChild(link); link.click(); document.body.removeChild(link); // Clean up the URL window.URL.revokeObjectURL(url);` }); sourceFile.addFunction({ name: "downloadFileOperator", isExported: true, typeParameters: [ { name: "T", constraint: "Blob" } ], parameters: [ { name: "filename", type: "string | ((blob: T) => string)" }, { name: "mimeType", type: "string", hasQuestionToken: true } ], returnType: "(source: Observable<T>) => Observable<T>", statements: ` return (source: Observable<T>) => { return source.pipe( tap((blob: T) => { const actualFilename = typeof filename === 'function' ? filename(blob) : filename; downloadFile(blob, actualFilename, mimeType); }) ); };` }); sourceFile.addFunction({ name: "extractFilenameFromContentDisposition", isExported: true, parameters: [ { name: "contentDisposition", type: "string | null" }, { name: "fallbackFilename", type: "string", initializer: '"download"' } ], returnType: "string", statements: ` if (!contentDisposition) { return fallbackFilename; } // Try to extract filename from Content-Disposition header // Supports both "filename=" and "filename*=" formats const filenameMatch = contentDisposition.match(/filename\\*?=['"]?([^'"\\n;]+)['"]?/i); if (filenameMatch && filenameMatch[1]) { // Decode if it's RFC 5987 encoded (filename*=UTF-8''...) const filename = filenameMatch[1]; if (filename.includes("''")) { const parts = filename.split("''"); if (parts.length === 2) { try { return decodeURIComponent(parts[1]); } catch { return parts[1]; } } } return filename; } return fallbackFilename;` }); sourceFile.formatText(); sourceFile.saveSync(); } }; __name(_FileDownloadGenerator, "FileDownloadGenerator"); var FileDownloadGenerator = _FileDownloadGenerator; // src/lib/generators/utility/date-transformer.generator.ts var import_ts_morph3 = require("ts-morph"); var path4 = __toESM(require("path")); var _DateTransformerGenerator = class _DateTransformerGenerator { constructor(project) { __publicField(this, "project"); this.project = project; } generate(outputDir) { const utilsDir = path4.join(outputDir, "utils"); const filePath = path4.join(utilsDir, "date-transformer.ts"); const sourceFile = this.project.createSourceFile(filePath, "", { overwrite: true }); sourceFile.addImportDeclarations([ { namedImports: [ "HttpEvent", "HttpHandler", "HttpInterceptor", "HttpRequest", "HttpResponse" ], moduleSpecifier: "@angular/common/http" }, { namedImports: [ "Injectable" ], moduleSpecifier: "@angular/core" }, { namedImports: [ "Observable", "map" ], moduleSpecifier: "rxjs" } ]); sourceFile.addVariableStatement({ isExported: true, declarationKind: import_ts_morph3.VariableDeclarationKind.Const, declarations: [ { name: "ISO_DATE_REGEX", initializer: "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?$/" } ] }); sourceFile.addFunction({ name: "transformDates", isExported: true, parameters: [ { name: "obj", type: "any" } ], returnType: "any", statements: ` if (obj === null || obj === undefined || t