@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
JavaScript
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;