UNPKG

@hey-api/openapi-ts

Version:

🌀 OpenAPI to TypeScript codegen. Production-grade SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.

1,843 lines (1,842 loc) 545 kB
import { createRequire } from "node:module"; import { StructureModel, detectInteractiveSession, fromRef, isNode, isRef, isSymbol, loadConfigFile, log, nodeBrand, ref, refs } from "@hey-api/codegen-core"; import { ConfigError, OperationPath, OperationStrategy, applyNaming, buildSymbolIn, childContext, createOperationKey, createSchemaProcessor, createSchemaWalker, deduplicateSchema, definePluginConfig, dependencyFactory, ensureDirSync, escapeComment, findTsConfigPath, getInput, getLogs, getParser, hasOperationDataRequired, hasParameterGroupObjectRequired, isEnvironment, mappers, operationPagination, operationResponsesMap, outputHeaderToPrefix, parseUrl, pathToJsonPointer, pathToName, refToName, requestValidatorLayers, resolveSource, resolveValidatorLayer, satisfies, statusCodeToGroup, toCase, valueToObject, warnOnConflictingDuplicatePlugins } from "@hey-api/shared"; import colors from "ansi-colors"; import path from "node:path"; import { fileURLToPath } from "node:url"; import fs from "node:fs"; import { parseTsconfig } from "get-tsconfig"; import ts from "typescript"; //#region \0rolldown/runtime.js var __require = /* @__PURE__ */ createRequire(import.meta.url); //#endregion //#region src/config/expand.ts function expandToJobs(configs) { const jobs = []; let jobIndex = 0; for (const config of configs) { const inputs = getInput(config); const outputs = config.output instanceof Array ? config.output : [config.output]; if (outputs.length === 1) jobs.push({ config: { ...config, input: inputs, output: outputs[0] }, index: jobIndex++ }); else if (outputs.length > 1 && inputs.length !== outputs.length) { console.warn(`⚙️ ${colors.yellow("Warning:")} You provided ${colors.cyan(String(inputs.length))} ${colors.cyan(inputs.length === 1 ? "input" : "inputs")} and ${colors.yellow(String(outputs.length))} ${colors.yellow("outputs")}. This will produce identical output in multiple locations. You likely want to provide a single output or the same number of outputs as inputs.`); for (const output of outputs) jobs.push({ config: { ...config, input: inputs, output }, index: jobIndex++ }); } else if (outputs.length > 1) outputs.forEach((output, index) => { jobs.push({ config: { ...config, input: inputs[index], output }, index: jobIndex++ }); }); } return jobs; } //#endregion //#region src/config/packages.ts /** * Finds and reads the project's package.json file by searching upwards from the config file location, * or from process.cwd() if no config file is provided. * This ensures we get the correct dependencies even in monorepo setups. * * @param configFilePath - The path to the configuration file (e.g., openapi-ts.config.ts) * @returns An object containing all project dependencies (dependencies, devDependencies, peerDependencies, optionalDependencies) */ const getProjectDependencies = (configFilePath) => { let currentDir = configFilePath ? path.dirname(configFilePath) : process.cwd(); while (currentDir !== path.dirname(currentDir)) { const packageJsonPath = path.join(currentDir, "package.json"); if (fs.existsSync(packageJsonPath)) try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); return { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies, ...packageJson.optionalDependencies }; } catch {} const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; } return {}; }; //#endregion //#region src/config/output/postprocess.ts const postProcessors = { "biome:check": { args: [ "check", "--write", "{{path}}" ], command: "biome", name: "Biome (Check)" }, "biome:format": { args: [ "format", "--write", "{{path}}" ], command: "biome", name: "Biome (Format)" }, "biome:lint": { args: [ "lint", "--write", "{{path}}" ], command: "biome", name: "Biome (Lint)" }, eslint: { args: ["{{path}}", "--fix"], command: "eslint", name: "ESLint" }, oxfmt: { args: ["{{path}}"], command: "oxfmt", name: "Oxfmt" }, oxlint: { args: ["--fix", "{{path}}"], command: "oxlint", name: "Oxlint" }, prettier: { args: [ "--ignore-unknown", "{{path}}", "--write", "--ignore-path", "./.prettierignore" ], command: "prettier", name: "Prettier" } }; //#endregion //#region src/config/output/config.ts const __filename$1 = fileURLToPath(import.meta.url); const __dirname$1 = path.dirname(__filename$1); function getOutput(userConfig) { if (userConfig.output instanceof Array) throw new Error("Unexpected array of outputs in user configuration. This should have been expanded already."); const userOutput = typeof userConfig.output === "string" ? { path: userConfig.output } : userConfig.output ?? {}; const legacyPostProcess = resolveLegacyPostProcess(userOutput); const output = valueToObject({ defaultValue: { clean: true, entryFile: true, fileName: { case: "preserve", name: "{{name}}", suffix: ".gen" }, format: null, lint: null, module: {}, path: "", postProcess: [], preferExportAll: false }, mappers: { object: (fields, defaultValue) => ({ ...fields, fileName: valueToObject({ defaultValue: { ...defaultValue.fileName }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: fields.fileName }), module: valueToObject({ defaultValue: { extension: fields.importFileExtension, resolve: fields.resolveModuleName }, mappers: { object: (moduleFields) => ({ ...moduleFields, extension: fields.importFileExtension ?? moduleFields.extension, resolve: fields.resolveModuleName ?? moduleFields.resolve }) }, value: fields.module }) }) }, value: userOutput }); output.tsConfig = loadTsConfig(findTsConfigPath(__dirname$1, output.tsConfigPath)); if (output.module.extension === void 0 && (output.tsConfig?.compilerOptions?.moduleResolution === "nodenext" || output.tsConfig?.compilerOptions?.moduleResolution === "NodeNext" || output.tsConfig?.compilerOptions?.moduleResolution === "node16" || output.tsConfig?.compilerOptions?.moduleResolution === "Node16" || output.tsConfig?.compilerOptions?.module === "nodenext" || output.tsConfig?.compilerOptions?.module === "NodeNext" || output.tsConfig?.compilerOptions?.module === "node16" || output.tsConfig?.compilerOptions?.module === "Node16")) output.module.extension = ".js"; if (output.module.extension && !output.module.extension.startsWith(".")) output.module.extension = `.${output.module.extension}`; output.postProcess = normalizePostProcess(userOutput.postProcess ?? legacyPostProcess); output.source = resolveSource(output); return output; } function resolveLegacyPostProcess(config) { const result = []; if (config.lint !== void 0) { let processor; let preset; if (config.lint) { preset = config.lint === "biome" ? "biome:lint" : config.lint; processor = postProcessors[preset]; if (processor) result.push(processor); } log.warnDeprecated({ context: "output", field: "lint", replacement: `postProcess: [${processor && preset ? `'${preset}'` : ""}]` }); } if (config.format !== void 0) { let processor; let preset; if (config.format) { preset = config.format === "biome" ? "biome:format" : config.format; processor = postProcessors[preset]; if (processor) result.push(processor); } log.warnDeprecated({ context: "output", field: "format", replacement: `postProcess: [${processor && preset ? `'${preset}'` : ""}]` }); } return result; } function normalizePostProcess(input) { if (!input) return []; return input.map((item) => { if (typeof item === "string") { const preset = postProcessors[item]; if (!preset) throw new Error(`Unknown post-processor preset: "${item}"`); return preset; } return { name: item.name ?? item.command, ...item }; }); } function loadTsConfig(configPath) { if (!configPath) return null; try { return parseTsconfig(configPath); } catch { throw new Error(`Couldn't read tsconfig from path: ${configPath}`); } } //#endregion //#region src/plugins/@angular/common/httpRequests/config.ts function resolveHttpRequests(config, context) { let input = config.httpRequests; if (typeof input === "string" || typeof input === "function") input = { strategy: input }; else if (typeof input === "boolean") input = { enabled: input }; else if (!input) input = {}; const strategy = input.strategy ?? "flat"; return context.valueToObject({ defaultValue: { container: "class", enabled: true, methods: "instance", nesting: "operationId", nestingDelimiters: /[./]/, strategy, strategyDefaultTag: "default" }, mappers: { object(value) { value.containerName = context.valueToObject({ defaultValue: strategy === "single" ? { casing: "PascalCase", name: "HttpRequests" } : { casing: "PascalCase" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.containerName }); value.methodName = context.valueToObject({ defaultValue: strategy === "flat" ? { casing: "camelCase", name: "{{name}}Request" } : { casing: "camelCase" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.methodName }); value.segmentName = context.valueToObject({ defaultValue: { casing: "PascalCase", name: "{{name}}Requests" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.segmentName }); return value; } }, value: input }); } //#endregion //#region src/plugins/@angular/common/httpRequests/resolve.ts function resolvePath$3(plugin) { if (plugin.config.httpRequests.nesting === "id") return OperationPath.id(); if (plugin.config.httpRequests.nesting === "operationId") return OperationPath.fromOperationId({ delimiters: plugin.config.httpRequests.nestingDelimiters, fallback: OperationPath.id() }); return plugin.config.httpRequests.nesting; } function resolveHttpRequestsStrategy(plugin) { if (plugin.config.httpRequests.strategy === "flat") return OperationStrategy.flat({ path: (operation) => [resolvePath$3(plugin)(operation).join(".")] }); if (plugin.config.httpRequests.strategy === "single") { const root = plugin.config.httpRequests.containerName; return OperationStrategy.single({ path: resolvePath$3(plugin), root: typeof root.name === "string" ? root.name : root.name?.("") ?? "" }); } if (plugin.config.httpRequests.strategy === "byTags") return OperationStrategy.byTags({ fallback: plugin.config.httpRequests.strategyDefaultTag, path: resolvePath$3(plugin) }); return plugin.config.httpRequests.strategy; } //#endregion //#region src/plugins/@angular/common/httpResources/config.ts function resolveHttpResources(config, context) { let input = config.httpResources; if (typeof input === "string" || typeof input === "function") input = { strategy: input }; else if (typeof input === "boolean") input = { enabled: input }; else if (!input) input = {}; const strategy = input.strategy ?? "flat"; return context.valueToObject({ defaultValue: { container: "class", enabled: true, methods: "instance", nesting: "operationId", nestingDelimiters: /[./]/, strategy, strategyDefaultTag: "default" }, mappers: { object(value) { value.containerName = context.valueToObject({ defaultValue: strategy === "single" ? { casing: "PascalCase", name: "HttpResources" } : { casing: "PascalCase" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.containerName }); value.methodName = context.valueToObject({ defaultValue: strategy === "flat" ? { casing: "camelCase", name: "{{name}}Resource" } : { casing: "camelCase" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.methodName }); value.segmentName = context.valueToObject({ defaultValue: { casing: "PascalCase", name: "{{name}}Resources" }, mappers: { function: (name) => ({ name }), string: (name) => ({ name }) }, value: value.segmentName }); return value; } }, value: input }); } //#endregion //#region src/plugins/@angular/common/httpResources/resolve.ts function resolvePath$2(plugin) { if (plugin.config.httpResources.nesting === "id") return OperationPath.id(); if (plugin.config.httpResources.nesting === "operationId") return OperationPath.fromOperationId({ delimiters: plugin.config.httpResources.nestingDelimiters, fallback: OperationPath.id() }); return plugin.config.httpResources.nesting; } function resolveHttpResourcesStrategy(plugin) { if (plugin.config.httpResources.strategy === "flat") return OperationStrategy.flat({ path: (operation) => [resolvePath$2(plugin)(operation).join(".")] }); if (plugin.config.httpResources.strategy === "single") { const root = plugin.config.httpResources.containerName; return OperationStrategy.single({ path: resolvePath$2(plugin), root: typeof root.name === "string" ? root.name : root.name?.("") ?? "" }); } if (plugin.config.httpResources.strategy === "byTags") return OperationStrategy.byTags({ fallback: plugin.config.httpResources.strategyDefaultTag, path: resolvePath$2(plugin) }); return plugin.config.httpResources.strategy; } //#endregion //#region src/config/utils.ts function getTypedConfig(plugin) { if ("context" in plugin) return plugin.context.config; return plugin.config; } //#endregion //#region src/plugins/@hey-api/client-core/utils.ts function getClientBaseUrlKey(config) { const client = getClientPlugin(config); if (client.name === "@hey-api/client-axios" || client.name === "@hey-api/client-nuxt") return "baseURL"; return "baseUrl"; } function getClientPlugin(config) { for (const name of config.pluginOrder) { const plugin = config.plugins[name]; if (plugin?.tags?.includes("client")) return plugin; } return { config: { name: "" }, name: "" }; } //#endregion //#region src/ts-dsl/base.ts var TsDsl = class { analyze(_) {} clone() { const cloned = Object.create(Object.getPrototypeOf(this)); Object.assign(cloned, this); return cloned; } exported; file; get name() { return { ...this._name, set: (value) => { this._name = ref(value); if (isSymbol(value)) value.setNode(this); }, toString: () => this._name ? this.$name(this._name) : "" }; } nameSanitizer; language = "typescript"; parent; root = false; scope = "value"; structuralChildren; structuralParents; symbol; toAst() {} "~brand" = nodeBrand; $if(value, ifTrue, ifFalse) { if (value) { let result; try { result = ifTrue?.(this, value); } catch {} if (result === void 0) try { result = ifTrue?.(value); } catch {} if (result === void 0) try { result = ifTrue?.(); } catch {} return result ?? this; } if (ifFalse) { let result; try { result = ifFalse?.(this, value); } catch {} if (result === void 0) try { result = ifFalse?.(value); } catch {} if (result === void 0) try { result = ifFalse?.(); } catch {} return result ?? this; } return this; } $maybeId(expr) { return typeof expr === "string" ? ts.factory.createIdentifier(expr) : expr; } $name(name) { const value = fromRef(name); if (isSymbol(value)) try { return value.finalName; } catch { return value.name; } return String(value); } $node(value) { if (value === void 0) return; if (isRef(value)) value = fromRef(value); if (isSymbol(value)) return this.$maybeId(value.finalName); if (typeof value === "string") return this.$maybeId(value); if (value instanceof Array) return value.map((item) => { if (isRef(item)) item = fromRef(item); return this.unwrap(item); }); return this.unwrap(value); } $type(value, args) { if (value === void 0) return; if (isRef(value)) value = fromRef(value); if (isSymbol(value)) return ts.factory.createTypeReferenceNode(value.finalName, args); if (typeof value === "string") return ts.factory.createTypeReferenceNode(value, args); if (typeof value === "boolean") { const literal = value ? ts.factory.createTrue() : ts.factory.createFalse(); return ts.factory.createLiteralTypeNode(literal); } if (typeof value === "number") return ts.factory.createLiteralTypeNode(ts.factory.createNumericLiteral(value)); if (value instanceof Array) return value.map((item) => this.$type(item, args)); return this.unwrap(value); } _name; /** Unwraps nested nodes into raw TypeScript AST. */ unwrap(value) { return isNode(value) ? value.toAst() : value; } }; var TypeTsDsl = class extends TsDsl {}; //#endregion //#region src/ts-dsl/expr/id.ts const Mixed$56 = TsDsl; var IdTsDsl = class extends Mixed$56 { "~dsl" = "IdTsDsl"; constructor(name) { super(); this.name.set(name); } analyze(ctx) { super.analyze(ctx); } toAst() { return ts.factory.createIdentifier(this.name.toString()); } }; //#endregion //#region src/ts-dsl/layout/newline.ts var NewlineTsDsl = class extends TsDsl { "~dsl" = "NewlineTsDsl"; analyze(ctx) { super.analyze(ctx); } toAst() { return this.$node(new IdTsDsl("\n")); } }; //#endregion //#region src/ts-dsl/mixins/args.ts /** * Adds `.arg()` and `.args()` for managing expression arguments in call-like nodes. */ function ArgsMixin(Base) { class Args extends Base { _args = []; analyze(ctx) { super.analyze(ctx); for (const arg of this._args) ctx.analyze(arg); } arg(arg) { if (arg !== void 0) this._args.push(ref(arg)); return this; } args(...args) { this._args.push(...args.filter((a) => a !== void 0).map((a) => ref(a))); return this; } $args() { return this.$node(this._args).map((arg) => this.$node(arg)); } } return Args; } //#endregion //#region src/ts-dsl/expr/prefix.ts const Mixed$55 = TsDsl; var PrefixTsDsl = class extends Mixed$55 { "~dsl" = "PrefixTsDsl"; _expr; _op; constructor(expr, op) { super(); this._expr = expr; this._op = op; } analyze(ctx) { super.analyze(ctx); ctx.analyze(this._expr); } /** Returns true when all required builder calls are present. */ get isValid() { return !this.missingRequiredCalls().length; } /** Sets the operand (the expression being prefixed). */ expr(expr) { this._expr = expr; return this; } /** Sets the operator to MinusToken for negation (`-`). */ neg() { this._op = ts.SyntaxKind.MinusToken; return this; } /** Sets the operator to ExclamationToken for logical NOT (`!`). */ not() { this._op = ts.SyntaxKind.ExclamationToken; return this; } /** Sets the operator (e.g., `ts.SyntaxKind.ExclamationToken` for `!`). */ op(op) { this._op = op; return this; } toAst() { this.$validate(); return ts.factory.createPrefixUnaryExpression(this._op, this.$node(this._expr)); } $validate() { const missing = this.missingRequiredCalls(); if (!missing.length) return; throw new Error(`Prefix unary expression missing ${missing.join(" and ")}`); } missingRequiredCalls() { const missing = []; if (!this._expr) missing.push(".expr()"); if (!this._op) missing.push("operator (e.g., .not(), .neg())"); return missing; } }; //#endregion //#region src/ts-dsl/utils/factories.ts function createFactory(name) { let impl; const slot = ((...args) => { if (!impl) throw new Error(`${name} factory not registered`); return impl(...args); }); slot.set = (fn) => { impl = fn; }; return slot; } const f = { as: createFactory("as"), attr: createFactory("attr"), await: createFactory("await"), call: createFactory("call"), method: createFactory("method"), new: createFactory("new"), return: createFactory("return"), spread: createFactory("spread"), type: { expr: createFactory("type.expr"), idx: createFactory("type.idx"), operator: createFactory("type.operator"), query: createFactory("type.query"), tupleMember: createFactory("type.tupleMember") }, typeofExpr: createFactory("typeofExpr") }; //#endregion //#region src/ts-dsl/mixins/as.ts function AsMixin(Base) { class As extends Base { analyze(ctx) { super.analyze(ctx); } as(...args) { return f.as(this, ...args); } } return As; } //#endregion //#region src/ts-dsl/expr/literal.ts const Mixed$54 = AsMixin(TsDsl); var LiteralTsDsl = class extends Mixed$54 { "~dsl" = "LiteralTsDsl"; value; constructor(value) { super(); this.value = value; } analyze(ctx) { super.analyze(ctx); } toAst() { if (typeof this.value === "boolean") return this.value ? ts.factory.createTrue() : ts.factory.createFalse(); if (typeof this.value === "number") { const expr = ts.factory.createNumericLiteral(Math.abs(this.value)); return this.value < 0 ? this.$node(new PrefixTsDsl(expr).neg()) : expr; } if (typeof this.value === "string") return ts.factory.createStringLiteral(this.value, true); if (typeof this.value === "bigint") return ts.factory.createBigIntLiteral(this.value.toString()); if (this.value === null) return ts.factory.createNull(); throw new Error(`Unsupported literal: ${String(this.value)}`); } }; const regexp = { illegalStartCharacters: /^[^$_\p{ID_Start}]+/u, number: /^-?\d+(\.\d+)?$/, typeScriptIdentifier: /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u }; const keywords = { browserGlobals: [ "AbortController", "AbortSignal", "Blob", "CustomEvent", "document", "Event", "EventTarget", "fetch", "File", "FileList", "FileReader", "FormData", "Headers", "history", "location", "navigator", "Request", "Response", "TextDecoder", "TextEncoder", "URL", "URLSearchParams", "window" ], javaScriptGlobals: [ "Array", "ArrayBuffer", "atob", "BigInt", "Boolean", "btoa", "clearInterval", "clearTimeout", "console", "crypto", "DataView", "Date", "Error", "Function", "globalThis", "Infinity", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "performance", "Promise", "Proxy", "queueMicrotask", "Reflect", "RegExp", "Set", "setInterval", "setTimeout", "String", "structuredClone", "Symbol", "WeakMap", "WeakSet" ], javaScriptKeywords: [ "arguments", "async", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "false", "finally", "for", "from", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield" ], nodeGlobals: [ "__dirname", "__filename", "Buffer", "exports", "global", "module", "process", "require" ], typeScriptKeywords: [ "any", "as", "bigint", "boolean", "namespace", "never", "null", "number", "string", "symbol", "type", "undefined", "unknown", "void" ] }; //#endregion //#region src/ts-dsl/utils/reserved.ts var ReservedList = class { _array; _set; constructor(values) { this._array = values; this._set = new Set(values); } get "~values"() { return this._set; } /** * Updates the reserved list with new values. * * @param values New reserved values or a function that receives the previous * reserved values and returns the new ones. */ set(values) { const vals = typeof values === "function" ? values(this._array) : values; this._array = vals; this._set = new Set(vals); } }; /** * Reserved names for identifiers. These names will not be used * for variables, functions, classes, or other identifiers in generated code. */ const reserved = { runtime: new ReservedList([ ...keywords.browserGlobals, ...keywords.javaScriptGlobals, ...keywords.javaScriptKeywords, ...keywords.nodeGlobals, ...keywords.typeScriptKeywords ]), type: new ReservedList([...keywords.javaScriptKeywords, ...keywords.typeScriptKeywords]) }; //#endregion //#region src/ts-dsl/utils/name.ts const safeAccessorName = (name) => { regexp.number.lastIndex = 0; if (regexp.number.test(name)) return name.startsWith("-") ? `'${name}'` : name; regexp.typeScriptIdentifier.lastIndex = 0; if (regexp.typeScriptIdentifier.test(name)) return name; return `'${name}'`; }; const safeMemberName = (name) => { regexp.typeScriptIdentifier.lastIndex = 0; if (regexp.typeScriptIdentifier.test(name)) return new IdTsDsl(name); return new LiteralTsDsl(name); }; const safePropName = (name) => { regexp.number.lastIndex = 0; if (regexp.number.test(name)) return name.startsWith("-") ? new LiteralTsDsl(name) : new LiteralTsDsl(Number(name)); regexp.typeScriptIdentifier.lastIndex = 0; if (regexp.typeScriptIdentifier.test(name)) return new IdTsDsl(name); return new LiteralTsDsl(name); }; const validTypeScriptChar = /^[\u200c\u200d\p{ID_Continue}]$/u; const safeName = (name, reserved) => { let sanitized = ""; let index; const first = name[0] ?? ""; regexp.illegalStartCharacters.lastIndex = 0; if (regexp.illegalStartCharacters.test(first)) if (validTypeScriptChar.test(first)) { sanitized += "_"; index = 0; } else { sanitized += "_"; index = 1; } else { sanitized += first; index = 1; } while (index < name.length) { const char = name[index] ?? ""; sanitized += validTypeScriptChar.test(char) ? char : "_"; index += 1; } if (reserved["~values"].has(sanitized)) sanitized = `${sanitized}_`; return sanitized || "_"; }; const safeRuntimeName = (name) => safeName(name, reserved.runtime); const safeTypeName = (name) => safeName(name, reserved.type); //#endregion //#region src/ts-dsl/decl/decorator.ts const Mixed$53 = ArgsMixin(TsDsl); var DecoratorTsDsl = class extends Mixed$53 { "~dsl" = "DecoratorTsDsl"; nameSanitizer = safeRuntimeName; constructor(name, ...args) { super(); this.name.set(name); this.args(...args); } analyze(ctx) { super.analyze(ctx); ctx.analyze(this.name); } toAst() { const target = this.$node(this.name); const args = this.$args(); return ts.factory.createDecorator(args.length ? ts.factory.createCallExpression(target, void 0, args) : target); } }; //#endregion //#region src/ts-dsl/mixins/decorator.ts function DecoratorMixin(Base) { class Decorator extends Base { decorators = []; analyze(ctx) { super.analyze(ctx); for (const decorator of this.decorators) ctx.analyze(decorator); } decorator(name, ...args) { this.decorators.push(new DecoratorTsDsl(name, ...args)); return this; } $decorators() { return this.$node(this.decorators); } } return Decorator; } //#endregion //#region src/ts-dsl/utils/context.ts function accessChainToNode(accessChain) { let result; accessChain.forEach((node, index) => { if (index === 0) result = node; else result = result.attr(node.name); }); return result; } function getAccessChainForNode(node) { const accessChain = structuralToAccessChain([...getStructuralChainForNode(node, /* @__PURE__ */ new Set())]); if (!accessChain.length) return [node.clone()]; return accessChain.map((node) => node.clone()); } function getScope(node) { return node.scope ?? "value"; } function getStructuralChainForNode(node, visited) { if (visited.has(node)) return []; visited.add(node); if (isStopNode(node)) return []; if (node.structuralParents) for (const [parent] of node.structuralParents) { if (getScope(parent) !== getScope(node)) continue; const chain = getStructuralChainForNode(parent, visited); if (chain.length) return [...chain, node]; } if (!node.root) return []; return [node]; } function isAccessorNode(node) { return node["~dsl"] === "FieldTsDsl" || node["~dsl"] === "GetterTsDsl" || node["~dsl"] === "MethodTsDsl"; } function isStopNode(node) { return node["~dsl"] === "FuncTsDsl" || node["~dsl"] === "TemplateTsDsl"; } /** * Fold a structural chain to an access chain by removing * non-accessor nodes. */ function structuralToAccessChain(structuralChain) { const accessChain = []; structuralChain.forEach((node, index) => { if (index === 0) accessChain.push(node); else if (isAccessorNode(node)) accessChain.push(node); }); return accessChain; } function transformAccessChain(accessChain, options = {}) { return accessChain.map((node, index) => { const transformedNode = options.transform?.(node, index, accessChain); if (transformedNode) return transformedNode; const accessNode = node.toAccessNode?.(node, options, { chain: accessChain, index, isLeaf: index === accessChain.length - 1, isRoot: index === 0, length: accessChain.length }); if (accessNode) return accessNode; if (index === 0) { if (node["~dsl"] === "ClassTsDsl") { const nextNode = accessChain[index + 1]; if (nextNode && isAccessorNode(nextNode)) { if (nextNode.hasModifier("static")) return $(node.name); } return $.new(node.name).args(); } return $(node.name); } return node; }); } var TsDslContext = class { /** * Build an expression for accessing the node. * * @param node - The node or symbol to build access for * @param options - Access options * @returns Expression for accessing the node * * @example * ```ts * ctx.access(node); // → Expression for accessing the node * ``` */ access(node, options) { const n = isSymbol(node) ? node.node : node; if (!n) throw new Error(`Symbol ${node.name} is not resolved to a node.`); return accessChainToNode(transformAccessChain(getAccessChainForNode(n), options)); } /** * Build an example. * * @param node - The node to generate an example for * @param options - Example options * @returns Full example string * * @example * ```ts * ctx.example(node, { moduleName: 'my-sdk' }); // → Full example string * ``` */ example(node, options, astOptions) { if (astOptions) return TypeScriptRenderer.astToString(astOptions); options ||= {}; const accessChain = getAccessChainForNode(node); if (options.importName) accessChain[0].name.set(options.importName); const importNode = $(accessChain[0].name.toString()); const finalChain = transformAccessChain(accessChain, { context: "example" }); const setupNode = options.importSetup ? typeof options.importSetup === "function" ? options.importSetup({ $, node: importNode }) : options.importSetup : finalChain[0]; const setupName = options.setupName; let payload = typeof options.payload === "function" ? options.payload({ $ }) : options.payload; payload = payload instanceof Array ? payload : payload ? [payload] : []; let nodes = []; if (setupName) nodes = [$.const(setupName).assign(setupNode), $.await(accessChainToNode([$(setupName), ...finalChain.slice(1)]).call(...payload))]; else nodes = [$.await(accessChainToNode([setupNode, ...finalChain.slice(1)]).call(...payload))]; const localName = importNode.name.toString(); return TypeScriptRenderer.astToString({ imports: [[{ imports: !options.importKind || options.importKind === "named" ? [{ isTypeOnly: false, localName, sourceName: localName }] : [], isTypeOnly: false, kind: options.importKind ?? "named", localName: options.importKind !== "named" ? localName : void 0, modulePath: options.moduleName ?? "your-package" }]], nodes, trailingNewline: false }); } }; const ctx = new TsDslContext(); //#endregion //#region src/ts-dsl/layout/doc.ts var DocTsDsl = class extends TsDsl { "~dsl" = "DocTsDsl"; _lines = []; constructor(lines, fn) { super(); if (lines) this.add(lines); fn?.(this); } analyze(ctx) { super.analyze(ctx); } add(lines) { this._lines.push(lines); return this; } apply(node) { const lines = this._lines.reduce((lines, line) => { if (typeof line === "function") line = line(ctx); for (const l of typeof line === "string" ? [line] : line) if (l || l === "") lines.push(l); return lines; }, []); if (!lines.length) return node; const jsdocTexts = lines.map((line) => ts.factory.createJSDocText(`${line}\n`)); const jsdoc = ts.factory.createJSDocComment(ts.factory.createNodeArray(jsdocTexts), void 0); const cleanedJsdoc = ts.createPrinter().printNode(ts.EmitHint.Unspecified, jsdoc, node.getSourceFile?.() ?? ts.createSourceFile("", "", ts.ScriptTarget.Latest)).replace("/*", "").replace("* */", ""); ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, cleanedJsdoc, true); return node; } toAst() { return this.$node(new IdTsDsl("")); } }; //#endregion //#region src/ts-dsl/mixins/doc.ts function DocMixin(Base) { class Doc extends Base { _doc; analyze(ctx) { super.analyze(ctx); } doc(lines, fn) { this._doc = new DocTsDsl(lines, fn); return this; } $docs(node) { return this._doc ? this._doc.apply(node) : node; } } return Doc; } //#endregion //#region src/ts-dsl/mixins/modifiers.ts function modifierToKind(modifier) { switch (modifier) { case "abstract": return ts.SyntaxKind.AbstractKeyword; case "async": return ts.SyntaxKind.AsyncKeyword; case "const": return ts.SyntaxKind.ConstKeyword; case "declare": return ts.SyntaxKind.DeclareKeyword; case "default": return ts.SyntaxKind.DefaultKeyword; case "export": return ts.SyntaxKind.ExportKeyword; case "override": return ts.SyntaxKind.OverrideKeyword; case "private": return ts.SyntaxKind.PrivateKeyword; case "protected": return ts.SyntaxKind.ProtectedKeyword; case "public": return ts.SyntaxKind.PublicKeyword; case "readonly": return ts.SyntaxKind.ReadonlyKeyword; case "static": return ts.SyntaxKind.StaticKeyword; } } function ModifiersMixin(Base) { class Modifiers extends Base { modifiers = []; analyze(ctx) { super.analyze(ctx); } hasModifier(modifier) { const kind = modifierToKind(modifier); return Boolean(this.modifiers.find((mod) => mod.kind === kind)); } _m(modifier, condition) { if (condition) { const kind = modifierToKind(modifier); this.modifiers.push(ts.factory.createModifier(kind)); } return this; } } return Modifiers; } /** * Mixin that adds an `abstract` modifier to a node. */ function AbstractMixin(Base) { const Mixed = ModifiersMixin(Base); class Abstract extends Mixed { abstract(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("abstract", cond); } } return Abstract; } /** * Mixin that adds an `async` modifier to a node. */ function AsyncMixin(Base) { const Mixed = ModifiersMixin(Base); class Async extends Mixed { async(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("async", cond); } } return Async; } /** * Mixin that adds a `const` modifier to a node. */ function ConstMixin(Base) { const Mixed = ModifiersMixin(Base); class Const extends Mixed { const(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("const", cond); } } return Const; } /** * Mixin that adds a `default` modifier to a node. */ function DefaultMixin(Base) { const Mixed = ModifiersMixin(Base); class Default extends Mixed { /** * Adds the `default` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ default(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("default", cond); } } return Default; } /** * Mixin that adds an `export` modifier to a node. */ function ExportMixin(Base) { const Mixed = ModifiersMixin(Base); class Export extends Mixed { /** * Adds the `export` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ export(condition) { const cond = !arguments.length ? true : Boolean(condition); this.exported = cond; if (this.symbol) this.symbol.setExported(cond); return this._m("export", cond); } } return Export; } /** * Mixin that adds a `private` modifier to a node. */ function PrivateMixin(Base) { const Mixed = ModifiersMixin(Base); class Private extends Mixed { private(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("private", cond); } } return Private; } /** * Mixin that adds a `protected` modifier to a node. */ function ProtectedMixin(Base) { const Mixed = ModifiersMixin(Base); class Protected extends Mixed { protected(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("protected", cond); } } return Protected; } /** * Mixin that adds a `public` modifier to a node. */ function PublicMixin(Base) { const Mixed = ModifiersMixin(Base); class Public extends Mixed { public(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("public", cond); } } return Public; } /** * Mixin that adds a `readonly` modifier to a node. */ function ReadonlyMixin(Base) { const Mixed = ModifiersMixin(Base); class Readonly extends Mixed { readonly(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("readonly", cond); } } return Readonly; } /** * Mixin that adds a `static` modifier to a node. */ function StaticMixin(Base) { const Mixed = ModifiersMixin(Base); class Static extends Mixed { static(condition) { const cond = !arguments.length ? true : Boolean(condition); return this._m("static", cond); } } return Static; } //#endregion //#region src/ts-dsl/type/param.ts const Mixed$52 = TsDsl; var TypeParamTsDsl = class extends Mixed$52 { "~dsl" = "TypeParamTsDsl"; scope = "type"; constraint; defaultValue; constructor(name, fn) { super(); if (name) this.name.set(name); fn?.(this); } analyze(ctx) { super.analyze(ctx); ctx.analyze(this.name); ctx.analyze(this.constraint); ctx.analyze(this.defaultValue); } /** Sets the parameter default value. */ default(value) { this.defaultValue = ref(value); return this; } extends(constraint) { this.constraint = ref(constraint); return this; } toAst() { if (!this.name.toString()) throw new Error("Missing type name"); return ts.factory.createTypeParameterDeclaration(void 0, this.$node(this.name), this.$type(this.constraint), this.$type(this.defaultValue)); } }; //#endregion //#region src/ts-dsl/mixins/type-params.ts function TypeParamsMixin(Base) { class TypeParams extends Base { _generics = []; analyze(ctx) { super.analyze(ctx); for (const g of this._generics) ctx.analyze(g); } generic(...args) { const g = new TypeParamTsDsl(...args); this._generics.push(g); return this; } generics(...args) { for (let arg of args) { if (typeof arg === "string" || typeof arg === "number" || isSymbol(arg) || isRef(arg)) arg = new TypeParamTsDsl(arg); this._generics.push(arg); } return this; } $generics() { return this.$node(this._generics); } } return TypeParams; } //#endregion //#region src/ts-dsl/mixins/optional.ts function OptionalMixin(Base) { class Optional extends Base { _optional; analyze(ctx) { super.analyze(ctx); } optional(condition) { this._optional = !arguments.length ? true : Boolean(condition); return this; } required(condition) { this._optional = !arguments.length ? false : !condition; return this; } } return Optional; } //#endregion //#region src/ts-dsl/mixins/value.ts function ValueMixin(Base) { class Value extends Base { value; analyze(ctx) { super.analyze(ctx); ctx.analyze(this.value); } assign(expr) { this.value = expr; return this; } $value() { return this.$node(this.value); } } return Value; } //#endregion //#region src/ts-dsl/token.ts var TokenTsDsl = class extends TsDsl { "~dsl" = "TokenTsDsl"; _kind; /** Sets the token kind */ kind(kind) { this._kind = kind; return this; } /** Creates `-` */ minus() { return this.kind(ts.SyntaxKind.MinusToken); } /** Creates `?` (optional) */ optional() { return this.kind(ts.SyntaxKind.QuestionToken); } /** Creates `+` */ plus() { return this.kind(ts.SyntaxKind.PlusToken); } /** Creates `?.` (optional chaining token) */ questionDot() { return this.kind(ts.SyntaxKind.QuestionDotToken); } /** Creates `readonly` */ readonly() { return this.kind(ts.SyntaxKind.ReadonlyKeyword); } /** Creates `...` (spread / rest) */ spread() { return this.kind(ts.SyntaxKind.DotDotDotToken); } toAst() { this.$validate(); return ts.factory.createToken(this._kind); } $validate() { const missing = this.missingRequiredCalls(); if (!missing.length) return; throw new Error(`Token missing ${missing.join(" and ")}`); } missingRequiredCalls() { const missing = []; if (!this._kind) missing.push(".kind()"); return missing; } /** Returns true when all required builder calls are present. */ get isValid() { return !this.missingRequiredCalls().length; } }; //#endregion //#region src/ts-dsl/mixins/type-args.ts function TypeArgsMixin(Base) { class TypeArgs extends Base { _generics = []; analyze(ctx) { super.analyze(ctx); for (const g of this._generics) ctx.analyze(g); } generic(arg) { this._generics.push(ref(arg)); return this; } generics(...args) { this._generics.push(...args.map((a) => ref(a))); return this; } $generics() { return this.$type(this._generics); } } return TypeArgs; } //#endregion //#region src/ts-dsl/mixins/type-expr.ts function TypeExprMixin(Base) { class TypeExpr extends Base { analyze(ctx) { super.analyze(ctx); } idx(...args) { return f.type.idx(this, ...args); } keyof() { return f.type.operator().keyof(this); } readonly() { return f.type.operator().readonly(this); } returnType(...args) { return f.type.expr("ReturnType").generic(f.type.query(this, ...args)); } typeofExpr(...args) { return f.typeofExpr(this, ...args); } typeofType(...args) { return f.type.query(this, ...args); } unique() { return f.type.operator().unique(this); } } return TypeExpr; } //#endregion //#region src/ts-dsl/type/attr.ts const Mixed$51 = TypeExprMixin(TsDsl); var TypeAttrTsDsl = class extends Mixed$51 { "~dsl" = "TypeAttrTsDsl"; scope = "type"; _base; _right; constructor(base, right) { super(); if (right) { this.base(base); this.right(right); } else { this.base(); this.right(base); } } analyze(ctx) { super.analyze(ctx); ctx.analyze(this._base); ctx.analyze(this._right); } /** Returns true when all required builder calls are present. */ get isValid() { return !this.missingRequiredCalls().length; } base(base) { if (isRef(base)) this._base = base; else this._base = base ? ref(base) : void 0; return this; } right(right) { this._right = ref(right); return this; } toAst() { this.$validate(); const left = this.$node(this._base); if (!ts.isEntityName(left)) throw new Error("TypeAttrTsDsl: base must be an EntityName"); return ts.factory.createQualifiedName(left, this.$node(this._right)); } $validate() { const missing = this.missingRequiredCalls(); if (!missing.length) return; throw new Error(`Type attribute missing ${missing.join(" and ")}`); } missingRequiredCalls() { const missing = []; if (!this._base) missing.push(".base()"); if (!this._right) missing.push(".right()"); return missing; } }; //#endregion //#region src/ts-dsl/type/expr.ts const Mixed$50 = TypeArgsMixin(TypeExprMixin(TsDsl)); var TypeExprTsDsl = class extends Mixed$50 { "~dsl" = "TypeExprTsDsl"; scope = "type"; _exprInput; constructor(name, fn) { super(); if (typeof name === "function") name(this); else { this._exprInput = name ? ref(name) : void 0; fn?.(this); } } analyze(ctx) { super.analyze(ctx); ctx.analyze(this._exprInput); } /** Returns true when all required builder calls are present. */ get isValid() { return !this.missingRequiredCalls().length; } /** Accesses a nested type (e.g., `Foo.Bar`). */ attr(right) { this._exprInput = isNode(right) ? ref(right.base(this._exprInput)) : ref(new TypeAttrTsDsl(this._exprInput, right)); return this; } toAst() { this.$validate(); return ts.factory.createTypeReferenceNode(this.$type(this._exprInput), this.$generics()); } $validate() { const missing = this.missingRequiredCalls(); if (!missing.length) return; throw new Error(`Type expression missing ${missing.join(" and ")}`); } missingRequiredCalls() { const missing = []; if (!this._exprInput) missing.push("name or .attr()"); return missing; } }; f.type.expr.set((...args) => new TypeExprTsDsl(...args)); //#endregion //#region src/ts-dsl/decl/field.ts const Mixed$49 = DecoratorMixin(DocMixin(OptionalMixin(PrivateMixin(ProtectedMixin(PublicMixin(ReadonlyMixin(StaticMixin(ValueMixin(TsDsl))))))))); var FieldTsDsl = class extends Mixed$49 { "~dsl" = "FieldTsDsl"; nameSanitizer = safeAccessorName; _type; constructor(name, fn) { super(); this.name.set(name); fn?.(this); } analyze(ctx) { super.analyze(ctx); ctx.analyze(this.name); ctx.analyze(this._type); } /** Sets the field type. */ type(type) { this._type = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); return this; } toAst() { const node = ts.factory.createPropertyDeclaration([...this.$decorators(), ...this.modifiers], this.$node(this.name), this._optional ? this.$node(new TokenTsDsl().optional()) : void 0, this.$type(this._type), this.$value()); return this.$docs(node); } }; //#endregion //#region src/ts-dsl/stmt/stmt.ts const Mixed$48 = TsDsl; var StmtTsDsl = class extends Mixed$48 { "~dsl" = "StmtTsDsl"; _inner; constructor(inner) { super(); this._inner = inner; } analyze(ctx) { super.analyze(ctx); ctx.analyze(this._inner); } toAst() { const node = this.$node(this._inner); return ts.isStatement(node) ? node : ts.factory.createExpressionStatement(node); } }; //#endregion //#region src/ts-dsl/mixins/do.ts /** * Adds `.do()` for appending statements or expressions to a body. */ function DoMixin(Base) { class Do extends Base { _do = []; analyze(ctx) { super.analyze(ctx); ctx.pushScope(); try { for (const item of this._do) ctx.analyze(item); } finally { ctx.popScope(); } } do(...items) { this._do.push(...items); return this; } $do() { return this.$node(this._do.map((item) => new StmtTsDsl(item))); } } return Do; } //#endregion //#region src/ts-dsl/decl/pattern.ts const Mixed$47 = TsDsl; /** * Builds binding patterns (e.g., `{ foo, bar }`, `[a, b, ...rest]`). */ var PatternTsDsl = class extends Mixed$47 { "~dsl" = "PatternTsDsl"; pattern; _spread; analyze(ctx) { super.analyze(ctx); } /** Returns true when all required builder calls are present. */ get isValid() { return !this.missingRequiredCalls().length; } /** Defines an array pattern (e.g., `[a, b, c]`). */ array(...props) { this.pattern = { kind: "array", values: props[0] instanceof Array ? [...props[0]] : props }; return this; } /** Defines an object pattern (e.g., `{ a, b: alias }`). */ object(...props) { const entries = {}; for (const p of props) if (typeof p === "string") entries[p] = p; else if (p instanceof Array) for (const n of p) entries[n] = n; else Object.assign(entries, p); this.pattern = { kind: "object", values: entries }; return this; } /** Adds a spread element (e.g., `...rest`, `...options`, `...args`). */ spread(name) { this._spread = name; return this; } toAst() { this.$validate(); if (this.pattern.kind === "object") { const elements = Object.entries(this.pattern.values).map(([key, alias]) => key === alias ? ts.factory.createBindingElement(void 0, void 0, key, void 0) : ts.factory.createBindingElement(void 0, key, alias, void 0)); const spread = this.createSpread(); if (spread) elements.push(spread); return ts.factory.createObjectBindingPattern(elements); } if (this.pattern.kind === "array") { const elements = this.pattern.values.map((p) => ts.factory.createBindingElement(void 0, void 0, p, void 0)); const spread = this.createSpread(); if (spread) elements.push(spread); return ts.factory.createArrayBindingPattern(elements); } throw new Error("PatternTsDsl requires object() or array() pattern"); } $validate() { const missing = this.missingRequiredCalls(); if (!missing.length) return; throw new Error(`Binding pattern missing ${missing.join(" and ")}`); } missingRequiredCalls() { const missing = []; if (!this.pattern) missing.push(".array() or .object()"); return missing; } createSpread() { return this._spread ? ts.factory.createBindingElement(this.$node(new TokenTsDsl().spread()), void 0, this.$node(new IdTsDsl(this._spread))) : void 0; } }; //#endregion //#region src/ts-dsl/mixins/pattern.ts /** * Mixin providing `.array()`, `.object()`, and `.spread()` methods for defining destructuring patterns. */ function PatternMixin(Base) { class Pattern extends Base { pattern; analyze(ctx) { super.analyze(ctx); ctx.analyze(this.pattern); } array(...props) { (this.pattern ??= new PatternTsDsl()).array(...props); return this; } object(...props) { (this.pattern ??= new PatternTsDsl()).object(...props); return this; } /** Adds a spread element (e.g., `...args`, `...options`) to the pattern. */ spread(name) { (this.pattern ??= new PatternTsDsl()).spread(name); return this; } /** Renders the pattern into a `BindingName`. */ $pattern() { if (!this.pattern) return; return this.$node(this.pattern); } } return Pattern;