diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index 2320c36875..f0d31addb5 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -39,10 +39,11 @@ export default defineConfig(() => { '3.1.x', // 'circular.yaml', // 'dutchie.json', + // 'enum-names-values.yaml', // 'invalid', - 'full.yaml', + // 'full.yaml', // 'object-property-names.yaml', - // 'openai.yaml', + 'openai.yaml', // 'opencode.yaml', // 'pagination-ref.yaml', // 'sdk-instance.yaml', @@ -82,7 +83,7 @@ export default defineConfig(() => { // 'https://somefakedomain.com/openapi.yaml', ], logs: { - // level: 'debug', + // level: 'silent', path: './logs', }, // name: 'foo', @@ -217,7 +218,7 @@ export default defineConfig(() => { { // baseUrl: false, // exportFromIndex: true, - name: '@hey-api/client-angular', + // name: '@hey-api/client-angular', // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), // runtimeConfigPath: './src/hey-api.ts', // strictBaseUrl: true, @@ -236,6 +237,7 @@ export default defineConfig(() => { // error: '他們_error_{{name}}', // name: '你們_errors_{{name}}', // }, + // exportFromIndex: false, name: '@hey-api/typescript', // requests: '我們_data_{{name}}', // responses: { @@ -265,13 +267,13 @@ export default defineConfig(() => { name: '@hey-api/sdk', // operationId: false, // paramsStructure: 'flat', - responseStyle: 'data', + // responseStyle: 'data', // signature: 'auto', // signature: 'client', // signature: 'object', // transformer: '@hey-api/transformers', // transformer: true, - validator: 'valibot', + validator: 'zod', // validator: { // request: 'zod', // response: 'zod', @@ -293,21 +295,22 @@ export default defineConfig(() => { { // bigInt: true, dates: true, - name: '@hey-api/transformers', + // name: '@hey-api/transformers', }, { - name: 'fastify', + // name: 'fastify', }, { - name: 'swr', + // name: 'swr', }, { // case: 'SCREAMING_SNAKE_CASE', // comments: false, exportFromIndex: true, - // infiniteQueryKeys: { - // name: '{{name}}IQK', - // }, + infiniteQueryKeys: { + // name: '{{name}}IQK', + name: 'options', + }, infiniteQueryOptions: { meta() { return { @@ -315,6 +318,7 @@ export default defineConfig(() => { }; }, // name: '{{name}}IQO', + name: 'options', }, mutationOptions: { meta() { @@ -323,10 +327,12 @@ export default defineConfig(() => { }; }, // name: '{{name}}MO', + name: 'options', }, - name: '@tanstack/react-query', + // name: '@tanstack/react-query', queryKeys: { // name: '{{name}}QK', + name: 'options', tags: true, }, // queryOptions: false, @@ -336,7 +342,8 @@ export default defineConfig(() => { // custom: 'value', // } // }, - name: '{{name}}QO', + // name: '{{name}}QO', + name: 'options', }, useQuery: true, '~hooks': { @@ -357,7 +364,7 @@ export default defineConfig(() => { }, }, { - name: 'arktype', + // name: 'arktype', types: { infer: true, }, @@ -365,7 +372,7 @@ export default defineConfig(() => { { // case: 'SCREAMING_SNAKE_CASE', // comments: false, - // definitions: 'z{{name}}Definition', + definitions: 'z{{name}}', exportFromIndex: true, // metadata: true, name: 'valibot', @@ -373,10 +380,10 @@ export default defineConfig(() => { // case: 'PascalCase', // name: '{{name}}Data', // }, - // responses: { - // // case: 'snake_case', - // name: 'z{{name}}TestResponse', - // }, + responses: { + // case: 'snake_case', + name: 'z{{name}}TestResponse', + }, // webhooks: { // name: 'q{{name}}CoolWebhook', // }, @@ -419,10 +426,7 @@ export default defineConfig(() => { validator({ $, schema, v }) { return [ $.const('parsed').assign( - $(v.placeholder) - .attr('safeParseAsync') - .call(schema.placeholder, 'data') - .await(), + $(v).attr('safeParseAsync').call(schema, 'data').await(), ), $('parsed').return(), ]; @@ -443,7 +447,7 @@ export default defineConfig(() => { // infer: 'D{{name}}ZodType', // }, }, - // exportFromIndex: true, + exportFromIndex: true, metadata: true, name: 'zod', // requests: { @@ -499,10 +503,7 @@ export default defineConfig(() => { validator({ $, schema }) { return [ $.const('parsed').assign( - $(schema.placeholder) - .attr('safeParseAsync') - .call('data') - .await(), + $(schema).attr('safeParseAsync').call('data').await(), ), $('parsed').return(), ]; @@ -522,7 +523,7 @@ export default defineConfig(() => { httpResources: { asClass: true, }, - name: '@angular/common', + // name: '@angular/common', }, { exportFromIndex: true, diff --git a/docs/index.md b/docs/index.md index 9513b13c11..e5d4b0e1aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,8 +15,8 @@ hero: text: Roadmap theme: alt image: - alt: Two people looking at the blueprint - src: /images/blueprint-640w.png + alt: Two people looking at the TypeScript logo + src: /images/hero-920w.png features: - icon: diff --git a/docs/package.json b/docs/package.json index d6b803f503..34794fcd7c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "@docs/openapi-ts", "version": "0.10.4", - "description": "Documentation for OpenaAPI TypeScript.", + "description": "Documentation for OpenAPI TypeScript.", "private": true, "type": "module", "scripts": { diff --git a/docs/public/hero.png b/docs/public/hero.png new file mode 100644 index 0000000000..84a2da5705 Binary files /dev/null and b/docs/public/hero.png differ diff --git a/docs/public/images/hero-300w.png b/docs/public/images/hero-300w.png new file mode 100644 index 0000000000..4a640e3d93 Binary files /dev/null and b/docs/public/images/hero-300w.png differ diff --git a/docs/public/images/hero-640w.png b/docs/public/images/hero-640w.png new file mode 100644 index 0000000000..846bb84f2e Binary files /dev/null and b/docs/public/images/hero-640w.png differ diff --git a/docs/public/images/hero-920w.png b/docs/public/images/hero-920w.png new file mode 100644 index 0000000000..acba3b4f2f Binary files /dev/null and b/docs/public/images/hero-920w.png differ diff --git a/docs/scripts/optimize-images.js b/docs/scripts/optimize-images.js index 2bc7898f9c..fc10d69efd 100644 --- a/docs/scripts/optimize-images.js +++ b/docs/scripts/optimize-images.js @@ -31,6 +31,23 @@ const images = [ ], source: 'bricks.png', }, + { + sizes: [ + { + formats: ['png'], + width: 300, + }, + { + formats: ['png'], + width: 640, + }, + { + formats: ['png'], + width: 920, + }, + ], + source: 'hero.png', + }, { sizes: [ { diff --git a/examples/openapi-ts-openai/src/client/types.gen.ts b/examples/openapi-ts-openai/src/client/types.gen.ts index ac7532f675..449f08aca6 100644 --- a/examples/openapi-ts-openai/src/client/types.gen.ts +++ b/examples/openapi-ts-openai/src/client/types.gen.ts @@ -259,8 +259,8 @@ export const AssistantSupportedModels = { GPT_3_5_TURBO_16K_0613: 'gpt-3.5-turbo-16k-0613', } as const; -export type AssistantSupportedModels = - (typeof AssistantSupportedModels)[keyof typeof AssistantSupportedModels]; +export type AssistantSupportedModels2 = + (typeof AssistantSupportedModels2)[keyof typeof AssistantSupportedModels2]; /** * Code interpreter tool @@ -378,8 +378,8 @@ export const AudioResponseFormat = { * The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, or `vtt`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`, the only supported format is `json`. * */ -export type AudioResponseFormat = - (typeof AudioResponseFormat)[keyof typeof AudioResponseFormat]; +export type AudioResponseFormat2 = + (typeof AudioResponseFormat2)[keyof typeof AudioResponseFormat2]; /** * A log of a user action or configuration change within this organization. @@ -389,7 +389,7 @@ export type AuditLog = { * The ID of this log. */ id: string; - type: AuditLogEventType; + type: AuditLogEventType2; /** * The Unix timestamp (in seconds) of the event. */ @@ -949,8 +949,8 @@ export const AuditLogEventType = { /** * The event type. */ -export type AuditLogEventType = - (typeof AuditLogEventType)[keyof typeof AuditLogEventType]; +export type AuditLogEventType2 = + (typeof AuditLogEventType2)[keyof typeof AuditLogEventType2]; /** * Auto Chunking Strategy @@ -1911,8 +1911,8 @@ export const ChatCompletionRole = { /** * The role of the author of a message */ -export type ChatCompletionRole = - (typeof ChatCompletionRole)[keyof typeof ChatCompletionRole]; +export type ChatCompletionRole2 = + (typeof ChatCompletionRole2)[keyof typeof ChatCompletionRole2]; /** * Options for streaming response. Only set this when you set `stream: true`. @@ -2698,7 +2698,7 @@ export type CreateAssistantRequest = { * ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](https://platform.openai.com/docs/models) for descriptions of them. * */ - model: string | AssistantSupportedModels; + model: string | AssistantSupportedModels2; /** * The name of the assistant. The maximum length is 256 characters. * @@ -2714,7 +2714,7 @@ export type CreateAssistantRequest = { * */ instructions?: string; - reasoning_effort?: ReasoningEffort; + reasoning_effort?: ReasoningEffort2; /** * A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`. * @@ -2815,8 +2815,8 @@ export type CreateChatCompletionRequest = CreateModelResponseProperties & { */ model: ModelIdsShared; modalities?: ResponseModalities; - verbosity?: Verbosity; - reasoning_effort?: ReasoningEffort; + verbosity?: Verbosity2; + reasoning_effort?: ReasoningEffort2; /** * An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). * @@ -2856,7 +2856,7 @@ export type CreateChatCompletionRequest = CreateModelResponseProperties & { type: 'approximate'; approximate: WebSearchLocation; }; - search_context_size?: WebSearchContextSize; + search_context_size?: WebSearchContextSize2; }; /** * An integer between 0 and 20 specifying the number of most likely tokens to @@ -3077,7 +3077,7 @@ export type CreateChatCompletionResponse = { * The model used for the chat completion. */ model: string; - service_tier?: ServiceTier; + service_tier?: ServiceTier2; /** * This fingerprint represents the backend configuration that the model runs with. * @@ -3151,7 +3151,7 @@ export type CreateChatCompletionStreamResponse = { * The model to generate the completion. */ model: string; - service_tier?: ServiceTier; + service_tier?: ServiceTier2; /** * This fingerprint represents the backend configuration that the model runs with. * Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. @@ -3859,7 +3859,7 @@ export type CreateFileRequest = { * */ file: Blob | File; - purpose: FilePurpose; + purpose: FilePurpose2; expires_after?: FileExpirationAfter; }; @@ -4065,7 +4065,7 @@ export type CreateImageEditRequest = { * */ user?: string; - input_fidelity?: ImageInputFidelity; + input_fidelity?: ImageInputFidelity2; /** * Edit the image in streaming mode. Defaults to `false`. See the * [Image generation guide](https://platform.openai.com/docs/guides/image-generation) for more information. @@ -4498,7 +4498,7 @@ export type CreateResponse = CreateModelResponseProperties & * enrolled in the zero data retention program). * */ - include?: Array; + include?: Array; /** * Whether to allow the model to run tool calls in parallel. * @@ -4538,8 +4538,8 @@ export type CreateRunRequest = { /** * The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used. */ - model?: string | AssistantSupportedModels; - reasoning_effort?: ReasoningEffort; + model?: string | AssistantSupportedModels2; + reasoning_effort?: ReasoningEffort2; /** * Overrides the [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) of the assistant. This is useful for modifying the behavior on a per-run basis. */ @@ -4836,7 +4836,7 @@ export type CreateTranscriptionRequest = { * */ prompt?: string; - response_format?: AudioResponseFormat; + response_format?: AudioResponseFormat2; /** * The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. * @@ -4866,7 +4866,7 @@ export type CreateTranscriptionRequest = { * the models `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`. * */ - include?: Array; + include?: Array; }; /** @@ -5746,7 +5746,7 @@ export type EvalResponsesSource = { /** * Optional reasoning effort parameter. This is a query parameter used to select responses. */ - reasoning_effort?: ReasoningEffort; + reasoning_effort?: ReasoningEffort2; /** * Sampling temperature. This is a query parameter used to select responses. */ @@ -6181,8 +6181,8 @@ export const FileSearchRanker = { /** * The ranker to use for the file search. If not specified will use the `auto` ranker. */ -export type FileSearchRanker = - (typeof FileSearchRanker)[keyof typeof FileSearchRanker]; +export type FileSearchRanker2 = + (typeof FileSearchRanker2)[keyof typeof FileSearchRanker2]; /** * File search tool call ranking options @@ -6193,7 +6193,7 @@ export type FileSearchRanker = * */ export type FileSearchRankingOptions = { - ranker?: FileSearchRanker; + ranker?: FileSearchRanker2; /** * The score threshold for the file search. All values must be a floating point number between 0 and 1. */ @@ -7313,7 +7313,7 @@ export type ImageGenTool = { * */ background?: 'transparent' | 'opaque' | 'auto'; - input_fidelity?: ImageInputFidelity; + input_fidelity?: ImageInputFidelity2; /** * Optional mask for inpainting. Contains `image_url` * (string, optional) and `file_id` (string, optional). @@ -7381,8 +7381,8 @@ export const ImageInputFidelity = { HIGH: 'high', LOW: 'low' } as const; * for `gpt-image-1`. Supports `high` and `low`. Defaults to `low`. * */ -export type ImageInputFidelity = - (typeof ImageInputFidelity)[keyof typeof ImageInputFidelity]; +export type ImageInputFidelity2 = + (typeof ImageInputFidelity2)[keyof typeof ImageInputFidelity2]; /** * Image generation response @@ -7494,7 +7494,7 @@ export const Includable = { * enrolled in the zero data retention program). * */ -export type Includable = (typeof Includable)[keyof typeof Includable]; +export type Includable2 = (typeof Includable2)[keyof typeof Includable2]; /** * Audio input @@ -8846,7 +8846,7 @@ export type ModelIdsResponses = | 'computer-use-preview' | 'computer-use-preview-2025-03-11'; -export type ModelIdsShared = string | ChatModel; +export type ModelIdsShared = string | ChatModel2; export type ModelResponseProperties = { metadata?: Metadata; @@ -8892,7 +8892,7 @@ export type ModelResponseProperties = { * */ prompt_cache_key?: string; - service_tier?: ServiceTier; + service_tier?: ServiceTier2; }; export type ModifyAssistantRequest = { @@ -8900,8 +8900,8 @@ export type ModifyAssistantRequest = { * ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](https://platform.openai.com/docs/models) for descriptions of them. * */ - model?: string | AssistantSupportedModels; - reasoning_effort?: ReasoningEffort; + model?: string | AssistantSupportedModels2; + reasoning_effort?: ReasoningEffort2; /** * The name of the assistant. The maximum length is 256 characters. * @@ -12424,7 +12424,7 @@ export type RealtimeTranscriptionSessionCreateResponse = { * */ export type Reasoning = { - effort?: ReasoningEffort; + effort?: ReasoningEffort2; /** * A summary of the reasoning performed by the model. This can be * useful for debugging and understanding the model's reasoning process. @@ -12468,8 +12468,8 @@ export const ReasoningEffort = { * on reasoning in a response. * */ -export type ReasoningEffort = - (typeof ReasoningEffort)[keyof typeof ReasoningEffort]; +export type ReasoningEffort2 = + (typeof ReasoningEffort2)[keyof typeof ReasoningEffort2]; /** * Reasoning @@ -12980,7 +12980,7 @@ export type ResponseCustomToolCallInputDoneEvent = { * */ export type ResponseError = { - code: ResponseErrorCode; + code: ResponseErrorCode2; /** * A human-readable description of the error. * @@ -13017,8 +13017,8 @@ export const ResponseErrorCode = { * The error code for the response. * */ -export type ResponseErrorCode = - (typeof ResponseErrorCode)[keyof typeof ResponseErrorCode]; +export type ResponseErrorCode2 = + (typeof ResponseErrorCode2)[keyof typeof ResponseErrorCode2]; /** * Emitted when an error occurs. @@ -13896,7 +13896,7 @@ export type ResponseProperties = { */ text?: { format?: TextResponseFormatConfiguration; - verbosity?: Verbosity; + verbosity?: Verbosity2; }; /** * An array of tools the model may call while generating a response. You @@ -13923,7 +13923,7 @@ export type ResponseProperties = { * */ tool_choice?: - | ToolChoiceOptions + | ToolChoiceOptions2 | ToolChoiceAllowed | ToolChoiceTypes | ToolChoiceFunction @@ -14765,7 +14765,7 @@ export type RunObject = { * The ID of the [assistant](https://platform.openai.com/docs/api-reference/assistants) used for execution of this run. */ assistant_id: string; - status: RunStatus; + status: RunStatus2; /** * Details on the action required to continue the run. Will be `null` if no action is required. */ @@ -15190,7 +15190,7 @@ export type RunStepDetailsToolCallsFileSearchObject = { * The ranking options for the file search. */ export type RunStepDetailsToolCallsFileSearchRankingOptionsObject = { - ranker: FileSearchRanker; + ranker: FileSearchRanker2; /** * The score threshold for the file search. All values must be a floating point number between 0 and 1. */ @@ -15537,7 +15537,7 @@ export const ServiceTier = { * When the `service_tier` parameter is set, the response body will include the `service_tier` value based on the processing mode actually used to serve the request. This response value may be different from the value set in the parameter. * */ -export type ServiceTier = (typeof ServiceTier)[keyof typeof ServiceTier]; +export type ServiceTier2 = (typeof ServiceTier2)[keyof typeof ServiceTier2]; /** * Emitted for each chunk of audio data generated during speech synthesis. @@ -15933,8 +15933,8 @@ export const ToolChoiceOptions = { * `required` means the model must call one or more tools. * */ -export type ToolChoiceOptions = - (typeof ToolChoiceOptions)[keyof typeof ToolChoiceOptions]; +export type ToolChoiceOptions2 = + (typeof ToolChoiceOptions2)[keyof typeof ToolChoiceOptions2]; /** * Hosted tool @@ -16100,8 +16100,8 @@ export type TranscriptionChunkingStrategy = 'auto' | VadConfig; export const TranscriptionInclude = { LOGPROBS: 'logprobs' } as const; -export type TranscriptionInclude = - (typeof TranscriptionInclude)[keyof typeof TranscriptionInclude]; +export type TranscriptionInclude2 = + (typeof TranscriptionInclude2)[keyof typeof TranscriptionInclude2]; export type TranscriptionSegment = { /** @@ -17000,7 +17000,7 @@ export const Verbosity = { * Currently supported values are `low`, `medium`, and `high`. * */ -export type Verbosity = (typeof Verbosity)[keyof typeof Verbosity]; +export type Verbosity2 = (typeof Verbosity2)[keyof typeof Verbosity2]; export type VoiceIdsShared = | string @@ -17106,8 +17106,8 @@ export const WebSearchContextSize = { * search. One of `low`, `medium`, or `high`. `medium` is the default. * */ -export type WebSearchContextSize = - (typeof WebSearchContextSize)[keyof typeof WebSearchContextSize]; +export type WebSearchContextSize2 = + (typeof WebSearchContextSize2)[keyof typeof WebSearchContextSize2]; /** * Web search location @@ -18269,7 +18269,7 @@ export const FilePurpose = { * The intended purpose of the uploaded file. One of: - `assistants`: Used in the Assistants API - `batch`: Used in the Batch API - `fine-tune`: Used for fine-tuning - `vision`: Images used for vision fine-tuning - `user_data`: Flexible file type for any purpose - `evals`: Used for eval data sets * */ -export type FilePurpose = (typeof FilePurpose)[keyof typeof FilePurpose]; +export type FilePurpose2 = (typeof FilePurpose2)[keyof typeof FilePurpose2]; export type BatchError = { /** @@ -18451,7 +18451,7 @@ export const ChatModel = { GPT_3_5_TURBO_16K_0613: 'gpt-3.5-turbo-16k-0613', } as const; -export type ChatModel = (typeof ChatModel)[keyof typeof ChatModel]; +export type ChatModel2 = (typeof ChatModel2)[keyof typeof ChatModel2]; export type CreateThreadAndRunRequestWithoutStream = { /** @@ -18567,8 +18567,8 @@ export type CreateRunRequestWithoutStream = { /** * The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used. */ - model?: string | AssistantSupportedModels; - reasoning_effort?: ReasoningEffort; + model?: string | AssistantSupportedModels2; + reasoning_effort?: ReasoningEffort2; /** * Overrides the [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) of the assistant. This is useful for modifying the behavior on a per-run basis. */ @@ -18648,7 +18648,7 @@ export const RunStatus = { /** * The status of the run, which can be either `queued`, `in_progress`, `requires_action`, `cancelling`, `cancelled`, `failed`, `completed`, `incomplete`, or `expired`. */ -export type RunStatus = (typeof RunStatus)[keyof typeof RunStatus]; +export type RunStatus2 = (typeof RunStatus2)[keyof typeof RunStatus2]; /** * The delta containing the fields that have changed on the run step. @@ -20419,7 +20419,7 @@ export type ListAuditLogsData = { /** * Return only events with a `type` in one of these values. For example, `project.created`. For all options, see the documentation for the [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object). */ - 'event_types[]'?: Array; + 'event_types[]'?: Array; /** * Return only events performed by these actors. Can be a user ID, a service account ID, or an api key tracking ID. */ @@ -22143,7 +22143,7 @@ export type GetResponseData = { * parameter for Response creation above for more information. * */ - include?: Array; + include?: Array; /** * If set to true, the model response data will be streamed to the client * as it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). @@ -22251,7 +22251,7 @@ export type ListInputItemsData = { * parameter for Response creation above for more information. * */ - include?: Array; + include?: Array; }; url: '/responses/{response_id}/input_items'; }; diff --git a/packages/codegen-core/package.json b/packages/codegen-core/package.json index 45ce169240..d579f19f87 100644 --- a/packages/codegen-core/package.json +++ b/packages/codegen-core/package.json @@ -60,6 +60,10 @@ "engines": { "node": ">=20.19.0" }, + "dependencies": { + "ansi-colors": "4.1.3", + "color-support": "1.1.3" + }, "peerDependencies": { "typescript": ">=5.5.3" }, diff --git a/packages/codegen-core/src/__tests__/bimap.test.ts b/packages/codegen-core/src/__tests__/bimap.test.ts index b28fe2665e..2a4b25d968 100644 --- a/packages/codegen-core/src/__tests__/bimap.test.ts +++ b/packages/codegen-core/src/__tests__/bimap.test.ts @@ -8,11 +8,11 @@ describe('BiMap', () => { // set and get expect(bimap.set(1, 'a')).toBe(bimap); expect(bimap.set(2, 'b')).toBe(bimap); - // get, getKey + // get, getKeys expect(bimap.get(1)).toBe('a'); expect(bimap.get(2)).toBe('b'); - expect(bimap.getKey('a')).toBe(1); - expect(bimap.getKey('b')).toBe(2); + expect(bimap.getKeys('a')).toEqual(new Set([1])); + expect(bimap.getKeys('b')).toEqual(new Set([2])); // hasKey, hasValue expect(bimap.hasKey(1)).toBe(true); expect(bimap.hasKey(2)).toBe(true); @@ -52,13 +52,14 @@ describe('BiMap', () => { // Overwrite value for existing key bimap.set(1, 'z'); expect(bimap.get(1)).toBe('z'); - expect(bimap.getKey('z')).toBe(1); + expect(bimap.getKeys('z')).toEqual(new Set([1])); // Overwrite key for existing value bimap.set(3, 'z'); - expect(bimap.getKey('z')).toBe(3); - expect(bimap.get(1)).toBeUndefined(); + expect(bimap.getKeys('z')).toEqual(new Set([1, 3])); + expect(bimap.get(1)).toBe('z'); // Iteration after overwrite expect(Array.from(bimap)).toEqual([ + [1, 'z'], [2, 'y'], [3, 'z'], ]); diff --git a/packages/codegen-core/src/__tests__/files.test.ts b/packages/codegen-core/src/__tests__/files.test.ts index ef376d1a10..981194c1e2 100644 --- a/packages/codegen-core/src/__tests__/files.test.ts +++ b/packages/codegen-core/src/__tests__/files.test.ts @@ -17,6 +17,7 @@ describe('FileRegistry', () => { extension: undefined, external: undefined, id: expect.any(Number), + localNames: new Set(), name: 'Foo', path: undefined, resolvedNames: expect.any(Object), @@ -58,6 +59,7 @@ describe('FileRegistry', () => { extension: undefined, external: undefined, id: expect.any(Number), + localNames: new Set(), name: 'Bar', path: '/bar', resolvedNames: expect.any(Object), diff --git a/packages/codegen-core/src/__tests__/symbols.test.ts b/packages/codegen-core/src/__tests__/symbols.test.ts index 6a1edc28dc..42aa30779d 100644 --- a/packages/codegen-core/src/__tests__/symbols.test.ts +++ b/packages/codegen-core/src/__tests__/symbols.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { SymbolRegistry } from '../symbols/registry'; +import { isSymbol } from '../symbols/symbol'; describe('SymbolRegistry', () => { it('covers the full public interface', () => { @@ -13,18 +14,25 @@ describe('SymbolRegistry', () => { // Register a symbol with meta const symbol1 = registry.register({ - meta: { - foo: 'bar', - }, - placeholder: 'Foo', - }); - expect(symbol1).toEqual({ - id: expect.any(Number), - meta: { - foo: 'bar', - }, + meta: { foo: 'bar' }, + name: '', placeholder: 'Foo', }); + expect(symbol1).toEqual( + expect.objectContaining({ + _dependencies: new Set(), + exportFrom: [], + exported: false, + id: expect.any(Number), + importKind: 'named', + kind: 'var', + meta: { + foo: 'bar', + }, + name: '', + placeholder: 'Foo', + }), + ); // get by id and meta expect(registry.get(symbol1.id)).toEqual(symbol1); @@ -35,30 +43,33 @@ describe('SymbolRegistry', () => { expect(registry.isRegistered({ foo: 'bar' })).toBe(true); // Registering again with same meta creates a new symbol - const symbol1b = registry.register({ meta: { foo: 'bar' } }); + const symbol1b = registry.register({ + meta: { foo: 'bar' }, + name: '', + }); expect(symbol1b).not.toEqual(symbol1); - // Registering with id overrides the symbol - const symbol1c = registry.register({ id: symbol1.id }); - expect(symbol1c).not.toEqual(symbol1); - expect(symbol1c.id).toBe(symbol1.id); - // Reference returns same symbol const ref1 = registry.reference({ foo: 'bar' }); - expect(ref1).toEqual(symbol1c); + expect(ref1).toEqual(symbol1); // Register a new symbol with different meta const symbol2 = registry.register({ exportFrom: ['x'], meta: { bar: 'baz' }, + name: '', placeholder: 'Bar', }); - expect(symbol2).toEqual({ - exportFrom: ['x'], - id: expect.any(Number), - meta: { bar: 'baz' }, - placeholder: 'Bar', - }); + expect(symbol2).toEqual( + expect.objectContaining({ + dependencies: new Set(), + exportFrom: ['x'], + id: expect.any(Number), + meta: { bar: 'baz' }, + name: '', + placeholder: 'Bar', + }), + ); // Registered symbols are yielded in order const registered = Array.from(registry.registered()); @@ -79,6 +90,7 @@ describe('SymbolRegistry', () => { expect(registry.isRegistered(symRef.id)).toBe(false); const symRegistered = registry.register({ meta: { qux: true }, + name: '', placeholder: 'Qux', }); expect(registry.isRegistered(symRegistered.id)).toBe(true); @@ -122,11 +134,11 @@ describe('SymbolRegistry', () => { const refAD = registry.reference({ a: 0, d: 0 }); const refAC = registry.reference({ a: 0, c: 0 }); - expect(symC).toEqual(refA); - expect(symC).toEqual(refAB); - expect(symC).toEqual(refAC); - expect(symC).not.toEqual(refAD); - expect(symC).not.toEqual(refB); + expect(refA.canonical).toEqual(symC); + expect(refAB.canonical).toEqual(symC); + expect(refAC.canonical).toEqual(symC); + expect(refAD.canonical).not.toEqual(symC); + expect(refB.canonical).not.toEqual(symC); expect(symC.meta).toEqual({ a: 0, b: 0, c: 0 }); }); @@ -249,4 +261,88 @@ describe('SymbolRegistry', () => { const newQuery = registry.query({ something: 'else' }); expect(newQuery).toEqual([]); }); + + it('isSymbol covers various inputs', () => { + const registry = new SymbolRegistry(); + + // real registered symbol + const sym = registry.register({ meta: { a: 1 }, name: 'A' }); + expect(isSymbol(sym)).toBe(true); + + // stub reference (unregistered) + const stub = registry.reference({ b: 2 }); + expect(isSymbol(stub)).toBe(true); + + // primitives + expect(isSymbol(null)).toBe(false); + expect(isSymbol(undefined)).toBe(false); + expect(isSymbol(123)).toBe(false); + expect(isSymbol('foo')).toBe(false); + expect(isSymbol(true)).toBe(false); + + // arrays and plain objects + expect(isSymbol([])).toBe(false); + expect(isSymbol({})).toBe(false); + + // object with different tag + expect(isSymbol({ '~tag': 'not-a-symbol' })).toBe(false); + + // object masquerading as a symbol (matches tag) + expect(isSymbol({ '~tag': 'heyapi.symbol' })).toBe(true); + + // Date, Map, Set should be false + expect(isSymbol(new Date())).toBe(false); + expect(isSymbol(new Map())).toBe(false); + expect(isSymbol(new Set())).toBe(false); + + // Typed arrays and ArrayBuffer should be false + expect(isSymbol(new Uint8Array())).toBe(false); + expect(isSymbol(new ArrayBuffer(8))).toBe(false); + + // Functions without tag should be false + const fn = () => {}; + expect(isSymbol(fn)).toBe(false); + + // Class instance without tag should be false + class Foo {} + const foo = new Foo(); + expect(isSymbol(foo)).toBe(false); + + // Proxy with tag should be true if own property is present + const target = {} as Record; + const proxied = new Proxy(target, { + get(_, prop) { + if (prop === '~tag') return 'heyapi.symbol'; + return undefined; + }, + getOwnPropertyDescriptor(_, prop) { + if (prop === '~tag') + return { + configurable: true, + enumerable: true, + value: 'heyapi.symbol', + writable: false, + }; + return undefined; + }, + has(_, prop) { + return prop === '~tag'; + }, + }); + // Define as own property to satisfy hasOwn + Object.defineProperty(target, '~tag', { + configurable: true, + value: 'heyapi.symbol', + }); + expect(isSymbol(proxied)).toBe(true); + + // Inherited tag should be false (not own property) + const proto = { '~tag': 'heyapi.symbol' }; + const objWithProto = Object.create(proto); + expect(isSymbol(objWithProto)).toBe(false); + + // Primitive edge cases + expect(isSymbol(Symbol('x'))).toBe(false); + expect(isSymbol(0n)).toBe(false); + }); }); diff --git a/packages/codegen-core/src/bimap/bimap.ts b/packages/codegen-core/src/bimap/bimap.ts index 93f21afb96..d28794531d 100644 --- a/packages/codegen-core/src/bimap/bimap.ts +++ b/packages/codegen-core/src/bimap/bimap.ts @@ -2,7 +2,7 @@ import type { IBiMap } from './types'; export class BiMap implements IBiMap { private map = new Map(); - private reverse = new Map(); + private reverse = new Map>(); delete(key: Key): boolean { const value = this.map.get(key); @@ -13,9 +13,11 @@ export class BiMap implements IBiMap { } deleteValue(value: Value): boolean { - const key = this.reverse.get(value); - if (key !== undefined) { - this.map.delete(key); + const keys = this.reverse.get(value); + if (keys) { + for (const key of keys) { + this.map.delete(key); + } } return this.reverse.delete(value); } @@ -28,7 +30,7 @@ export class BiMap implements IBiMap { return this.map.get(key); } - getKey(value: Value): Key | undefined { + getKeys(value: Value): Set | undefined { return this.reverse.get(value); } @@ -47,14 +49,18 @@ export class BiMap implements IBiMap { set(key: Key, value: Value): this { const oldValue = this.map.get(key); if (oldValue !== undefined && oldValue !== value) { - this.reverse.delete(oldValue); - } - const oldKey = this.reverse.get(value); - if (oldKey !== undefined && oldKey !== key) { - this.map.delete(oldKey); + const oldKeys = this.reverse.get(oldValue); + if (oldKeys) { + oldKeys.delete(key); + if (oldKeys.size === 0) { + this.reverse.delete(oldValue); + } + } } this.map.set(key, value); - this.reverse.set(value, key); + const keys = this.reverse.get(value) ?? new Set(); + keys.add(key); + this.reverse.set(value, keys); return this; } diff --git a/packages/codegen-core/src/bimap/types.d.ts b/packages/codegen-core/src/bimap/types.d.ts index c7b81aea3c..529d71699c 100644 --- a/packages/codegen-core/src/bimap/types.d.ts +++ b/packages/codegen-core/src/bimap/types.d.ts @@ -30,11 +30,11 @@ export interface IBiMap { */ get(key: Key): Value | undefined; /** - * Gets the key associated with a value. + * Gets the keys associated with a value. * * @param value The value to look up. */ - getKey(value: Value): Key | undefined; + getKeys(value: Value): Set | undefined; /** * Checks if a key exists in the map. * diff --git a/packages/codegen-core/src/bindings/plan.d.ts b/packages/codegen-core/src/bindings/plan.d.ts new file mode 100644 index 0000000000..7224f38660 --- /dev/null +++ b/packages/codegen-core/src/bindings/plan.d.ts @@ -0,0 +1,76 @@ +import type { SymbolImportKind } from '../symbols/types'; + +export interface PlannedImport { + /** ID of the file where the symbol lives */ + from: number; + /** The final exported name of the symbol in its source file */ + importedName: string; + /** Whether this import is type-only. */ + isTypeOnly: boolean; + /** Import flavor. */ + kind: SymbolImportKind; + /** + * The name this symbol will have locally in this file. + * This is where aliasing is applied: + * + * import { Foo as Foo$2 } from "./x" + * + * localName === "Foo$2" + */ + localName: string; + /** ID of the symbol being imported */ + symbolId: number; +} + +export interface PlannedExport { + /** + * Whether the export was explicitly requested by the plugin/DSL + * (e.g. symbol.exported = true) vs implicitly required (e.g. re-export). + */ + explicit: boolean; + /** + * The name this symbol will be exported under from this file. + * + * This may differ from the symbol's finalName if aliasing is needed: + * + * export { Foo as Foo2 } + * + * exportedName === "Foo2" + */ + exportedName: string; + /** Whether this export is type-only. */ + isTypeOnly: boolean; + /** Export flavor. */ + kind: SymbolImportKind; + /** ID of the symbol being exported */ + symbolId: number; +} + +export interface PlannedReexport { + /** + * Name under which the symbol is exported in this file. + * + * export { Foo as Bar } from "./models" + * + * exportedName === "Bar" + */ + exportedName: string; + /** ID of the source file containing the symbol */ + from: number; + /** + * The name the symbol has in the source file’s exports. + * + * export { Foo as Bar } from "./models" + * + * importedName === "Foo" + * + * This handles aliasing in the source file's export list. + */ + importedName: string; + /** Whether this re-export is type-only. */ + isTypeOnly: boolean; + /** Export flavor. */ + kind: SymbolImportKind; + /** ID of the symbol being re-exported */ + symbolId: number; +} diff --git a/packages/codegen-core/src/bindings/utils.ts b/packages/codegen-core/src/bindings/utils.ts index 84cf003f4f..f8e9cf12d9 100644 --- a/packages/codegen-core/src/bindings/utils.ts +++ b/packages/codegen-core/src/bindings/utils.ts @@ -1,5 +1,5 @@ import type { IFileOut } from '../files/types'; -import type { ISymbolOut } from '../symbols/types'; +import type { Symbol } from '../symbols/symbol'; import type { IBinding } from './types'; export const createBinding = ({ @@ -10,7 +10,7 @@ export const createBinding = ({ }: { file: IFileOut; modulePath: string; - symbol: ISymbolOut; + symbol: Symbol; symbolFile: IFileOut; }): IBinding => { const names: Array = []; @@ -48,12 +48,12 @@ export const createBinding = ({ } } else if (symbol.name && fileResolvedName !== symbol.name) { name = symbol.name; - binding.aliases[name] = symbol.placeholder; + binding.aliases[name] = symbol.placeholder!; } } - names.push(name); + names.push(name!); if (symbol.kind === 'type') { - typeNames.push(name); + typeNames.push(name!); } } // cast type names to names to allow for cleaner API, diff --git a/packages/codegen-core/src/debug.ts b/packages/codegen-core/src/debug.ts new file mode 100644 index 0000000000..5e1cbcb569 --- /dev/null +++ b/packages/codegen-core/src/debug.ts @@ -0,0 +1,35 @@ +import colors from 'ansi-colors'; +// @ts-expect-error +import colorSupport from 'color-support'; + +colors.enabled = colorSupport().hasBasic; + +const DEBUG_GROUPS = { + analyzer: colors.greenBright, + dsl: colors.cyanBright, + registry: colors.blueBright, + symbol: colors.magentaBright, +} as const; + +export function debug(message: string, group: keyof typeof DEBUG_GROUPS) { + const value = process.env.DEBUG; + if (!value) return; + + const groups = value.split(',').map((x) => x.trim().toLowerCase()); + + if ( + !( + groups.includes('*') || + groups.includes('heyapi:*') || + groups.includes(`heyapi:${group}`) || + groups.includes(group) + ) + ) { + return; + } + + const color = DEBUG_GROUPS[group] ?? colors.whiteBright; + const prefix = color(`heyapi:${group}`); + + console.debug(`${prefix} ${message}`); +} diff --git a/packages/codegen-core/src/extensions/types.d.ts b/packages/codegen-core/src/extensions.d.ts similarity index 100% rename from packages/codegen-core/src/extensions/types.d.ts rename to packages/codegen-core/src/extensions.d.ts diff --git a/packages/codegen-core/src/files/registry.ts b/packages/codegen-core/src/files/registry.ts index cd89e62dd9..3391a875bb 100644 --- a/packages/codegen-core/src/files/registry.ts +++ b/packages/codegen-core/src/files/registry.ts @@ -99,13 +99,13 @@ export class FileRegistry implements IFileRegistry { result = { ...result, ...file, // clone to avoid mutation + exports: result?.exports ?? [], id, + imports: result?.imports ?? [], + reexports: result?.reexports ?? [], + reservedNames: result?.reservedNames ?? new Map(), resolvedNames: result?.resolvedNames ?? new BiMap(), - symbols: result?.symbols ?? { - body: [], - exports: [], - imports: [], - }, + symbols: result?.symbols ?? [], }; this.values.set(id, result); diff --git a/packages/codegen-core/src/files/rules.d.ts b/packages/codegen-core/src/files/rules.d.ts new file mode 100644 index 0000000000..07f6c1fabc --- /dev/null +++ b/packages/codegen-core/src/files/rules.d.ts @@ -0,0 +1,14 @@ +export interface Rules { + /** Whether two exported names may collide. */ + allowExportNameShadowing: boolean; + /** Whether a local symbol can shadow another local name without error. */ + allowLocalNameShadowing: boolean; + /** Whether the language requires file-scoped name uniqueness. */ + fileScopedNamesMustBeUnique: boolean; + /** Whether `import { X } from "mod"` introduces a local binding `X`. */ + importCreatesLocalBinding: boolean; + /** Whether `export { X } from "mod"` introduces a local binding `X`. */ + reexportCreatesLocalBinding: boolean; + /** Whether the language distinguishes type-only imports. */ + supportsTypeImports: boolean; +} diff --git a/packages/codegen-core/src/files/types.d.ts b/packages/codegen-core/src/files/types.d.ts index 892fbe2eae..7bf21a16b2 100644 --- a/packages/codegen-core/src/files/types.d.ts +++ b/packages/codegen-core/src/files/types.d.ts @@ -1,4 +1,12 @@ import type { IBiMap } from '../bimap/types'; +import type { + PlannedExport, + PlannedImport, + PlannedReexport, +} from '../bindings/plan'; +import type { Symbol } from '../symbols/symbol'; +import type { SymbolKind } from '../symbols/types'; +import type { Rules } from './rules'; /** * Selector array used to reference files. @@ -10,9 +18,7 @@ export type IFileSelector = ReadonlyArray; export type IFileIdentifier = number | IFileSelector; export type IFileIn = { - /** - * File extension, if any. - */ + /** File extension, if any. */ extension?: string; /** * Indicates whether the file is external, meaning it is not generated @@ -22,9 +28,7 @@ export type IFileIn = { * @example true */ external?: boolean; - /** - * Unique file ID. If one is not provided, it will be auto-generated. - */ + /** Unique file ID. If one is not provided, it will be auto-generated. */ readonly id?: number; /** * The desired name for the file within the project. If there are multiple files @@ -48,31 +52,22 @@ export type IFileIn = { }; export type IFileOut = IFileIn & { - /** - * Unique file ID. - */ + /** Named exports originating from this file */ + readonly exports: Array; + /** Unique file ID. */ readonly id: number; - /** - * Map holding resolved names for symbols in this file. - */ + /** Fully resolved imports this file needs */ + readonly imports: Array; + /** Re-exports (“export { X } from …”) */ + readonly reexports: Array; + /** Top-level names in this file that cannot be reused (imports, exports, declarations, symbol names). */ + readonly reservedNames: Map>; + /** Map holding resolved names for symbols in this file. */ readonly resolvedNames: IBiMap; - /** - * Symbols in this file, categorized by their role. - */ - readonly symbols: { - /** - * Symbols declared in the body of this file. - */ - body: Array; - /** - * Symbols re-exported from other files. - */ - exports: Array; - /** - * Symbols imported from other files. - */ - imports: Array; - }; + /** Rules that control naming, imports, reexports, and scope behavior for this file. */ + rules?: Rules; + /** Symbols defined inside this file */ + readonly symbols: Array; }; export interface IFileRegistry { diff --git a/packages/codegen-core/src/index.ts b/packages/codegen-core/src/index.ts index 22d8c0fd83..4723164dd3 100644 --- a/packages/codegen-core/src/index.ts +++ b/packages/codegen-core/src/index.ts @@ -1,22 +1,30 @@ export type { IBiMap as BiMap } from './bimap/types'; +export type { + PlannedExport, + PlannedImport, + PlannedReexport, +} from './bindings/plan'; export type { IBinding as Binding } from './bindings/types'; export { createBinding, mergeBindings } from './bindings/utils'; +export { debug } from './debug'; export type { IProjectRenderMeta as ProjectRenderMeta, ISymbolMeta as SymbolMeta, -} from './extensions/types'; +} from './extensions'; export type { IFileOut as File, IFileIdentifier as FileIdentifier, IFileIn as FileIn, } from './files/types'; -export type { IOutput as Output } from './output/types'; +export type { INode as Node } from './nodes/node'; +export type { IOutput as Output } from './output'; export { Project } from './project/project'; export type { IProject } from './project/types'; export type { IRenderer as Renderer } from './renderer/types'; export { renderIds } from './renderer/utils'; +export { AnalysisContext, Analyzer } from './symbols/analyzer'; +export { isSymbol, Symbol, symbolBrand } from './symbols/symbol'; export type { - ISymbolOut as Symbol, ISymbolIdentifier as SymbolIdentifier, ISymbolIn as SymbolIn, } from './symbols/types'; diff --git a/packages/codegen-core/src/nodes/node.d.ts b/packages/codegen-core/src/nodes/node.d.ts new file mode 100644 index 0000000000..8c62d9999c --- /dev/null +++ b/packages/codegen-core/src/nodes/node.d.ts @@ -0,0 +1,13 @@ +import type { IAnalysisContext } from '../symbols/analyzer'; +import type { Symbol } from '../symbols/symbol'; + +export interface INode { + /** Perform semantic analysis. */ + analyze(ctx: IAnalysisContext): void; + /** Parent node in the constructed syntax tree. */ + parent?: INode; + /** The symbol associated with this node, if it defines a top‑level symbol. */ + symbol?: Symbol; + /** Brand used for renderer dispatch. */ + readonly '~brand': symbol; +} diff --git a/packages/codegen-core/src/nodes/registry.ts b/packages/codegen-core/src/nodes/registry.ts new file mode 100644 index 0000000000..c99eab5984 --- /dev/null +++ b/packages/codegen-core/src/nodes/registry.ts @@ -0,0 +1,26 @@ +import type { INode } from './node'; +import type { INodeRegistry } from './types'; + +export class NodeRegistry implements INodeRegistry { + private brands: Map> = new Map(); + private list: Array = []; + + add(node: INode): void { + this.list.push(node); + + let group = this.brands.get(node['~brand']); + if (!group) { + group = []; + this.brands.set(node['~brand'], group); + } + group.push(node); + } + + all(): ReadonlyArray { + return this.list; + } + + byBrand(brand: symbol): ReadonlyArray { + return this.brands.get(brand) ?? []; + } +} diff --git a/packages/codegen-core/src/nodes/types.d.ts b/packages/codegen-core/src/nodes/types.d.ts new file mode 100644 index 0000000000..a21416b76a --- /dev/null +++ b/packages/codegen-core/src/nodes/types.d.ts @@ -0,0 +1,16 @@ +import type { INode } from './node'; + +export interface INodeRegistry { + /** + * Register a syntax node. + */ + add(node: INode): void; + /** + * All nodes in insertion order. + */ + all(): ReadonlyArray; + /** + * Nodes by backend brand, so planner doesn't need to filter repeatedly. + */ + byBrand(brand: symbol): ReadonlyArray; +} diff --git a/packages/codegen-core/src/output/types.d.ts b/packages/codegen-core/src/output.d.ts similarity index 100% rename from packages/codegen-core/src/output/types.d.ts rename to packages/codegen-core/src/output.d.ts diff --git a/packages/codegen-core/src/project/namespace.ts b/packages/codegen-core/src/project/namespace.ts new file mode 100644 index 0000000000..d6b1aaa1d0 --- /dev/null +++ b/packages/codegen-core/src/project/namespace.ts @@ -0,0 +1,77 @@ +import type { SymbolKind } from '../symbols/types'; + +/** + * Returns true if two declarations of given kinds + * are allowed to share the same identifier in TypeScript. + */ +export function canShareName(a: SymbolKind, b: SymbolKind): boolean { + // same-kind always valid for interfaces (merging) + if (a === 'interface' && b === 'interface') return true; + + // type vs interface merges + if ( + (a === 'interface' && b === 'type') || + (a === 'type' && b === 'interface') + ) { + return false; // TypeScript does NOT merge type-alias with interface. + } + + // type vs type = conflict + if (a === 'type' && b === 'type') return false; + + // interface vs class = allowed (declare-merge) + if ( + (a === 'interface' && b === 'class') || + (a === 'class' && b === 'interface') + ) { + return true; + } + + // enum vs namespace = allowed (merges into value+type) + if ( + (a === 'enum' && b === 'namespace') || + (a === 'namespace' && b === 'enum') + ) { + return true; + } + + // class vs namespace = allowed + if ( + (a === 'class' && b === 'namespace') || + (a === 'namespace' && b === 'class') + ) { + return true; + } + + // namespace vs namespace = allowed (merging) + if (a === 'namespace' && b === 'namespace') return true; + + // enum vs enum = conflict IF values conflict (TypeScript flags duplicates) + if (a === 'enum' && b === 'enum') return false; + + // function and namespace merge (namespace can augment function) + if ( + (a === 'function' && b === 'namespace') || + (a === 'namespace' && b === 'function') + ) { + return true; + } + + // these collide with each other in the value namespace + const valueKinds = new Set(['class', 'enum', 'function', 'var']); + + const aInValue = valueKinds.has(a); + const bInValue = valueKinds.has(b); + + if (aInValue && bInValue) return false; + + // type-only declarations do not collide with value-only declarations + const typeKinds = new Set(['interface', 'type']); + const aInType = typeKinds.has(a); + const bInType = typeKinds.has(b); + + // if one is type-only and the other is value-only, they do NOT collide + if (aInType !== bInType) return true; + + return true; +} diff --git a/packages/codegen-core/src/project/project.ts b/packages/codegen-core/src/project/project.ts index c479b54287..cbf1c67ea9 100644 --- a/packages/codegen-core/src/project/project.ts +++ b/packages/codegen-core/src/project/project.ts @@ -1,22 +1,26 @@ import path from 'node:path'; -import type { IProjectRenderMeta } from '../extensions/types'; +import type { IProjectRenderMeta } from '../extensions'; import { FileRegistry } from '../files/registry'; -import type { IFileOut, IFileSelector } from '../files/types'; -import type { IOutput } from '../output/types'; +import type { IFileSelector } from '../files/types'; +import { NodeRegistry } from '../nodes/registry'; +import type { IOutput } from '../output'; import type { IRenderer } from '../renderer/types'; +import { Analyzer } from '../symbols/analyzer'; import { SymbolRegistry } from '../symbols/registry'; -import type { ISymbolOut } from '../symbols/types'; +import type { Symbol } from '../symbols/symbol'; +import type { SymbolKind } from '../symbols/types'; +import { canShareName } from './namespace'; import type { IProject } from './types'; const externalSourceSymbol = '@'; export class Project implements IProject { - private symbolIdToFileIds: Map> = new Map(); - + readonly analyzer = new Analyzer(); readonly defaultFileName: string; readonly files = new FileRegistry(); readonly fileName?: (name: string) => string; + readonly nodes = new NodeRegistry(); readonly renderers: Record = {}; readonly root: string; readonly symbols = new SymbolRegistry(); @@ -33,107 +37,222 @@ export class Project implements IProject { this.root = root; } - private getRenderer(file: IFileOut): IRenderer | undefined { - return file.extension ? this.renderers[file.extension] : undefined; + render(meta?: IProjectRenderMeta): ReadonlyArray { + this.prepareFiles(); + return this.renderFiles(meta); } private prepareFiles(): void { - // TODO: infer extension from symbols - const extension = '.ts'; - for (const symbol of this.symbols.registered()) { + this.assignFiles(); + this.registerFiles(); + this.resolveFinalSymbolNames(); + this.planImports(); + this.planExports(); + } + + /** + * Creates a file for every symbol so all files exist before planning. + */ + private assignFiles(): void { + this.analyzer.analyze(this.nodes.all(), (ctx) => { + const symbol = ctx.root; const selector = this.symbolToFileSelector(symbol); const file = this.files.reference(selector); - file.symbols.body.push(symbol.id); - // update symbol->files map - const symbolIdToFileIds = - this.symbolIdToFileIds.get(symbol.id) ?? new Set(); - symbolIdToFileIds.add(file.id); - this.symbolIdToFileIds.set(symbol.id, symbolIdToFileIds); - // update re-exports - if (symbol.exportFrom) { - for (const exportFrom of symbol.exportFrom) { - const exportSelector = [exportFrom]; - const exportFile = this.files.reference(exportSelector); - if (exportFile.id !== file.id) { - exportFile.symbols.exports.push(symbol.id); - } + file.symbols.push(symbol); + symbol.setFile(file); + for (const exportFrom of symbol.exportFrom) { + const selector = [exportFrom]; + this.files.reference(selector); + } + for (const dependency of ctx.symbols) { + if (dependency.external) { + const selector = this.symbolToFileSelector(dependency); + const file = this.files.reference(selector); + dependency.setFile(file); } } - } - for (const file of this.files.referenced()) { - if (!file.selector) continue; - if (file.selector[0] === externalSourceSymbol) { - const filePath = file.selector[1]; - if (!filePath) { - this.files.register({ - external: true, - selector: file.selector, - }); - continue; + }); + } + + private planExports(): void { + const seenByFile = new Map< + number, + Map }> + >(); + const sourceFile = new Map(); + + this.analyzer.analyze(this.nodes.all(), (ctx) => { + const symbol = ctx.root; + const file = ctx.root.file; + if (!file) return; + + for (const exportFrom of symbol.exportFrom) { + const target = this.files.reference([exportFrom]); + if (target.id === file.id) continue; + + let map = seenByFile.get(target.id); + if (!map) { + map = new Map(); + seenByFile.set(target.id, map); } - const extension = path.extname(filePath); - if (!extension) { - this.files.register({ - external: true, - path: filePath, - selector: file.selector, - }); - continue; + + const dep = this.symbols.register({ + exported: true, + external: symbol.external, + importKind: symbol.importKind, + kind: symbol.kind, + meta: symbol.meta, + name: symbol.finalName, + }); + dep.setFile(target); + sourceFile.set(dep.id, file.id); + this.resolveSymbolFinalName(dep); + + let entry = map.get(dep.finalName); + if (!entry) { + entry = { dep, kinds: new Set() }; + map.set(dep.finalName, entry); } + entry.kinds.add(dep.kind); + } + }); + + for (const [fileId, map] of seenByFile) { + const target = this.files.get(fileId)!; + for (const [, entry] of map) { + const symbol = entry.dep; + target.reexports.push({ + exportedName: symbol.finalName, + from: sourceFile.get(symbol.id)!, + importedName: symbol.name, + isTypeOnly: [...entry.kinds].every( + (kind) => kind === 'type' || kind === 'interface', + ), + kind: symbol.importKind, + symbolId: symbol.id, + }); + } + } + } + + private planImports(): void { + const seenByFile = new Map>(); + + this.analyzer.analyze(this.nodes.all(), (ctx) => { + const file = ctx.root.file; + if (!file) return; + + let seen = seenByFile.get(file.id); + if (!seen) { + seen = new Set(); + seenByFile.set(file.id, seen); + } + + for (const dependency of ctx.symbols) { + if (!dependency.file || dependency.file.id === file.id) continue; + + this.resolveSymbolFinalName(dependency); + const from = dependency.file.id; + const importedName = dependency.name; + const localName = dependency.finalName; + const isTypeOnly = false; // keep as-is for now + const kind = dependency.importKind; + + const key = `${from}|${importedName}|${localName}|${kind}|${isTypeOnly}`; + if (seen.has(key)) continue; + seen.add(key); + + file.imports.push({ + from, + importedName, + isTypeOnly, + kind, + localName, + symbolId: dependency.id, + }); + } + }); + } + + /** + * Registers all files. + */ + private registerFiles(): void { + for (const file of this.files.referenced()) { + const selector = file.selector; + if (!selector) continue; + if (selector[0] === externalSourceSymbol) { this.files.register({ - extension, external: true, - path: filePath, + path: selector[1], + selector, + }); + } else { + const dirs = file.selector.slice(0, -1); + let name = file.selector[file.selector.length - 1]!; + name = this.fileName?.(name) || name; + const extension = '.ts'; + this.files.register({ + extension, + name, + path: path.resolve(this.root, ...dirs, `${name}${extension}`), selector: file.selector, }); - continue; } - const dirs = file.selector.slice(0, -1); - let name = file.selector[file.selector.length - 1]!; - name = this.fileName?.(name) || name; - this.files.register({ - extension, - name, - path: path.resolve(this.root, ...dirs, `${name}${extension}`), - selector: file.selector, - }); } - - // TODO: track symbol dependencies and inject imports into files - // based on symbol references so the render step can just render } - render(meta?: IProjectRenderMeta): ReadonlyArray { - this.prepareFiles(); + private renderFiles(meta?: IProjectRenderMeta): ReadonlyArray { const files: Map = new Map(); for (const file of this.files.registered()) { if (file.external || !file.path) continue; - const renderer = this.getRenderer(file); + const renderer = file.extension + ? this.renderers[file.extension] + : undefined; if (!renderer) continue; files.set(file.id, { - content: renderer.renderSymbols(file, this, meta), + content: renderer.render(file, this, meta), path: file.path, }); } - for (const [fileId, value] of files.entries()) { - const file = this.files.get(fileId)!; - const renderer = this.getRenderer(file)!; - const content = renderer.renderFile(value.content, file, this, meta); - if (content) { - files.set(file.id, { ...value, content }); - } else { - files.delete(file.id); + return Array.from(files.values()); + } + + private resolveSymbolFinalName(symbol: Symbol): void { + const file = symbol.file; + if (symbol._finalName || !file) return; + + let name = symbol.name; + let index = 1; + while (file.reservedNames.has(name)) { + const scopes = file.reservedNames.get(name)!; + let exit = true; + for (const kind of scopes) { + if (!canShareName(symbol.kind, kind)) { + exit = false; + index = index + 1; + name = `${name}${index}`; + break; + } } + if (exit) break; } - return Array.from(files.values()); + // TODO: ensure valid names + symbol.setFinalName(name); + const scopes = file.reservedNames.get(name) ?? new Set(); + scopes.add(symbol.kind); + file.reservedNames.set(name, scopes); } - symbolIdToFiles(symbolId: number): ReadonlyArray { - const fileIds = this.symbolIdToFileIds.get(symbolId); - return Array.from(fileIds ?? []).map((fileId) => this.files.get(fileId)!); + private resolveFinalSymbolNames(): void { + for (const file of this.files.registered()) { + for (const symbol of file.symbols) { + this.resolveSymbolFinalName(symbol); + } + } } - private symbolToFileSelector(symbol: ISymbolOut): IFileSelector { + private symbolToFileSelector(symbol: Symbol): IFileSelector { if (symbol.external) { return [externalSourceSymbol, symbol.external]; } diff --git a/packages/codegen-core/src/project/types.d.ts b/packages/codegen-core/src/project/types.d.ts index 22d2c6ba79..1bd90a1b8b 100644 --- a/packages/codegen-core/src/project/types.d.ts +++ b/packages/codegen-core/src/project/types.d.ts @@ -1,11 +1,13 @@ -import type { IProjectRenderMeta } from '../extensions/types'; -import type { IFileOut, IFileRegistry } from '../files/types'; -import type { IOutput } from '../output/types'; +import type { IProjectRenderMeta } from '../extensions'; +import type { IFileRegistry } from '../files/types'; +import type { INodeRegistry } from '../nodes/types'; +import type { IOutput } from '../output'; import type { IRenderer } from '../renderer/types'; import type { ISymbolRegistry } from '../symbols/types'; /** - * Represents a code generation project consisting of multiple codegen files. + * Represents a code generation project consisting of codegen files. + * * Manages imports, symbols, and output generation across the project. */ export interface IProject { @@ -22,10 +24,10 @@ export interface IProject { * @returns The transformed file name. */ readonly fileName?: (name: string) => string; - /** - * Centralized file registry for the project. - */ + /** Centralized file registry for the project. */ readonly files: IFileRegistry; + /** Centralized node registry for the project. */ + readonly nodes: INodeRegistry; /** * Produces output representations for all files in the project. * @@ -45,25 +47,8 @@ export interface IProject { * } */ readonly renderers: Record; - /** - * The absolute path to the root folder of the project. - */ + /** The absolute path to the root folder of the project. */ readonly root: string; - /** - * Retrieves files that include symbol ID. The first file is the one - * where the symbol is declared, the rest are files that re-export it. - * - * @param symbolId The symbol ID to find. - * @returns An array of files containing the symbol. - * @example - * const files = project.symbolIdToFiles(31); - * for (const file of files) { - * console.log(file.path); - * } - */ - symbolIdToFiles(symbolId: number): ReadonlyArray; - /** - * Centralized symbol registry for the project. - */ + /** Centralized symbol registry for the project. */ readonly symbols: ISymbolRegistry; } diff --git a/packages/codegen-core/src/renderer/types.d.ts b/packages/codegen-core/src/renderer/types.d.ts index 651d28c0cb..6c1aa4d7a9 100644 --- a/packages/codegen-core/src/renderer/types.d.ts +++ b/packages/codegen-core/src/renderer/types.d.ts @@ -1,8 +1,17 @@ -import type { IProjectRenderMeta } from '../extensions/types'; +import type { IProjectRenderMeta } from '../extensions'; import type { IFileOut } from '../files/types'; import type { IProject } from '../project/types'; export interface IRenderer { + /** + * Renders the given file. + * + * @param file The file to render. + * @param project The project the file belongs to. + * @param meta Arbitrary metadata. + * @returns Rendered content. + */ + render(file: IFileOut, project: IProject, meta?: IProjectRenderMeta): string; /** * Renders content with replaced symbols. * diff --git a/packages/codegen-core/src/symbols/analyzer.ts b/packages/codegen-core/src/symbols/analyzer.ts new file mode 100644 index 0000000000..5ce93f085c --- /dev/null +++ b/packages/codegen-core/src/symbols/analyzer.ts @@ -0,0 +1,61 @@ +import { debug } from '../debug'; +import type { INode } from '../nodes/node'; +import type { Symbol } from './symbol'; + +export interface IAnalysisContext { + /** Register a dependency on another symbol. */ + addDependency(symbol: Symbol): void; + /** Local names declared by nodes within the analyzed symbol. */ + localNames: Set; + /** Root symbol for the current top‑level analysis pass. */ + root: Symbol; + /** Collected symbol references discovered during analysis. */ + symbols: Set; +} + +export class AnalysisContext implements IAnalysisContext { + localNames: Set = new Set(); + root: Symbol; + symbols: Set = new Set(); + + constructor(symbol: Symbol) { + this.root = symbol; + } + + addDependency(symbol: Symbol): void { + if (this.root !== symbol) { + this.symbols.add(symbol); + } + } +} + +export class Analyzer { + private nodeCache = new WeakMap(); + + analyzeNode(node: INode): AnalysisContext { + const cached = this.nodeCache.get(node); + if (cached) return cached; + + if (!node.symbol) { + const message = `Analyzer: cannot analyze node "${node}" without a defining symbol.`; + debug(message, 'analyzer'); + throw new Error(message); + } + + const ctx = new AnalysisContext(node.symbol); + node.analyze(ctx); + + this.nodeCache.set(node, ctx); + return ctx; + } + + analyze( + nodes: ReadonlyArray, + callback?: (ctx: AnalysisContext) => void, + ): void { + for (const node of nodes) { + const ctx = this.analyzeNode(node); + callback?.(ctx); + } + } +} diff --git a/packages/codegen-core/src/symbols/registry.ts b/packages/codegen-core/src/symbols/registry.ts index 00a6cd0e7c..59eed97017 100644 --- a/packages/codegen-core/src/symbols/registry.ts +++ b/packages/codegen-core/src/symbols/registry.ts @@ -1,11 +1,6 @@ -import type { ISymbolMeta } from '../extensions/types'; -import { wrapId } from '../renderer/utils'; -import type { - ISymbolIdentifier, - ISymbolIn, - ISymbolOut, - ISymbolRegistry, -} from './types'; +import type { ISymbolMeta } from '../extensions'; +import { Symbol } from './symbol'; +import type { ISymbolIdentifier, ISymbolIn, ISymbolRegistry } from './types'; type IndexEntry = [string, unknown]; type IndexKeySpace = ReadonlyArray; @@ -23,9 +18,9 @@ export class SymbolRegistry implements ISymbolRegistry { private registerOrder: Set = new Set(); private stubCache: Map = new Map(); private stubs: Set = new Set(); - private values: Map = new Map(); + private values: Map = new Map(); - get(identifier: ISymbolIdentifier): ISymbolOut | undefined { + get(identifier: ISymbolIdentifier): Symbol | undefined { return typeof identifier === 'number' ? this.values.get(identifier) : this.query(identifier)[0]; @@ -48,7 +43,7 @@ export class SymbolRegistry implements ISymbolRegistry { return symbol ? this.registerOrder.has(symbol.id) : false; } - query(filter: ISymbolMeta): ReadonlyArray { + query(filter: ISymbolMeta): ReadonlyArray { const cacheKey = this.buildCacheKey(filter); const cachedIds = this.queryCache.get(cacheKey); if (cachedIds) { @@ -87,33 +82,28 @@ export class SymbolRegistry implements ISymbolRegistry { return resultIds.map((symbolId) => this.values.get(symbolId)!); } - reference(meta: ISymbolMeta): ISymbolOut { + reference(meta: ISymbolMeta): Symbol { const [registered] = this.query(meta); if (registered) return registered; + const cacheKey = this.buildCacheKey(meta); const cachedId = this.stubCache.get(cacheKey); if (cachedId !== undefined) return this.values.get(cachedId)!; - const id = this.id; - const stub: ISymbolOut = { - id, - meta, - placeholder: wrapId(String(id)), - }; + + const stub = new Symbol({ meta, name: '' }, this.id); + this.values.set(stub.id, stub); this.stubs.add(stub.id); this.stubCache.set(cacheKey, stub.id); return stub; } - register(symbol: ISymbolIn): ISymbolOut { - const id = symbol.id !== undefined ? symbol.id : this.id; - const result: ISymbolOut = { - ...symbol, // clone to avoid mutation - id, - placeholder: symbol.placeholder ?? wrapId(String(id)), - }; + register(symbol: ISymbolIn): Symbol { + const result = new Symbol(symbol, this.id); + this.values.set(result.id, result); this.registerOrder.add(result.id); + if (result.meta) { const indexKeySpace = this.buildIndexKeySpace(result.meta); this.indexSymbol(result.id, indexKeySpace); @@ -123,12 +113,15 @@ export class SymbolRegistry implements ISymbolRegistry { return result; } - *registered(): IterableIterator { + *registered(): IterableIterator { for (const id of this.registerOrder.values()) { yield this.values.get(id)!; } } + /** + * @deprecated + */ setValue(symbolId: SymbolId, value: unknown): Map { return this.nodes.set(symbolId, value); } @@ -192,7 +185,7 @@ export class SymbolRegistry implements ISymbolRegistry { return true; } - private replaceStubs(symbol: ISymbolOut, indexKeySpace: IndexKeySpace): void { + private replaceStubs(symbol: Symbol, indexKeySpace: IndexKeySpace): void { for (const stubId of this.stubs.values()) { const stub = this.values.get(stubId); if ( @@ -201,8 +194,8 @@ export class SymbolRegistry implements ISymbolRegistry { ) { const cacheKey = this.buildCacheKey(stub.meta); this.stubCache.delete(cacheKey); - this.values.set(stubId, Object.assign(stub, symbol)); this.stubs.delete(stubId); + stub.setCanonical(symbol); } } } diff --git a/packages/codegen-core/src/symbols/symbol.ts b/packages/codegen-core/src/symbols/symbol.ts new file mode 100644 index 0000000000..c9e09106f1 --- /dev/null +++ b/packages/codegen-core/src/symbols/symbol.ts @@ -0,0 +1,360 @@ +import { debug } from '../debug'; +import type { ISymbolMeta } from '../extensions'; +import type { IFileOut } from '../files/types'; +import type { INode } from '../nodes/node'; +import { wrapId } from '../renderer/utils'; +import type { ISymbolIn, SymbolImportKind, SymbolKind } from './types'; + +export const symbolBrand = globalThis.Symbol('symbol'); + +export class Symbol { + /** + * Canonical symbol this stub resolves to, if any. + * + * Stubs created during DSL construction may later be associated + * with a fully registered symbol. Once set, all property lookups + * should defer to the canonical symbol. + * + * @private + */ + private _canonical?: Symbol; + /** + * Private set of direct symbol dependencies. + * + * @private + */ + private readonly _dependencies = new Set(); + /** + * True if this symbol is exported from its defining file. + * + * @default false + */ + private _exported: boolean; + /** + * Names of files (without extension) from which this symbol is re-exported. + * + * @default [] + */ + private _exportFrom: ReadonlyArray; + /** + * External module name if this symbol is imported from a module not managed + * by the project (e.g. "zod", "lodash"). + * + * @default undefined + */ + private _external?: string; + /** + * The file this symbol is ultimately emitted into. + * + * @private + */ + private _file?: IFileOut; + /** + * The alias-resolved, conflict-free emitted name. + */ + _finalName?: string; + /** + * Custom strategy to determine file output path. + * + * @returns The file path to output the symbol to, or undefined to fallback to default behavior. + */ + private _getFilePath?: (symbol: Symbol) => string | undefined; + /** + * How this symbol should be imported (namespace/default/named). + * + * @default 'named' + */ + private _importKind: SymbolImportKind; + /** + * Kind of symbol (class, type, alias, etc.). + * + * @default 'var' + */ + private _kind: SymbolKind; + /** + * Arbitrary user metadata. + * + * @default undefined + */ + private _meta?: ISymbolMeta; + /** + * Intended user-facing name before conflict resolution. + * + * @example "UserModel" + */ + private _name: string; + /** + * Node that defines this symbol. + * + * @private + */ + private _node?: INode; + + /** Brand used for identifying symbols. */ + readonly '~brand': symbol = symbolBrand; + /** + * Globally unique, stable symbol ID. + */ + readonly id: number; + /** + * Placeholder name for the symbol to be replaced later with the final value. + * + * @deprecated + * @example "_heyapi_31_" + */ + readonly placeholder: string; + + constructor(input: ISymbolIn, id: number) { + this._exported = input.exported ?? false; + this._exportFrom = input.exportFrom ?? []; + this._external = input.external; + this._getFilePath = input.getFilePath; + this.id = id; + this._importKind = input.importKind ?? 'named'; + this._kind = input.kind ?? 'var'; + this._meta = input.meta; + this._name = input.name; + this.placeholder = input.placeholder || wrapId(String(id)); + } + + /** + * Returns the canonical symbol for this instance. + * + * If this symbol was created as a stub, this getter returns + * the fully registered canonical symbol. Otherwise, it returns + * the symbol itself. + */ + get canonical(): Symbol { + return this._canonical ?? this; + } + + /** + * Read-only access to dependencies. + */ + get dependencies(): ReadonlySet { + return this.canonical._dependencies; + } + + /** + * Indicates whether this symbol is exported from its defining file. + */ + get exported(): boolean { + return this.canonical._exported; + } + + /** + * Names of files (without extension) that re-export this symbol. + */ + get exportFrom(): ReadonlyArray { + return this.canonical._exportFrom; + } + + /** + * External module from which this symbol originates, if any. + */ + get external(): string | undefined { + return this.canonical._external; + } + + /** + * Read‑only accessor for the assigned output file. + */ + get file(): IFileOut | undefined { + return this.canonical._file; + } + + /** + * Read‑only accessor for the resolved final emitted name. + */ + get finalName(): string { + return ( + this.canonical._finalName || + this.canonical.placeholder || + this.canonical.name + ); + } + + /** + * Custom file path resolver, if provided. + */ + get getFilePath(): ((symbol: Symbol) => string | undefined) | undefined { + return this.canonical._getFilePath; + } + + /** + * How this symbol should be imported (named/default/namespace). + */ + get importKind(): SymbolImportKind { + return this.canonical._importKind; + } + + /** + * The symbol's kind (class, type, alias, variable, etc.). + */ + get kind(): SymbolKind { + return this.canonical._kind; + } + + /** + * Arbitrary user‑provided metadata associated with this symbol. + */ + get meta(): ISymbolMeta | undefined { + return this.canonical._meta; + } + + /** + * User-intended name before aliasing or conflict resolution. + */ + get name(): string { + return this.canonical._name; + } + + /** + * Read‑only accessor for the defining node. + */ + get node(): INode | undefined { + return this.canonical._node; + } + + /** + * Add a direct dependency on another symbol. + */ + addDependency(symbol: Symbol): void { + this.assertCanonical(); + if (symbol !== this) this._dependencies.add(symbol); + } + + /** + * Marks this symbol as a stub and assigns its canonical symbol. + * + * After calling this, all semantic queries (name, kind, file, + * meta, etc.) should reflect the canonical symbol's values. + * + * @param symbol — The canonical symbol this stub should resolve to. + */ + setCanonical(symbol: Symbol): void { + this._canonical = symbol; + } + + /** + * Marks the symbol as exported from its file. + * + * @param exported — Whether the symbol is exported. + */ + setExported(exported: boolean): void { + this.assertCanonical(); + this._exported = exported; + } + + /** + * Records file names that re‑export this symbol. + * + * @param list — Source files re‑exporting this symbol. + */ + setExportFrom(list: ReadonlyArray): void { + this.assertCanonical(); + this._exportFrom = list; + } + + /** + * Assigns the output file this symbol will be emitted into. + * + * This may only be set once. + */ + setFile(file: IFileOut): void { + this.assertCanonical(); + if (this._file && this._file !== file) { + throw new Error('Symbol is already assigned to a different file.'); + } + this._file = file; + } + + /** + * Assigns the conflict‑resolved final local name for this symbol. + * + * This may only be set once. + */ + setFinalName(name: string): void { + this.assertCanonical(); + if (this._finalName && this._finalName !== name) { + throw new Error('Symbol finalName has already been resolved.'); + } + this._finalName = name; + } + + /** + * Sets how this symbol should be imported. + * + * @param kind — The import strategy (named/default/namespace). + */ + setImportKind(kind: SymbolImportKind): void { + this.assertCanonical(); + this._importKind = kind; + } + + /** + * Sets the symbol's kind (class, type, alias, variable, etc.). + * + * @param kind — The new symbol kind. + */ + setKind(kind: SymbolKind): void { + this.assertCanonical(); + this._kind = kind; + } + + /** + * Updates the intended user‑facing name for this symbol. + * + * @param name — The new name. + */ + setName(name: string): void { + this.assertCanonical(); + this._name = name; + } + + /** + * Binds the node that defines this symbol. + * + * This may only be set once. + */ + setNode(node: INode): void { + this.assertCanonical(); + if (this._node && this._node !== node) { + throw new Error('Symbol is already bound to a different node.'); + } + this._node = node; + node.symbol = this; + } + + /** + * Returns a debug‑friendly string representation identifying the symbol. + */ + toString(): string { + return `[Symbol ${this.name}#${this.id}]`; + } + + /** + * Ensures this symbol is canonical before allowing mutation. + * + * A symbol that has been marked as a stub (i.e., its `_canonical` points + * to a different symbol) may not be mutated. This guard throws an error + * if any setter attempts to modify a stub, preventing accidental writes + * to non‑canonical instances. + * + * @throws {Error} If the symbol is a stub and is being mutated. + * @private + */ + private assertCanonical(): void { + if (this._canonical && this._canonical !== this) { + const message = `Illegal mutation of stub symbol ${this.toString()} → canonical: ${this._canonical.toString()}`; + debug(message, 'symbol'); + throw new Error(message); + } + } +} + +export function isSymbol(value: unknown): value is Symbol { + if (!value || typeof value !== 'object' || Array.isArray(value)) return false; + const obj = value as { '~brand'?: unknown }; + return obj['~brand'] === symbolBrand && Object.hasOwn(obj, '~brand'); +} diff --git a/packages/codegen-core/src/symbols/types.d.ts b/packages/codegen-core/src/symbols/types.d.ts index 572a6cd6fd..1c944d4a28 100644 --- a/packages/codegen-core/src/symbols/types.d.ts +++ b/packages/codegen-core/src/symbols/types.d.ts @@ -1,7 +1,20 @@ -import type { ISymbolMeta } from '../extensions/types'; +import type { ISymbolMeta } from '../extensions'; +import type { Symbol } from './symbol'; export type ISymbolIdentifier = number | ISymbolMeta; +export type SymbolImportKind = 'namespace' | 'default' | 'named'; + +export type SymbolKind = + | 'alias' // export { a as a2 } from 'a'; + | 'class' + | 'enum' + | 'function' + | 'interface' + | 'namespace' + | 'type' + | 'var'; + export type ISymbolIn = { /** * Array of file names (without extensions) from which this symbol is re-exported. @@ -27,19 +40,15 @@ export type ISymbolIn = { * * @returns The file path to output the symbol to, or undefined to fallback to default behavior. */ - getFilePath?: (symbol: ISymbolOut) => string | undefined; - /** - * Unique symbol ID. If one is not provided, it will be auto-generated. - */ - readonly id?: number; + getFilePath?: Symbol['getFilePath']; /** * Kind of import if this symbol represents an import. */ - importKind?: 'namespace' | 'default' | 'named'; + importKind?: SymbolImportKind; /** * Kind of symbol. */ - kind?: 'class' | 'function' | 'type'; + kind?: SymbolKind; /** * Arbitrary metadata about the symbol. * @@ -47,23 +56,22 @@ export type ISymbolIn = { */ meta?: ISymbolMeta; /** - * The desired name for the symbol within its file. If there are multiple symbols - * with the same desired name, this might not end up being the actual name. + * The intended, user-facing name of the symbol before any conflict resolution. + * It is **not** guaranteed to be the final emitted name — aliasing may occur if the + * file contains conflicting local identifiers or other symbols with the same intended name. * * @example "UserModel" */ - name?: string; + name: string; /** * Placeholder name for the symbol to be replaced later with the final value. * + * @deprecated * @example "_heyapi_31_" */ readonly placeholder?: string; }; -export type ISymbolOut = Omit & - Pick, 'id' | 'placeholder'>; - export interface ISymbolRegistry { /** * Get a symbol. @@ -71,7 +79,7 @@ export interface ISymbolRegistry { * @param identifier Symbol identifier to reference. * @returns The symbol, or undefined if not found. */ - get(identifier: ISymbolIdentifier): ISymbolOut | undefined; + get(identifier: ISymbolIdentifier): Symbol | undefined; /** * Returns the value associated with a symbol ID. * @@ -105,14 +113,14 @@ export interface ISymbolRegistry { * @param filter Metadata filter to query symbols by. * @returns Array of symbols matching the filter. */ - query(filter: ISymbolMeta): ReadonlyArray; + query(filter: ISymbolMeta): ReadonlyArray; /** * References a symbol. * * @param meta Metadata filter to reference symbol by. * @returns The referenced symbol. */ - reference(meta: ISymbolMeta): ISymbolOut; + reference(meta: ISymbolMeta): Symbol; /** * Register a symbol globally. * @@ -121,13 +129,13 @@ export interface ISymbolRegistry { * @param symbol Symbol to register. * @returns The registered symbol. */ - register(symbol: ISymbolIn): ISymbolOut; + register(symbol: ISymbolIn): Symbol; /** * Get all symbols in the order they were registered. * * @returns Array of all registered symbols, in insert order. */ - registered(): IterableIterator; + registered(): IterableIterator; /** * Sets a value for a symbol by its ID. * diff --git a/packages/codegen-core/tsconfig.base.json b/packages/codegen-core/tsconfig.base.json index 5afc381fb3..23fc135473 100644 --- a/packages/codegen-core/tsconfig.base.json +++ b/packages/codegen-core/tsconfig.base.json @@ -1,6 +1,8 @@ { "compilerOptions": { + "composite": true, "declaration": true, + "declarationMap": true, "esModuleInterop": true, "module": "ESNext", "moduleResolution": "Bundler", diff --git a/packages/codegen-core/tsconfig.json b/packages/codegen-core/tsconfig.json index 89ceb3bfac..2f0d0f5713 100644 --- a/packages/codegen-core/tsconfig.json +++ b/packages/codegen-core/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "declaration": false, "esModuleInterop": true, "resolveJsonModule": true, "skipLibCheck": true diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts index 49b25b351f..f1d1a20c3b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts @@ -267,7 +267,7 @@ export const vModelWithDictionary = v.object({ * This is a model with one property containing a circular reference */ export const vModelWithCircularReference: v.GenericSchema = v.object({ - prop: v.optional(v.lazy(() => vModelWithCircularReference)) + prop: v.optional(v.lazy(() => vModelWithCircularReference2)) }); /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts index 7d0f299782..300b40b878 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts @@ -343,7 +343,7 @@ export const vDeprecatedModel = v.object({ * This is a model with one property containing a circular reference */ export const vModelWithCircularReference: v.GenericSchema = v.object({ - prop: v.optional(v.lazy(() => vModelWithCircularReference)) + prop: v.optional(v.lazy(() => vModelWithCircularReference2)) }); /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/validators/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/validators/valibot.gen.ts index 8a3624bb42..2a9de6da4f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/validators/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/validators/valibot.gen.ts @@ -6,8 +6,8 @@ export const vBaz = v.optional(v.pipe(v.pipe(v.string(), v.regex(/foo\nbar/)), v export const vFoo: v.GenericSchema = v.optional(v.union([v.object({ foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))), - bar: v.optional(v.lazy(() => vBar)), - baz: v.optional(v.array(v.lazy(() => vFoo))), + bar: v.optional(v.lazy(() => vBar2)), + baz: v.optional(v.array(v.lazy(() => vFoo2))), qux: v.optional(v.pipe(v.number(), v.integer(), v.gtValue(0)), 0) }), v.null()]), null); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts index f2c944329f..b6a706caea 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts @@ -343,7 +343,7 @@ export const vDeprecatedModel = v.object({ * This is a model with one property containing a circular reference */ export const vModelWithCircularReference: v.GenericSchema = v.object({ - prop: v.optional(v.lazy(() => vModelWithCircularReference)) + prop: v.optional(v.lazy(() => vModelWithCircularReference2)) }); /** diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref-2/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref-2/valibot.gen.ts index 1dc875c6bf..e687e2f6bb 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref-2/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref-2/valibot.gen.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; export const vBar: v.GenericSchema = v.strictObject({ - bar: v.union([v.array(v.lazy(() => vBar)), v.null()]) + bar: v.union([v.array(v.lazy(() => vBar2)), v.null()]) }); export const vFoo = v.strictObject({ diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref/valibot.gen.ts index 090aa6a3a2..6ce5d2980d 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-circular-ref/valibot.gen.ts @@ -3,14 +3,14 @@ import * as v from 'valibot'; export const vBar: v.GenericSchema = v.object({ - bar: v.optional(v.array(v.lazy(() => vBar))) + bar: v.optional(v.array(v.lazy(() => vBar2))) }); export const vFoo = v.object({ foo: v.optional(vBar) }); -export const vBaz: v.GenericSchema = v.lazy(() => vQux); +export const vBaz: v.GenericSchema = v.lazy(() => vQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts index 149a0044be..c238f261c1 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts @@ -13,8 +13,8 @@ export const vQux = v.record(v.string(), v.object({ */ export const vFoo: v.GenericSchema = v.optional(v.union([v.object({ foo: v.optional(v.pipe(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/)), v.metadata({ description: 'This is foo property.' }))), - bar: v.optional(v.lazy(() => vBar)), - baz: v.optional(v.pipe(v.array(v.lazy(() => vFoo)), v.metadata({ description: 'This is baz property.' }))), + bar: v.optional(v.lazy(() => vBar2)), + baz: v.optional(v.pipe(v.array(v.lazy(() => vFoo4)), v.metadata({ description: 'This is baz property.' }))), qux: v.optional(v.pipe(v.pipe(v.number(), v.integer(), v.gtValue(0)), v.metadata({ description: 'This is qux property.' })), 0) }), v.null()]), null); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-types/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-types/valibot.gen.ts index b5a5dfce3a..64eb904ba1 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-types/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-types/valibot.gen.ts @@ -13,8 +13,8 @@ export const vQux = v.record(v.string(), v.object({ */ export const vFoo: v.GenericSchema = v.optional(v.union([v.object({ foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))), - bar: v.optional(v.lazy(() => vBar)), - baz: v.optional(v.array(v.lazy(() => vFoo))), + bar: v.optional(v.lazy(() => vBar2)), + baz: v.optional(v.array(v.lazy(() => vFoo4))), qux: v.optional(v.pipe(v.number(), v.integer(), v.gtValue(0)), 0) }), v.null()]), null); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators/valibot.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators/valibot.gen.ts index b5a5dfce3a..64eb904ba1 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators/valibot.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators/valibot.gen.ts @@ -13,8 +13,8 @@ export const vQux = v.record(v.string(), v.object({ */ export const vFoo: v.GenericSchema = v.optional(v.union([v.object({ foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))), - bar: v.optional(v.lazy(() => vBar)), - baz: v.optional(v.array(v.lazy(() => vFoo))), + bar: v.optional(v.lazy(() => vBar2)), + baz: v.optional(v.array(v.lazy(() => vFoo4))), qux: v.optional(v.pipe(v.number(), v.integer(), v.gtValue(0)), 0) }), v.null()]), null); diff --git a/packages/openapi-ts-tests/sdks/__snapshots__/method-class-conflict/class/sdk.gen.ts b/packages/openapi-ts-tests/sdks/__snapshots__/method-class-conflict/class/sdk.gen.ts index 18c322e48e..2f1bff95dc 100644 --- a/packages/openapi-ts-tests/sdks/__snapshots__/method-class-conflict/class/sdk.gen.ts +++ b/packages/openapi-ts-tests/sdks/__snapshots__/method-class-conflict/class/sdk.gen.ts @@ -39,7 +39,7 @@ export class AccountingCompanyMemberships { } public static get api() { - return Api; + return Api2; } } @@ -64,7 +64,7 @@ export class BankAccounts { } public static get api() { - return Api; + return Api2; } } @@ -89,7 +89,7 @@ export class BusinessAccountantAssignments { } public static get api() { - return Api; + return Api2; } } @@ -159,7 +159,7 @@ export class BusinessDocuments { } public static get api() { - return Api; + return Api2; } } @@ -172,7 +172,7 @@ export class BusinessDocumentsSummaries { } public static get api() { - return Api; + return Api2; } } @@ -231,7 +231,7 @@ export class Businesses { } public static get api() { - return Api; + return Api2; } } @@ -241,7 +241,7 @@ export class BusinessSummaries { } public static get api() { - return Api; + return Api2; } } @@ -277,7 +277,7 @@ export class Counterparties { } public static get api() { - return Api; + return Api2; } } @@ -302,7 +302,7 @@ export class DataBoxCredentials { } public static get api() { - return Api; + return Api2; } } @@ -312,7 +312,7 @@ export class DocumentTypes { } public static get api() { - return Api; + return Api2; } } @@ -363,7 +363,7 @@ export class Invitations { } public static get api() { - return Api; + return Api2; } } @@ -418,7 +418,7 @@ export class Invoices { } public static get api() { - return Api; + return Api2; } } @@ -439,7 +439,7 @@ export class InvoiceSettings { } public static get api() { - return Api; + return Api2; } } @@ -449,7 +449,7 @@ export class Licenses { } public static get api() { - return Api; + return Api2; } } @@ -494,7 +494,7 @@ export class PersonalDocuments { } public static get api() { - return Api; + return Api2; } } @@ -530,7 +530,7 @@ export class RecurringTasks { } public static get api() { - return Api; + return Api2; } } @@ -600,7 +600,7 @@ export class Tasks { } public static get api() { - return Api; + return Api2; } } @@ -693,7 +693,7 @@ export class Odata { } public static get accountingCompanies2() { - return AccountingCompanies; + return AccountingCompanies2; } static accountingCompanyMemberships2 = AccountingCompanyMemberships; @@ -735,7 +735,7 @@ export class Odata { export class MapIdentityApi { public static get api() { - return Api; + return Api2; } } diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/default/zod.gen.ts index e44daf15b2..460a92e524 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/mini/default/zod.gen.ts @@ -294,7 +294,7 @@ export const zModelWithDictionary = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/default/zod.gen.ts index 99cb26f2e9..0105d59cdf 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v3/default/zod.gen.ts @@ -293,7 +293,7 @@ export const zModelWithDictionary = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/default/zod.gen.ts index bc91971e37..c108394b3f 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/2.0.x/v4/default/zod.gen.ts @@ -294,7 +294,7 @@ export const zModelWithDictionary = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/circular/zod.gen.ts index 1563aa737e..cdd79ae781 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/circular/zod.gen.ts @@ -4,29 +4,29 @@ import * as z from 'zod/v4-mini'; export const zFoo = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zBar = z.object({ get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.lazy((): any => zBaz)); + return z.optional(z.lazy((): any => zBaz2)); } }); export const zBaz = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zQux = z.union([ z.intersection(z.object({ type: z.literal('struct') - }), z.lazy(() => z.lazy((): any => zCorge))), + }), z.lazy(() => z.lazy((): any => zCorge2))), z.intersection(z.object({ type: z.literal('array') }), zFoo) diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts index b6370f67cd..192ec3f4fc 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts @@ -392,7 +392,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/validators/zod.gen.ts index cfa348c12d..312cc05d25 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/validators/zod.gen.ts @@ -8,10 +8,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo2))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/circular/zod.gen.ts index 4caa3fbd4e..8a00210b07 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/circular/zod.gen.ts @@ -3,22 +3,22 @@ import { z } from 'zod'; export const zFoo: z.AnyZodObject = z.object({ - quux: z.lazy(() => zQuux).optional() + quux: z.lazy(() => zQuux2).optional() }); export const zBar: z.AnyZodObject = z.object({ - bar: z.lazy(() => zBar).optional(), - baz: z.lazy(() => zBaz).optional() + bar: z.lazy(() => zBar2).optional(), + baz: z.lazy(() => zBaz2).optional() }); export const zBaz: z.AnyZodObject = z.object({ - quux: z.lazy(() => zQuux).optional() + quux: z.lazy(() => zQuux2).optional() }); export const zQux: z.ZodTypeAny = z.union([ z.object({ type: z.literal('struct') - }).and(z.lazy(() => zCorge)), + }).and(z.lazy(() => zCorge2)), z.object({ type: z.literal('array') }).and(zFoo) diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts index e7fb6be965..c4839a4e15 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts @@ -391,7 +391,7 @@ export const zDeprecatedModel = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/validators/zod.gen.ts index 9d727f32d0..fb3dee2e92 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/validators/zod.gen.ts @@ -7,8 +7,8 @@ export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo2)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/circular/zod.gen.ts index 3b9c62d9da..fc6459c191 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/circular/zod.gen.ts @@ -4,29 +4,29 @@ import { z } from 'zod/v4'; export const zFoo = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zBar = z.object({ get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.lazy((): any => zBaz)); + return z.optional(z.lazy((): any => zBaz2)); } }); export const zBaz = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zQux = z.union([ z.object({ type: z.literal('struct') - }).and(z.lazy(() => z.lazy((): any => zCorge))), + }).and(z.lazy(() => z.lazy((): any => zCorge2))), z.object({ type: z.literal('array') }).and(zFoo) diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts index 3f328c90db..5c2b4db655 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts @@ -392,7 +392,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/validators/zod.gen.ts index 0daf7a0a68..c68523155c 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/validators/zod.gen.ts @@ -8,10 +8,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo2))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts index 7ca3c6f974..c9bcbd41e6 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts @@ -395,7 +395,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts index 3a8f1806c8..d1f1096374 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import * as z from 'zod/v4-mini'; export const zBar = z.object({ bar: z.union([ - z.array(z.lazy((): any => zBar)), + z.array(z.lazy((): any => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts index dda5d46941..1a31df18a6 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts @@ -4,7 +4,7 @@ import * as z from 'zod/v4-mini'; export const zBar = z.object({ get bar() { - return z.optional(z.array(z.lazy((): any => zBar))); + return z.optional(z.array(z.lazy((): any => zBar2))); } }); @@ -12,7 +12,7 @@ export const zFoo = z.object({ foo: z.optional(zBar) }); -export const zBaz = z.lazy((): any => zQux); +export const zBaz = z.lazy((): any => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts index 8ecedac9aa..111c93c546 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts index 789eb6d8af..b81bbff613 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts @@ -17,10 +17,10 @@ export const zFoo = z._default(z.union([ description: 'This is foo property.' })), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo)).register(z.globalRegistry, { + return z.optional(z.array(z.lazy((): any => zFoo4)).register(z.globalRegistry, { description: 'This is baz property.' })); }, diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts index a8353e832e..477ec22efb 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts @@ -19,10 +19,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators/zod.gen.ts index 304d89bb57..6573ca6903 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts index 75782a6a3d..811635f69a 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts @@ -394,7 +394,7 @@ export const zDeprecatedModel = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts index f2c188be3e..17d8767ab5 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export const zBar: z.AnyZodObject = z.object({ bar: z.union([ - z.array(z.lazy(() => zBar)), + z.array(z.lazy(() => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts index d1f4aaa4b2..b345598be5 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts @@ -3,14 +3,14 @@ import { z } from 'zod'; export const zBar: z.AnyZodObject = z.object({ - bar: z.array(z.lazy(() => zBar)).optional() + bar: z.array(z.lazy(() => zBar2)).optional() }); export const zFoo = z.object({ foo: zBar.optional() }); -export const zBaz: z.ZodTypeAny = z.lazy(() => zQux); +export const zBaz: z.ZodTypeAny = z.lazy(() => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts index c9317d5912..c954012997 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts index 58ea4d7b85..b6145c9a66 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).describe('This is foo property.').optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).describe('This is baz property.').optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).describe('This is baz property.').optional(), qux: z.number().int().gt(0).describe('This is qux property.').optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts index c26d40cf35..a212842caf 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts @@ -18,8 +18,8 @@ export type QuxZodType = z.infer; export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators/zod.gen.ts index 8e5318a8c7..b1c07a86ec 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts index 445c9f1030..ad62250b4a 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts @@ -395,7 +395,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts index 0560094fdc..ee8197c498 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod/v4'; export const zBar = z.object({ bar: z.union([ - z.array(z.lazy((): any => zBar)), + z.array(z.lazy((): any => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts index 7bd218fcbf..f79413d94b 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod/v4'; export const zBar = z.object({ get bar() { - return z.optional(z.array(z.lazy((): any => zBar))); + return z.optional(z.array(z.lazy((): any => zBar2))); } }); @@ -12,7 +12,7 @@ export const zFoo = z.object({ foo: z.optional(zBar) }); -export const zBaz = z.lazy((): any => zQux); +export const zBaz = z.lazy((): any => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts index 5781d917ff..112a30355a 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts index c3c88f8105..2e20f5b8ee 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts @@ -17,10 +17,10 @@ export const zFoo = z.union([ description: 'This is foo property.' })), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo)).register(z.globalRegistry, { + return z.optional(z.array(z.lazy((): any => zFoo4)).register(z.globalRegistry, { description: 'This is baz property.' })); }, diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts index b2d4ca7177..8813506b66 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts @@ -19,10 +19,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators/zod.gen.ts index 15238b2a7e..6601c24f4f 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/default/zod.gen.ts index e00616a4ba..2620039bdf 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/mini/default/zod.gen.ts @@ -294,7 +294,7 @@ export const zModelWithDictionary = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/default/zod.gen.ts index 93c84d92c1..5f66381353 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v3/default/zod.gen.ts @@ -293,7 +293,7 @@ export const zModelWithDictionary = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/default/zod.gen.ts index f2848ba82d..6f19e6c247 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/2.0.x/v4/default/zod.gen.ts @@ -294,7 +294,7 @@ export const zModelWithDictionary = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/circular/zod.gen.ts index 7cced96978..d96af8123d 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/circular/zod.gen.ts @@ -4,29 +4,29 @@ import * as z from 'zod/mini'; export const zFoo = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zBar = z.object({ get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.lazy((): any => zBaz)); + return z.optional(z.lazy((): any => zBaz2)); } }); export const zBaz = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zQux = z.union([ z.intersection(z.object({ type: z.literal('struct') - }), z.lazy(() => z.lazy((): any => zCorge))), + }), z.lazy(() => z.lazy((): any => zCorge2))), z.intersection(z.object({ type: z.literal('array') }), zFoo) diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts index 9734ab8f18..9506b878dc 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts @@ -392,7 +392,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/validators/zod.gen.ts index 7a5c784862..a41e998730 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/validators/zod.gen.ts @@ -8,10 +8,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo2))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/circular/zod.gen.ts index 89c43bbd9d..75646c4eb5 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/circular/zod.gen.ts @@ -3,22 +3,22 @@ import { z } from 'zod/v3'; export const zFoo: z.AnyZodObject = z.object({ - quux: z.lazy(() => zQuux).optional() + quux: z.lazy(() => zQuux2).optional() }); export const zBar: z.AnyZodObject = z.object({ - bar: z.lazy(() => zBar).optional(), - baz: z.lazy(() => zBaz).optional() + bar: z.lazy(() => zBar2).optional(), + baz: z.lazy(() => zBaz2).optional() }); export const zBaz: z.AnyZodObject = z.object({ - quux: z.lazy(() => zQuux).optional() + quux: z.lazy(() => zQuux2).optional() }); export const zQux: z.ZodTypeAny = z.union([ z.object({ type: z.literal('struct') - }).and(z.lazy(() => zCorge)), + }).and(z.lazy(() => zCorge2)), z.object({ type: z.literal('array') }).and(zFoo) diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts index dc5b79a540..3fbd59e4ea 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts @@ -391,7 +391,7 @@ export const zDeprecatedModel = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/validators/zod.gen.ts index bdddc57752..ebbe71b839 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/validators/zod.gen.ts @@ -7,8 +7,8 @@ export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo2)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/circular/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/circular/zod.gen.ts index 8da477654f..00911017c2 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/circular/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/circular/zod.gen.ts @@ -4,29 +4,29 @@ import { z } from 'zod'; export const zFoo = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zBar = z.object({ get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.lazy((): any => zBaz)); + return z.optional(z.lazy((): any => zBaz2)); } }); export const zBaz = z.object({ get quux() { - return z.optional(z.lazy((): any => zQuux)); + return z.optional(z.lazy((): any => zQuux2)); } }); export const zQux = z.union([ z.object({ type: z.literal('struct') - }).and(z.lazy(() => z.lazy((): any => zCorge))), + }).and(z.lazy(() => z.lazy((): any => zCorge2))), z.object({ type: z.literal('array') }).and(zFoo) diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts index 925cc7bf25..9d4c97f489 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts @@ -392,7 +392,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/validators/zod.gen.ts index 944f96e474..adde4f7f05 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/validators/zod.gen.ts @@ -8,10 +8,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo2))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts index 45ebeaf533..84828113f3 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts @@ -395,7 +395,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts index 6014595afa..4ed3f70ef5 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import * as z from 'zod/mini'; export const zBar = z.object({ bar: z.union([ - z.array(z.lazy((): any => zBar)), + z.array(z.lazy((): any => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts index ee8b0c76ef..39b46eb560 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-circular-ref/zod.gen.ts @@ -4,7 +4,7 @@ import * as z from 'zod/mini'; export const zBar = z.object({ get bar() { - return z.optional(z.array(z.lazy((): any => zBar))); + return z.optional(z.array(z.lazy((): any => zBar2))); } }); @@ -12,7 +12,7 @@ export const zFoo = z.object({ foo: z.optional(zBar) }); -export const zBaz = z.lazy((): any => zQux); +export const zBaz = z.lazy((): any => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts index 9ac32990ce..ceb1549b4d 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-dates/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts index e263d218ef..fb296fd0d2 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-metadata/zod.gen.ts @@ -17,10 +17,10 @@ export const zFoo = z._default(z.union([ description: 'This is foo property.' })), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo)).register(z.globalRegistry, { + return z.optional(z.array(z.lazy((): any => zFoo4)).register(z.globalRegistry, { description: 'This is baz property.' })); }, diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts index 669174c0ac..65fbcd47c1 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-types/zod.gen.ts @@ -19,10 +19,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators/zod.gen.ts index b786abd75f..62cbdbe1b0 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z._default(z.union([ z.object({ foo: z.optional(z.string().check(z.regex(/^\d{3}-\d{2}-\d{4}$/))), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z._default(z.optional(z.int().check(z.gt(0))), 0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts index 6d118b7c72..3fe7311bfc 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts @@ -394,7 +394,7 @@ export const zDeprecatedModel = z.object({ * This is a model with one property containing a circular reference */ export const zModelWithCircularReference: z.AnyZodObject = z.object({ - prop: z.lazy(() => zModelWithCircularReference).optional() + prop: z.lazy(() => zModelWithCircularReference2).optional() }); /** diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts index 4d47e4d619..fa92f18d5c 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod/v3'; export const zBar: z.AnyZodObject = z.object({ bar: z.union([ - z.array(z.lazy(() => zBar)), + z.array(z.lazy(() => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts index 02708822cd..402e1414be 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-circular-ref/zod.gen.ts @@ -3,14 +3,14 @@ import { z } from 'zod/v3'; export const zBar: z.AnyZodObject = z.object({ - bar: z.array(z.lazy(() => zBar)).optional() + bar: z.array(z.lazy(() => zBar2)).optional() }); export const zFoo = z.object({ foo: zBar.optional() }); -export const zBaz: z.ZodTypeAny = z.lazy(() => zQux); +export const zBaz: z.ZodTypeAny = z.lazy(() => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts index 68a0a720f4..bee23c297b 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-dates/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts index 10af4babec..e2ef988c44 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-metadata/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).describe('This is foo property.').optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).describe('This is baz property.').optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).describe('This is baz property.').optional(), qux: z.number().int().gt(0).describe('This is qux property.').optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts index 2b0fa9eed9..f837d12dd3 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-types/zod.gen.ts @@ -18,8 +18,8 @@ export type QuxZodType = z.infer; export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators/zod.gen.ts index 15850f6a29..9ebcd26aee 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators/zod.gen.ts @@ -14,8 +14,8 @@ export const zQux = z.record(z.object({ export const zFoo: z.ZodTypeAny = z.union([ z.object({ foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), - bar: z.lazy(() => zBar).optional(), - baz: z.array(z.lazy(() => zFoo)).optional(), + bar: z.lazy(() => zBar2).optional(), + baz: z.array(z.lazy(() => zFoo4)).optional(), qux: z.number().int().gt(0).optional().default(0) }), z.null() diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts index d490dd142c..088a5b89e9 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts @@ -395,7 +395,7 @@ export const zDeprecatedModel = z.object({ */ export const zModelWithCircularReference = z.object({ get prop() { - return z.optional(z.lazy((): any => zModelWithCircularReference)); + return z.optional(z.lazy((): any => zModelWithCircularReference2)); } }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts index 696338aab2..26841bbb93 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref-2/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export const zBar = z.object({ bar: z.union([ - z.array(z.lazy((): any => zBar)), + z.array(z.lazy((): any => zBar2)), z.null() ]) }); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts index b628129549..9c5d6dae67 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-circular-ref/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export const zBar = z.object({ get bar() { - return z.optional(z.array(z.lazy((): any => zBar))); + return z.optional(z.array(z.lazy((): any => zBar2))); } }); @@ -12,7 +12,7 @@ export const zFoo = z.object({ foo: z.optional(zBar) }); -export const zBaz = z.lazy((): any => zQux); +export const zBaz = z.lazy((): any => zQux2); /** * description caused circular reference error diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts index 515d9ad8bd..3ce85de8f9 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-dates/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts index 995ccdb8a8..35bbb17e89 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-metadata/zod.gen.ts @@ -17,10 +17,10 @@ export const zFoo = z.union([ description: 'This is foo property.' })), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo)).register(z.globalRegistry, { + return z.optional(z.array(z.lazy((): any => zFoo4)).register(z.globalRegistry, { description: 'This is baz property.' })); }, diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts index 033e85ced5..c7065d54c5 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-types/zod.gen.ts @@ -19,10 +19,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators/zod.gen.ts index f73a35bac9..e5188fb7ff 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators/zod.gen.ts @@ -15,10 +15,10 @@ export const zFoo = z.union([ z.object({ foo: z.optional(z.string().regex(/^\d{3}-\d{2}-\d{4}$/)), get bar() { - return z.optional(z.lazy((): any => zBar)); + return z.optional(z.lazy((): any => zBar2)); }, get baz() { - return z.optional(z.array(z.lazy((): any => zFoo))); + return z.optional(z.array(z.lazy((): any => zFoo4))); }, qux: z.optional(z.int().gt(0)).default(0) }), diff --git a/packages/openapi-ts/README.md b/packages/openapi-ts/README.md index 8156356e3a..a04fefe05e 100644 --- a/packages/openapi-ts/README.md +++ b/packages/openapi-ts/README.md @@ -1,5 +1,5 @@
- Two people looking at the blueprint + Two people looking at the TypeScript logo

OpenAPI TypeScript

“OpenAPI codegen that just works.”
— Guillermo Rauch, CEO of Vercel

diff --git a/packages/openapi-ts/src/cli.ts b/packages/openapi-ts/src/cli.ts index a8517762d9..75f528a467 100644 --- a/packages/openapi-ts/src/cli.ts +++ b/packages/openapi-ts/src/cli.ts @@ -85,7 +85,7 @@ export const runCli = async (): Promise => { } : {}; - if (userConfig.debug || stringToBoolean(process.env.DEBUG)) { + if (userConfig.debug) { (userConfig.logs as Record).level = 'debug'; delete userConfig.debug; } else if (userConfig.silent) { diff --git a/packages/openapi-ts/src/generate/__tests__/renderer.test.ts b/packages/openapi-ts/src/generate/__tests__/renderer.test.ts index f9bf7bc899..a6a1b1081f 100644 --- a/packages/openapi-ts/src/generate/__tests__/renderer.test.ts +++ b/packages/openapi-ts/src/generate/__tests__/renderer.test.ts @@ -5,16 +5,16 @@ import { TypeScriptRenderer } from '../renderer'; // Minimal local BiMap for tests to avoid importing runtime-only class class LocalBiMap { private map = new Map(); - private reverse = new Map(); + private reverse = new Map>(); get(key: Key) { return this.map.get(key); } - getKey(value: Value) { + getKeys(value: Value) { return this.reverse.get(value); } set(key: Key, value: Value) { this.map.set(key, value); - this.reverse.set(value, key); + this.reverse.set(value, new Set([key])); return this; } hasValue(value: Value) { diff --git a/packages/openapi-ts/src/generate/renderer.ts b/packages/openapi-ts/src/generate/renderer.ts index 26c802a48d..2f96ea45bb 100644 --- a/packages/openapi-ts/src/generate/renderer.ts +++ b/packages/openapi-ts/src/generate/renderer.ts @@ -1,10 +1,12 @@ import path from 'node:path'; import type { - BiMap, Binding, File, IProject, + PlannedImport, + PlannedReexport, + Project, ProjectRenderMeta, Renderer, Symbol, @@ -13,7 +15,7 @@ import { createBinding, mergeBindings, renderIds } from '@hey-api/codegen-core'; import ts from 'typescript'; import { ensureValidIdentifier } from '~/openApi/shared/utils/identifier'; -import { $, TsDsl } from '~/ts-dsl'; +import { $, isTsDsl } from '~/ts-dsl'; const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, @@ -94,6 +96,103 @@ const nodeBuiltins = new Set([ ]); export class TypeScriptRenderer implements Renderer { + render(file: File, project: Project, meta?: ProjectRenderMeta): string { + let output = ''; + const header = '// This file is auto-generated by @hey-api/openapi-ts'; + output += `${header}\n`; + + let outputImports = ''; + for (const plan of file.imports) { + outputImports += nodeToString( + this.renderImport(plan, file, project, meta), + ); + outputImports += '\n'; + } + output = `${output}${output && outputImports ? '\n' : ''}${outputImports}`; + + let outputSymbols = ''; + for (const symbol of file.symbols) { + if (outputSymbols) outputSymbols += '\n'; + const node = symbol.node; + if (isTsDsl(node)) { + outputSymbols += nodeToString(node.$render()); + } + outputSymbols += '\n'; + } + output = `${output}${output && outputSymbols ? '\n' : ''}${outputSymbols}`; + + let outputReexports = ''; + for (const plan of file.reexports) { + if (!outputReexports && outputSymbols) outputReexports += '\n'; + outputReexports += nodeToString( + this.renderExport(plan, file, project, meta), + ); + outputReexports += '\n'; + } + output = `${output}${output && outputReexports ? '\n' : ''}${outputReexports}`; + + return output; + } + + private renderExport( + plan: PlannedReexport, + file: File, + project: Project, + meta?: ProjectRenderMeta, + ): ts.ExportDeclaration { + const modulePath = this.getBindingPath( + file, + project.files.get(plan.from)!, + meta, + ); + const specifier = ts.factory.createExportSpecifier( + false, + plan.importedName !== plan.exportedName + ? $.id(plan.importedName).$render() + : undefined, + $.id(plan.exportedName).$render(), + ); + return ts.factory.createExportDeclaration( + undefined, + plan.isTypeOnly, + ts.factory.createNamedExports([specifier]), + $.literal(modulePath).$render(), + ); + } + + private renderImport( + plan: PlannedImport, + file: File, + project: Project, + meta?: ProjectRenderMeta, + ): ts.ImportDeclaration { + const modulePath = this.getBindingPath( + file, + project.files.get(plan.from)!, + meta, + ); + const localName = $.id(plan.localName).$render(); + const specifier = ts.factory.createImportSpecifier( + false, + plan.importedName !== plan.localName + ? $.id(plan.importedName).$render() + : undefined, + localName, + ); + const importClause = ts.factory.createImportClause( + false, + plan.kind === 'default' ? localName : undefined, + plan.kind === 'namespace' + ? ts.factory.createNamespaceImport(localName) + : ts.factory.createNamedImports([specifier]), + ); + return ts.factory.createImportDeclaration( + undefined, + importClause, + $.literal(modulePath).$render(), + ); + } + renderFile( symbolsAndExports: string, file: File, @@ -118,26 +217,11 @@ export class TypeScriptRenderer implements Renderer { return `${output}${symbolsAndExports}`; } - renderSymbols( - file: File, - project: IProject, - meta?: ProjectRenderMeta, - ): string { + renderSymbols(file: File, project: IProject): string { const exports: Map = new Map(); let output = ''; - const bodyLines = this.getBodyLines(file, project); + const bodyLines: Array = []; output += `${bodyLines.join('\n\n')}${bodyLines.length ? '\n' : ''}`; - output = renderIds(output, (symbolId) => { - if (!file.symbols.body.includes(symbolId)) return; - const symbol = project.symbols.get(symbolId); - return this.replacerFn({ file, project, symbol }); - }); - for (const symbolId of file.symbols.exports) { - const symbol = project.symbols.get(symbolId); - if (symbol) { - this.addBinding({ bindings: exports, file, meta, project, symbol }); - } - } // cast everything into namespace exports for now for (const binding of exports.values()) { binding.namespaceBinding = true; @@ -169,7 +253,7 @@ export class TypeScriptRenderer implements Renderer { return; } - const [symbolFile] = project.symbolIdToFiles(symbol.id); + const symbolFile: File | undefined = undefined; if (!symbolFile || file === symbolFile) return; const modulePath = this.getBindingPath(file, symbolFile, meta); @@ -221,29 +305,6 @@ export class TypeScriptRenderer implements Renderer { return relativePath; } - private getBodyLine(value: unknown, lines: Array): void { - if (value instanceof TsDsl) { - const node = value.$render(); - lines.push(nodeToString(node)); - } else if (typeof value === 'string') { - lines.push(value); - } else if (value instanceof Array) { - for (const node of value) { - this.getBodyLine(node, lines); - } - } else if (value !== undefined && value !== null) { - lines.push(nodeToString(value as any)); - } - } - - private getBodyLines(file: File, project: IProject): Array { - const lines: Array = []; - for (const symbolId of file.symbols.body) { - this.getBodyLine(project.symbols.getValue(symbolId), lines); - } - return lines; - } - private getExportLines( bindings: Map, file: File, @@ -284,9 +345,10 @@ export class TypeScriptRenderer implements Renderer { finalName = renderIds(finalName, (symbolId) => { const symbol = project.symbols.get(symbolId); const name = this.replacerFn({ file, project, symbol }); - const [symbolFile] = project.symbolIdToFiles(symbolId); + const symbolFile: File | undefined = undefined; const sourceName = symbolFile - ? symbolFile.resolvedNames.get(symbolId) + ? // @ts-expect-error + symbolFile.resolvedNames.get(symbolId) : undefined; if (sourceName && sourceName !== name) { // handle only simple imports for now @@ -422,9 +484,10 @@ export class TypeScriptRenderer implements Renderer { finalName = renderIds(finalName, (symbolId) => { const symbol = project.symbols.get(symbolId); const name = this.replacerFn({ file, project, symbol }); - const [symbolFile] = project.symbolIdToFiles(symbolId); + const symbolFile: File | undefined = undefined; const sourceName = symbolFile - ? symbolFile.resolvedNames.get(symbolId) + ? // @ts-expect-error + symbolFile.resolvedNames.get(symbolId) : undefined; if (sourceName && sourceName !== name) { // handle only simple imports for now @@ -491,12 +554,46 @@ export class TypeScriptRenderer implements Renderer { return lines; } - private getUniqueName(base: string, names: BiMap): string { - let index = 2; - let name = base; - while (names.hasValue(name)) { - name = `${base}${index}`; - index += 1; + private getUniqueName({ + base, + file, + index, + project, + symbol, + }: { + base: string; + file: File; + index: number; + project: IProject; + symbol: Symbol; + }): string { + let name = index > 1 ? `${base}${index}` : base; + if (!file.resolvedNames.hasValue(name)) { + return name; + } + const conflictIds = file.resolvedNames.getKeys(name) ?? new Set(); + const conflictSymbols = [...conflictIds] + .map((id) => project.symbols.get(id)) + .filter((s): s is NonNullable => s !== undefined); + if (conflictSymbols.length > 0) { + const conflictKinds = conflictSymbols.map((s) => s.kind); + // avoid conflicts between class and type of the same name + if ( + (symbol.kind === 'type' && + (conflictKinds.includes('type') || + conflictKinds.includes('class'))) || + (symbol.kind !== 'type' && + conflictKinds.some((kind) => kind !== 'type')) || + (symbol.kind === 'class' && conflictKinds.includes('type')) + ) { + name = this.getUniqueName({ + base, + file, + index: index + 1, + project, + symbol, + }); + } } return name; } @@ -514,24 +611,16 @@ export class TypeScriptRenderer implements Renderer { const cached = file.resolvedNames.get(symbol.id); if (cached) return cached; if (!symbol.name) return; - const [symbolFile] = project.symbolIdToFiles(symbol.id); + const symbolFile: File | undefined = undefined; + // @ts-expect-error const symbolFileResolvedName = symbolFile?.resolvedNames.get(symbol.id); - let name = ensureValidIdentifier(symbolFileResolvedName ?? symbol.name); - const conflictId = file.resolvedNames.getKey(name); - if (conflictId !== undefined) { - const conflictSymbol = project.symbols.get(conflictId); - if (conflictSymbol) { - const kinds = [conflictSymbol.kind, symbol.kind]; - if ( - kinds.every((kind) => kind === 'type') || - kinds.every((kind) => kind !== 'type') || - // avoid conflicts between class and type of the same name - (kinds.includes('class') && kinds.includes('type')) - ) { - name = this.getUniqueName(name, file.resolvedNames); - } - } - } + const name = this.getUniqueName({ + base: ensureValidIdentifier(symbolFileResolvedName ?? symbol.name), + file, + index: 1, + project, + symbol, + }); file.resolvedNames.set(symbol.id, name); return name; } diff --git a/packages/openapi-ts/src/ir/types.d.ts b/packages/openapi-ts/src/ir/types.d.ts index 6c28cce866..e7ba0bfe20 100644 --- a/packages/openapi-ts/src/ir/types.d.ts +++ b/packages/openapi-ts/src/ir/types.d.ts @@ -1,3 +1,5 @@ +import type { Symbol } from '@hey-api/codegen-core'; + import type { JsonSchemaDraft2020_12 } from '~/openApi/3.1.x/types/json-schema-draft-2020-12'; import type { SecuritySchemeObject, @@ -185,6 +187,10 @@ interface IRSchemaObject * follow a specific convention. */ propertyNames?: IRSchemaObject; + /** + * Reference to symbol instead of `$ref` string. + */ + symbolRef?: Symbol; /** * Each schema eventually resolves into `type`. */ diff --git a/packages/openapi-ts/src/plugins/@angular/common/httpRequests.ts b/packages/openapi-ts/src/plugins/@angular/common/httpRequests.ts index 1da2efb655..be0653840a 100644 --- a/packages/openapi-ts/src/plugins/@angular/common/httpRequests.ts +++ b/packages/openapi-ts/src/plugins/@angular/common/httpRequests.ts @@ -133,7 +133,6 @@ const generateAngularClassRequests = ({ resource: '@angular/core.Injectable', }); const symbolClass = plugin.registerSymbol({ - exported: true, meta: { category: 'utility', resource: 'class', @@ -148,16 +147,16 @@ const generateAngularClassRequests = ({ name: currentClass.className, }), }); - const node = $.class(symbolClass.placeholder) - .export(symbolClass.exported) + const node = $.class(symbolClass) + .export() .$if(currentClass.root, (c) => c.decorator( - symbolInjectable.placeholder, + symbolInjectable, $.object().prop('providedIn', $.literal('root')), ), ) .do(...currentClass.nodes); - plugin.setSymbolValue(symbolClass, node); + plugin.addNode(node); generatedClasses.add(currentClass.className); }; @@ -181,7 +180,6 @@ const generateAngularFunctionRequests = ({ }); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'utility', resource: 'operation', @@ -197,7 +195,7 @@ const generateAngularFunctionRequests = ({ plugin, symbol, }); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }, { order: 'declarations', @@ -219,7 +217,7 @@ const generateRequestCallExpression = ({ const optionsClient = $('options') .attr('client') .optional() - .$if(symbolClient, (c, s) => c.coalesce(s.placeholder)); + .$if(symbolClient, (c, s) => c.coalesce(s)); return optionsClient .attr('requestOptions') @@ -261,18 +259,19 @@ const generateAngularRequestMethod = ({ role: 'data', tool: 'typescript', }); - const dataType = symbolDataType?.placeholder || 'unknown'; return $.method(methodName) .public() .$if(createOperationComment(operation), (c, v) => c.doc(v)) .param('options', (p) => - p - .required(isRequiredOptions) - .type(`${symbolOptions.placeholder}<${dataType}, ThrowOnError>`), + p.required(isRequiredOptions).type( + $.type(symbolOptions) + .generic(symbolDataType ?? 'unknown') + .generic('ThrowOnError'), + ), ) .generic('ThrowOnError', (g) => g.extends('boolean').default(false)) - .returns(`${symbolHttpRequest.placeholder}`) + .returns($.type(symbolHttpRequest).generic('unknown')) .do( $.return( generateRequestCallExpression({ @@ -312,20 +311,21 @@ const generateAngularRequestFunction = ({ role: 'data', tool: 'typescript', }); - const dataType = symbolDataType?.placeholder || 'unknown'; - return $.const(symbol.placeholder) - .export(symbol.exported) + return $.const(symbol) + .export() .$if(createOperationComment(operation), (c, v) => c.doc(v)) .assign( $.func() .param('options', (p) => - p - .required(isRequiredOptions) - .type(`${symbolOptions.placeholder}<${dataType}, ThrowOnError>`), + p.required(isRequiredOptions).type( + $.type(symbolOptions) + .generic(symbolDataType ?? 'unknown') + .generic('ThrowOnError'), + ), ) .generic('ThrowOnError', (g) => g.extends('boolean').default(false)) - .returns(`${symbolHttpRequest.placeholder}`) + .returns($.type(symbolHttpRequest).generic('unknown')) .do( $.return( generateRequestCallExpression({ diff --git a/packages/openapi-ts/src/plugins/@angular/common/httpResources.ts b/packages/openapi-ts/src/plugins/@angular/common/httpResources.ts index be5ecd8ece..0a31ed7e77 100644 --- a/packages/openapi-ts/src/plugins/@angular/common/httpResources.ts +++ b/packages/openapi-ts/src/plugins/@angular/common/httpResources.ts @@ -136,7 +136,6 @@ const generateAngularClassServices = ({ resource: '@angular/core.Injectable', }); const symbolClass = plugin.registerSymbol({ - exported: true, name: buildName({ config: { case: 'preserve', @@ -145,16 +144,16 @@ const generateAngularClassServices = ({ name: currentClass.className, }), }); - const node = $.class(symbolClass.placeholder) - .export(symbolClass.exported) + const node = $.class(symbolClass) + .export() .$if(currentClass.root, (c) => c.decorator( - symbolInjectable.placeholder, + symbolInjectable, $.object().prop('providedIn', $.literal('root')), ), ) .do(...currentClass.nodes); - plugin.setSymbolValue(symbolClass, node); + plugin.addNode(node); generatedClasses.add(currentClass.className); }; @@ -178,7 +177,6 @@ const generateAngularFunctionServices = ({ }); const symbol = plugin.registerSymbol({ - exported: true, name: plugin.config.httpResources.methodNameBuilder(operation), }); const node = generateAngularResourceFunction({ @@ -187,7 +185,7 @@ const generateAngularFunctionServices = ({ plugin, symbol, }); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }, { order: 'declarations', @@ -215,7 +213,6 @@ const generateResourceCallExpression = ({ resourceId: operation.id, role: 'response', }); - const responseType = symbolResponseType?.placeholder || 'unknown'; if (plugin.config.httpRequests.asClass) { // For class-based request methods, use inject and class hierarchy @@ -241,9 +238,8 @@ const generateResourceCallExpression = ({ category: 'external', resource: '@angular/core.inject', }); - let methodAccess: ReturnType = $( - symbolInject.placeholder, - ).call(symbolClass.placeholder); + let methodAccess: ReturnType = + $(symbolInject).call(symbolClass); // Navigate through the class hierarchy for (let i = 1; i < firstEntry.path.length; i++) { @@ -262,7 +258,7 @@ const generateResourceCallExpression = ({ plugin.config.httpRequests.methodNameBuilder(operation), ); - return $(symbolHttpResource.placeholder) + return $(symbolHttpResource) .call( $.func().do( $.const('opts').assign( @@ -277,7 +273,7 @@ const generateResourceCallExpression = ({ ), ), ) - .generic(responseType); + .generic(symbolResponseType ?? 'unknown'); } } else { const symbolHttpRequest = plugin.referenceSymbol({ @@ -288,7 +284,7 @@ const generateResourceCallExpression = ({ tool: 'angular', }); - return $(symbolHttpResource.placeholder) + return $(symbolHttpResource) .call( $.func().do( $.const('opts').assign( @@ -298,19 +294,19 @@ const generateResourceCallExpression = ({ ), $.return( $.ternary('opts') - .do($(symbolHttpRequest.placeholder).call('opts')) + .do($(symbolHttpRequest).call('opts')) .otherwise($.id('undefined')), ), ), ) - .generic(responseType); + .generic(symbolResponseType ?? 'unknown'); } // Fallback return (should not reach here) - return $(symbolHttpResource.placeholder).call( + return $(symbolHttpResource).call( $.func() .do($.return($.id('undefined'))) - .generic(responseType), + .generic(symbolResponseType ?? 'unknown'), ); }; @@ -338,17 +334,21 @@ const generateAngularResourceMethod = ({ role: 'data', tool: 'typescript', }); - const dataType = symbolDataType?.placeholder || 'unknown'; return $.method(methodName) .public() .$if(createOperationComment(operation), (c, v) => c.doc(v)) .param('options', (p) => - p - .required(isRequiredOptions) - .type( - `() => ${symbolOptions.placeholder}<${dataType}, ThrowOnError> | undefined`, + p.required(isRequiredOptions).type( + $.type.func().returns( + $.type.or( + $.type(symbolOptions) + .generic(symbolDataType ?? 'unknown') + .generic('ThrowOnError'), + $.type('undefined'), + ), ), + ), ) .generic('ThrowOnError', (g) => g.extends('boolean').default(false)) .do( @@ -385,19 +385,23 @@ const generateAngularResourceFunction = ({ role: 'data', tool: 'typescript', }); - const dataType = symbolDataType?.placeholder || 'unknown'; - return $.const(symbol.placeholder) - .export(symbol.exported) + return $.const(symbol) + .export() .$if(createOperationComment(operation), (c, v) => c.doc(v)) .assign( $.func() .param('options', (p) => - p - .required(isRequiredOptions) - .type( - `() => ${symbolOptions.placeholder}<${dataType}, ThrowOnError> | undefined`, + p.required(isRequiredOptions).type( + $.type.func().returns( + $.type.or( + $.type(symbolOptions) + .generic(symbolDataType ?? 'unknown') + .generic('ThrowOnError'), + $.type('undefined'), + ), ), + ), ) .generic('ThrowOnError', (g) => g.extends('boolean').default(false)) .do( diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts index 70a5710a93..c9feb0885b 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts @@ -79,26 +79,25 @@ export const createClient: PluginHandler = ({ plugin }) => { } const createConfigParameters = [ - $(symbolCreateConfig.placeholder) + $(symbolCreateConfig) .call(defaultVals.hasProps() ? defaultVals : undefined) - .generic(symbolClientOptions.placeholder), + .generic(symbolClientOptions), ]; const symbolClient = plugin.registerSymbol({ - exported: true, meta: { category: 'client', }, name: 'client', }); - const statement = $.const(symbolClient.placeholder) - .export(symbolClient.exported) + const statement = $.const(symbolClient) + .export() .assign( - $(symbolCreateClient.placeholder).$if( + $(symbolCreateClient).$if( symbolCreateClientConfig, - (c, s) => c.call($(s.placeholder).call(...createConfigParameters)), + (c, s) => c.call($(s).call(...createConfigParameters)), (c) => c.call(...createConfigParameters), ), ); - plugin.setSymbolValue(symbolClient, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/createClientConfig.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/createClientConfig.ts index 6e75295542..ea408a25d5 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/createClientConfig.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/createClientConfig.ts @@ -23,14 +23,12 @@ export const createClientConfigType = ({ name: 'ClientOptions', }); const symbolCreateClientConfig = plugin.registerSymbol({ - exported: true, - kind: 'type', name: 'CreateClientConfig', }); const typeCreateClientConfig = $.type - .alias(symbolCreateClientConfig.placeholder) - .export(symbolCreateClientConfig.exported) + .alias(symbolCreateClientConfig) + .export() .doc([ 'The `createClientConfig()` function will be called on client initialization', "and the returned object will become the client's initial configuration.", @@ -40,9 +38,7 @@ export const createClientConfigType = ({ 'to ensure your client always has the correct values.', ]) .generic('T', (g) => - g - .extends(symbolDefaultClientOptions.placeholder) - .default(symbolClientOptions.placeholder), + g.extends(symbolDefaultClientOptions).default(symbolClientOptions), ) .type( $.type @@ -51,21 +47,19 @@ export const createClientConfigType = ({ p .optional() .type( - $.type(symbolConfig.placeholder).generic( - $.type.and(symbolDefaultClientOptions.placeholder, 'T'), + $.type(symbolConfig).generic( + $.type.and(symbolDefaultClientOptions, 'T'), ), ), ) .returns( - $.type(symbolConfig.placeholder).generic( + $.type(symbolConfig).generic( $.type.and( - $.type('Required').generic( - symbolDefaultClientOptions.placeholder, - ), + $.type('Required').generic(symbolDefaultClientOptions), 'T', ), ), ), ); - plugin.setSymbolValue(symbolCreateClientConfig, typeCreateClientConfig); + plugin.addNode(typeCreateClientConfig); }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts index 349c4c69ce..ceb4cf5452 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts @@ -369,7 +369,6 @@ const schemasV2_0_X = ({ for (const name in context.spec.definitions) { const schema = context.spec.definitions[name]!; const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', resource: 'definition', @@ -383,8 +382,8 @@ const schemasV2_0_X = ({ plugin, schema, }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .assign( $( $.fromValue(obj, { @@ -392,7 +391,7 @@ const schemasV2_0_X = ({ }), ).as('const'), ); - plugin.setSymbolValue(symbol, statement); + plugin.addNode(statement); } }; @@ -410,7 +409,6 @@ const schemasV3_0_X = ({ for (const name in context.spec.components.schemas) { const schema = context.spec.components.schemas[name]!; const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', resource: 'definition', @@ -424,8 +422,8 @@ const schemasV3_0_X = ({ plugin, schema, }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .assign( $( $.fromValue(obj, { @@ -433,7 +431,7 @@ const schemasV3_0_X = ({ }), ).as('const'), ); - plugin.setSymbolValue(symbol, statement); + plugin.addNode(statement); } }; @@ -451,7 +449,6 @@ const schemasV3_1_X = ({ for (const name in context.spec.components.schemas) { const schema = context.spec.components.schemas[name]!; const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', resource: 'definition', @@ -465,8 +462,8 @@ const schemasV3_1_X = ({ plugin, schema, }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .assign( $( $.fromValue(obj, { @@ -474,7 +471,7 @@ const schemasV3_1_X = ({ }), ).as('const'), ); - plugin.setSymbolValue(symbol, statement); + plugin.addNode(statement); } }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts index 6e607ad8fb..c7c10fd3c7 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts @@ -56,8 +56,7 @@ const createRegistryClass = ({ }): TsDsl => { const defaultKey = 'defaultKey'; const instances = 'instances'; - return $.class(symbol.placeholder) - .export(symbol.exported) + return $.class(symbol) .generic('T') .field(defaultKey, (f) => f.private().readonly().assign($.literal('default')), @@ -122,9 +121,8 @@ const createClientClass = ({ category: 'external', resource: 'client.Client', }); - return $.class(symbol.placeholder) - .export(symbol.exported) - .field('client', (f) => f.protected().type(symbolClient.placeholder)) + return $.class(symbol) + .field('client', (f) => f.protected().type(symbolClient)) .newline() .init((i) => i @@ -135,7 +133,7 @@ const createClientClass = ({ $.type .object() .prop('client', (p) => - p.optional(optionalClient).type(symbolClient.placeholder), + p.optional(optionalClient).type(symbolClient), ), ), ) @@ -146,7 +144,7 @@ const createClientClass = ({ $('args') .attr('client') .optional(optionalClient) - .$if(optionalClient, (a) => a.coalesce(symClient!.placeholder)), + .$if(optionalClient, (a) => a.coalesce(symClient!)), ), ), ); @@ -268,14 +266,12 @@ export const generateClassSdk = ({ plugin.referenceSymbol({ category: 'external', resource: 'client.Composable', - }).placeholder, + }), ) .default($.type.literal('$fetch')), ) .generic(nuxtTypeDefault, (t) => - t.$if(symbolResponse, (t, s) => - t.extends(s.placeholder).default(s.placeholder), - ), + t.$if(symbolResponse, (t, s) => t.extends(s).default(s)), ), (m) => m.generic('ThrowOnError', (t) => @@ -311,8 +307,6 @@ export const generateClassSdk = ({ const symbolHeyApiClient = plugin.config.instance ? plugin.registerSymbol({ - exported: false, - kind: 'class', meta: { category: 'utility', resource: 'class', @@ -324,8 +318,6 @@ export const generateClassSdk = ({ : undefined; const symbolHeyApiRegistry = plugin.config.instance ? plugin.registerSymbol({ - exported: false, - kind: 'class', meta: { category: 'utility', resource: 'class', @@ -380,10 +372,10 @@ export const generateClassSdk = ({ .static(!plugin.config.instance) .assign( plugin.config.instance - ? $.new(refChildClass.placeholder).args( + ? $.new(refChildClass).args( $.object().prop('client', $('this').attr('client')), ) - : $(refChildClass.placeholder), + : $(refChildClass), ), ) : $.getter(memberName, (g) => @@ -392,10 +384,10 @@ export const generateClassSdk = ({ .do( $.return( plugin.config.instance - ? $.new(refChildClass.placeholder).args( + ? $.new(refChildClass).args( $.object().prop('client', $('this').attr('client')), ) - : refChildClass.placeholder, + : refChildClass, ), ), ); @@ -416,12 +408,10 @@ export const generateClassSdk = ({ plugin, symbol: symbolHeyApiClient, }); - plugin.setSymbolValue(symbolHeyApiClient, node); + plugin.addNode(node); } const symbol = plugin.registerSymbol({ - exported: true, - kind: 'class', meta: { category: 'utility', resource: 'class', @@ -447,14 +437,14 @@ export const generateClassSdk = ({ $.type .object() .prop('client', (p) => - p.required(isClientRequired).type(symbolClient.placeholder), + p.required(isClientRequired).type(symbolClient), ) .prop('key', (p) => p.optional().type('string')), ), ) .do( $('super').call('args'), - $(symbol.placeholder) + $(symbol) .attr(registryName) .attr('set') .call('this', $('args').attr('key').required(isClientRequired)), @@ -472,33 +462,31 @@ export const generateClassSdk = ({ sdkName: symbol.placeholder, symbol: symbolHeyApiRegistry, }); - plugin.setSymbolValue(symbolHeyApiRegistry, node); + plugin.addNode(node); const registryNode = $.field(registryName, (f) => f .public() .static() .readonly() - .assign( - $.new(symbolHeyApiRegistry.placeholder).generic(symbol.placeholder), - ), + .assign($.new(symbolHeyApiRegistry).generic(symbol)), ); currentClass.nodes.unshift(registryNode, $.newline()); } - const node = $.class(symbol.placeholder) - .export(symbol.exported) - .extends(symbolHeyApiClient?.placeholder) + const node = $.class(symbol) + .export() + .extends(symbolHeyApiClient) .$if(currentClass.root && isAngularClient, (c) => c.decorator( plugin.referenceSymbol({ category: 'external', resource: '@angular/core.Injectable', - }).placeholder, + }), $.object().prop('providedIn', $.literal('root')), ), ) .do(...currentClass.nodes); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }; for (const sdkClass of sdkClasses.values()) { diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/functions.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/functions.ts index eefe826add..e24b74b185 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/functions.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/functions.ts @@ -67,7 +67,6 @@ export const generateFlatSdk = ({ plugin, }); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'sdk', path: event._path, @@ -82,8 +81,8 @@ export const generateFlatSdk = ({ plugin, }), }); - const node = $.const(symbol.placeholder) - .export(symbol.exported) + const node = $.const(symbol) + .export() .$if(createOperationComment(operation), (c, v) => c.doc(v)) .assign( $.func() @@ -98,14 +97,14 @@ export const generateFlatSdk = ({ plugin.referenceSymbol({ category: 'external', resource: 'client.Composable', - }).placeholder, + }), ) .default($.type.literal('$fetch')), ) .generic(nuxtTypeDefault, (g) => g.$if( symbolResponse, - (t, s) => t.extends(s.placeholder).default(s.placeholder), + (t, s) => t.extends(s).default(s), (t) => t.default('undefined'), ), ), @@ -122,7 +121,7 @@ export const generateFlatSdk = ({ ) .do(...statements), ); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }, { order: 'declarations', diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts index ed35b7ac7a..de2b73c825 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts @@ -184,24 +184,22 @@ export const operationOptionsType = ({ resourceId: operation.id, role: 'response', }); - const dataType = isDataAllowed - ? symbolDataType?.placeholder || 'unknown' - : 'never'; - const responseType = symbolResponseType?.placeholder || 'unknown'; - return `${symbolOptions.placeholder}<${nuxtTypeComposable}, ${dataType}, ${responseType}, ${nuxtTypeDefault}>`; + return $.type(symbolOptions) + .generic(nuxtTypeComposable) + .generic(isDataAllowed ? (symbolDataType ?? 'unknown') : 'never') + .generic(symbolResponseType ?? 'unknown') + .generic(nuxtTypeDefault); } // TODO: refactor this to be more generic, works for now if (throwOnError) { - const dataType = isDataAllowed - ? symbolDataType?.placeholder || 'unknown' - : 'never'; - return `${symbolOptions.placeholder}<${dataType}, ${throwOnError}>`; + return $.type(symbolOptions) + .generic(isDataAllowed ? (symbolDataType ?? 'unknown') : 'never') + .generic(throwOnError); } - const dataType = isDataAllowed ? symbolDataType?.placeholder : 'never'; - return dataType - ? `${symbolOptions.placeholder}<${dataType}>` - : symbolOptions.placeholder; + return $.type(symbolOptions).$if(!isDataAllowed || symbolDataType, (t) => + t.generic(isDataAllowed ? symbolDataType! : 'never'), + ); }; type OperationParameters = { @@ -359,7 +357,6 @@ export const operationStatements = ({ resourceId: operation.id, role: isNuxtClient ? 'response' : 'responses', }); - const responseType = symbolResponseType?.placeholder || 'unknown'; const symbolErrorType = plugin.querySymbol({ category: 'type', @@ -367,7 +364,6 @@ export const operationStatements = ({ resourceId: operation.id, role: isNuxtClient ? 'error' : 'errors', }); - const errorType = symbolErrorType?.placeholder || 'unknown'; // TODO: transform parameters // const query = { @@ -394,7 +390,7 @@ export const operationStatements = ({ category: 'external', resource: 'client.formDataBodySerializer', }); - reqOptions.spread(symbol.placeholder); + reqOptions.spread(symbol); break; } case 'json': @@ -410,7 +406,7 @@ export const operationStatements = ({ category: 'external', resource: 'client.urlSearchParamsBodySerializer', }); - reqOptions.spread(symbol.placeholder); + reqOptions.spread(symbol); break; } } @@ -489,7 +485,7 @@ export const operationStatements = ({ }; if (plugin.isSymbolRegistered(query)) { const ref = plugin.referenceSymbol(query); - reqOptions.prop('responseTransformer', $(ref.placeholder)); + reqOptions.prop('responseTransformer', $(ref)); } } @@ -563,7 +559,7 @@ export const operationStatements = ({ }); statements.push( $.const('params').assign( - $(symbol.placeholder).call($.array(...args), $.array(...config)), + $(symbol).call($.array(...args), $.array(...config)), ), ); reqOptions.spread('params'); @@ -604,7 +600,7 @@ export const operationStatements = ({ if (plugin.config.instance) { clientExpression = optionsClient.coalesce($('this').attr('client')); } else if (symbolClient) { - clientExpression = optionsClient.coalesce(symbolClient.placeholder); + clientExpression = optionsClient.coalesce(symbolClient); } else { clientExpression = optionsClient; } @@ -623,11 +619,16 @@ export const operationStatements = ({ (f) => f .generic(nuxtTypeComposable) - .generic(`${responseType} | ${nuxtTypeDefault}`) - .generic(errorType) + .generic( + $.type.or(symbolResponseType ?? 'unknown', nuxtTypeDefault), + ) + .generic(symbolErrorType ?? 'unknown') .generic(nuxtTypeDefault), (f) => - f.generic(responseType).generic(errorType).generic('ThrowOnError'), + f + .generic(symbolResponseType ?? 'unknown') + .generic(symbolErrorType ?? 'unknown') + .generic('ThrowOnError'), ) .$if(plugin.config.responseStyle === 'data', (f) => f.generic($.type.literal(plugin.config.responseStyle)), diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/typeOptions.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/typeOptions.ts index af87dda6fb..e6f211652c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/typeOptions.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/typeOptions.ts @@ -35,8 +35,6 @@ export const createTypeOptions = ({ name: 'Options', }); const symbolOptions = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', resource: 'client-options', @@ -46,8 +44,8 @@ export const createTypeOptions = ({ }); const typeOptions = $.type - .alias(symbolOptions.placeholder) - .export(symbolOptions.exported) + .alias(symbolOptions) + .export() .$if( isNuxtClient, (t) => @@ -58,23 +56,19 @@ export const createTypeOptions = ({ plugin.referenceSymbol({ category: 'external', resource: 'client.Composable', - }).placeholder, + }), ) .default($.type.literal('$fetch')), ) .generic('TData', (g) => - g - .extends(symbolTDataShape.placeholder) - .default(symbolTDataShape.placeholder), + g.extends(symbolTDataShape).default(symbolTDataShape), ) .generic(nuxtTypeResponse, (g) => g.default('unknown')) .generic(nuxtTypeDefault, (g) => g.default('undefined')), (t) => t .generic('TData', (g) => - g - .extends(symbolTDataShape.placeholder) - .default(symbolTDataShape.placeholder), + g.extends(symbolTDataShape).default(symbolTDataShape), ) .generic('ThrowOnError', (g) => g.extends('boolean').default('boolean'), @@ -82,7 +76,7 @@ export const createTypeOptions = ({ ) .type( $.type.and( - $.type(symbolClientOptions.placeholder).$if( + $.type(symbolClientOptions).$if( isNuxtClient, (t) => t @@ -102,7 +96,7 @@ export const createTypeOptions = ({ 'custom client.', ]) .required(!plugin.config.client && !plugin.config.instance) - .type(symbolClient.placeholder), + .type(symbolClient), ) .prop('meta', (p) => p @@ -115,5 +109,5 @@ export const createTypeOptions = ({ ), ), ); - plugin.setSymbolValue(symbolOptions, typeOptions); + plugin.addNode(typeOptions); }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts index 63563223c8..d74b2e9080 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts @@ -4,8 +4,7 @@ import ts from 'typescript'; import { createOperationKey, operationResponsesMap } from '~/ir/operation'; import type { IR } from '~/ir/types'; import { buildName } from '~/openApi/shared/utils/name'; -import { $ } from '~/ts-dsl'; -import { TsDsl } from '~/ts-dsl'; +import { $, isTsDsl } from '~/ts-dsl'; import { refToName } from '~/utils/ref'; import type { HeyApiTransformersPlugin } from './types'; @@ -28,7 +27,7 @@ const isNodeReturnStatement = ({ }: { node: ts.Expression | ts.Statement | Expr; }) => { - if (node instanceof TsDsl) { + if (isTsDsl(node)) { node = node.$render(); } return node.kind === ts.SyntaxKind.ReturnStatement; @@ -112,13 +111,13 @@ const processSchemaType = ({ }); if (nodes.length) { - const node = $.const(symbol.placeholder).assign( + const node = $.const(symbol).assign( // TODO: parser - add types, generate types without transforms $.func() .param(dataVariableName, (p) => p.type('any')) .do(...ensureStatements(nodes)), ); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); } } finally { buildingSymbols.delete(symbol.id); @@ -131,7 +130,7 @@ const processSchemaType = ({ const currentValue = plugin.gen.symbols.getValue(symbol.id); if (currentValue || buildingSymbols.has(symbol.id)) { const ref = plugin.referenceSymbol(query); - const callExpression = $(ref.placeholder).call(dataExpression); + const callExpression = $(ref).call(dataExpression); if (dataExpression) { // In a map callback, the item needs to be returned, not just the transformation result @@ -337,7 +336,6 @@ export const handler: HeyApiTransformersPlugin['Handler'] = ({ plugin }) => { }); if (!nodes.length) return; const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'transform', resource: 'operation', @@ -352,17 +350,17 @@ export const handler: HeyApiTransformersPlugin['Handler'] = ({ plugin }) => { name: operation.id, }), }); - const value = $.const(symbol.placeholder) - .export(symbol.exported) + const value = $.const(symbol) + .export() .assign( // TODO: parser - add types, generate types without transforms $.func() .async() .param(dataVariableName, (p) => p.type('any')) - .returns($.type('Promise').generic(symbolResponse.placeholder)) + .returns($.type('Promise').generic(symbolResponse)) .do(...ensureStatements(nodes)), ); - plugin.setSymbolValue(symbol, value); + plugin.addNode(value); }, { order: 'declarations', diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/clientOptions.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/clientOptions.ts index 8c135d309d..0e27f6863a 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/clientOptions.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/clientOptions.ts @@ -51,8 +51,8 @@ export const createClientOptions = ({ } const node = $.type - .alias(symbolClientOptions.placeholder) - .export(symbolClientOptions.exported) + .alias(symbolClientOptions) + .export() .type( $.type .object() @@ -60,5 +60,5 @@ export const createClientOptions = ({ p.type($.type.or(...types)), ), ); - plugin.setSymbolValue(symbolClientOptions, node); + plugin.addNode(node); }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/export.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/export.ts index 21cf65a2f2..1cf7b2ac3e 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/export.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/export.ts @@ -1,13 +1,14 @@ -import type { Symbol } from '@hey-api/codegen-core'; - import type { IR } from '~/ir/types'; +import { buildName } from '~/openApi/shared/utils/name'; import { createSchemaComment } from '~/plugins/shared/utils/schema'; import type { MaybeTsDsl, TypeTsDsl } from '~/ts-dsl'; import { $ } from '~/ts-dsl'; +import { pathToJsonPointer, refToName } from '~/utils/ref'; import { numberRegExp } from '~/utils/regexp'; import { stringCase } from '~/utils/stringCase'; import type { HeyApiTypeScriptPlugin } from '../types'; +import type { IrSchemaToAstOptions } from './types'; const schemaToEnumObject = ({ plugin, @@ -83,14 +84,14 @@ const schemaToEnumObject = ({ export const exportType = ({ plugin, schema, - symbol, + state, type, -}: { - plugin: HeyApiTypeScriptPlugin['Instance']; +}: IrSchemaToAstOptions & { schema: IR.SchemaObject; - symbol: Symbol; type: MaybeTsDsl; }) => { + const $ref = pathToJsonPointer(state.path.value); + // root enums have an additional export if (schema.type === 'enum' && plugin.config.enums.enabled) { const enumObject = schemaToEnumObject({ plugin, schema }); @@ -106,7 +107,21 @@ export const exportType = ({ ); } - const objectNode = $.const(symbol.placeholder) + const symbolObject = plugin.registerSymbol({ + meta: { + category: 'utility', + path: state.path.value, + resource: 'definition', + resourceId: $ref, + tags: state.tags?.value, + tool: 'typescript', + }, + name: buildName({ + config: plugin.config.definitions, + name: refToName($ref), + }), + }); + const objectNode = $.const(symbolObject) .export() .$if(createSchemaComment(schema), (c, v) => c.doc(v)) .assign( @@ -118,17 +133,28 @@ export const exportType = ({ ), ).as('const'), ); + plugin.addNode(objectNode); + const symbol = plugin.registerSymbol({ + meta: { + category: 'type', + path: state.path.value, + resource: 'definition', + resourceId: $ref, + tags: state.tags?.value, + tool: 'typescript', + }, + name: buildName({ + config: plugin.config.definitions, + name: refToName($ref), + }), + }); const node = $.type - .alias(symbol.placeholder) + .alias(symbol) .export() .$if(createSchemaComment(schema), (t, v) => t.doc(v)) - .type( - $.type(symbol.placeholder) - .idx($.type(symbol.placeholder).typeof().keyof()) - .typeof(), - ); - plugin.setSymbolValue(symbol, [objectNode, node]); + .type($.type(symbol).idx($.type(symbol).typeof().keyof()).typeof()); + plugin.addNode(node); return; } else if ( plugin.config.enums.mode === 'typescript' || @@ -139,7 +165,21 @@ export const exportType = ({ (type) => type !== 'number' && type !== 'string', ); if (shouldCreateTypeScriptEnum) { - const enumNode = $.enum(symbol.placeholder) + const symbol = plugin.registerSymbol({ + meta: { + category: 'type', + path: state.path.value, + resource: 'definition', + resourceId: $ref, + tags: state.tags?.value, + tool: 'typescript', + }, + name: buildName({ + config: plugin.config.definitions, + name: refToName($ref), + }), + }); + const enumNode = $.enum(symbol) .export() .$if(createSchemaComment(schema), (e, v) => e.doc(v)) .const(plugin.config.enums.mode === 'typescript-const') @@ -150,16 +190,30 @@ export const exportType = ({ .value($.fromValue(item.schema.const)), ), ); - plugin.setSymbolValue(symbol, enumNode); + plugin.addNode(enumNode); return; } } } + const symbol = plugin.registerSymbol({ + meta: { + category: 'type', + path: state.path.value, + resource: 'definition', + resourceId: $ref, + tags: state.tags?.value, + tool: 'typescript', + }, + name: buildName({ + config: plugin.config.definitions, + name: refToName($ref), + }), + }); const node = $.type - .alias(symbol.placeholder) - .export(symbol.exported) + .alias(symbol) + .export() .$if(createSchemaComment(schema), (t, v) => t.doc(v)) .type(type); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts index 8dec4b9502..872a819ee5 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts @@ -121,8 +121,6 @@ const operationToDataType = ({ data.required = dataRequired; const symbol = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -138,8 +136,8 @@ const operationToDataType = ({ }), }); const node = $.type - .alias(symbol.placeholder) - .export(symbol.exported) + .alias(symbol) + .export() .type( irSchemaToAst({ plugin, @@ -147,7 +145,7 @@ const operationToDataType = ({ state, }), ); - plugin.setSymbolValue(symbol, node); + plugin.addNode(node); }; export const operationToType = ({ @@ -164,8 +162,6 @@ export const operationToType = ({ if (errors) { const symbolErrors = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -181,8 +177,8 @@ export const operationToType = ({ }), }); const node = $.type - .alias(symbolErrors.placeholder) - .export(symbolErrors.exported) + .alias(symbolErrors) + .export() .type( irSchemaToAst({ plugin, @@ -190,12 +186,10 @@ export const operationToType = ({ state, }), ); - plugin.setSymbolValue(symbolErrors, node); + plugin.addNode(node); if (error) { const symbol = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -214,21 +208,15 @@ export const operationToType = ({ }), }); const node = $.type - .alias(symbol.placeholder) - .export(symbol.exported) - .type( - $.type(symbolErrors.placeholder).idx( - $.type(symbolErrors.placeholder).keyof(), - ), - ); - plugin.setSymbolValue(symbol, node); + .alias(symbol) + .export() + .type($.type(symbolErrors).idx($.type(symbolErrors).keyof())); + plugin.addNode(node); } } if (responses) { const symbolResponses = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -244,8 +232,8 @@ export const operationToType = ({ }), }); const node = $.type - .alias(symbolResponses.placeholder) - .export(symbolResponses.exported) + .alias(symbolResponses) + .export() .type( irSchemaToAst({ plugin, @@ -253,12 +241,10 @@ export const operationToType = ({ state, }), ); - plugin.setSymbolValue(symbolResponses, node); + plugin.addNode(node); if (response) { const symbol = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -277,14 +263,10 @@ export const operationToType = ({ }), }); const node = $.type - .alias(symbol.placeholder) - .export(symbol.exported) - .type( - $.type(symbolResponses.placeholder).idx( - $.type(symbolResponses.placeholder).keyof(), - ), - ); - plugin.setSymbolValue(symbol, node); + .alias(symbol) + .export() + .type($.type(symbolResponses).idx($.type(symbolResponses).keyof())); + plugin.addNode(node); } } }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts index a4a0e3518d..b46c67b907 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts @@ -1,3 +1,5 @@ +import type { Symbol } from '@hey-api/codegen-core'; + import type { IR } from '~/ir/types'; import { buildName } from '~/openApi/shared/utils/name'; import { createSchemaComment } from '~/plugins/shared/utils/schema'; @@ -12,7 +14,7 @@ const operationToDataType = ({ state, }: IrSchemaToAstOptions & { operation: IR.OperationObject; -}): string => { +}): Symbol => { const data: IR.SchemaObject = { type: 'object', }; @@ -24,8 +26,6 @@ const operationToDataType = ({ if (operation.body) { const symbolWebhookPayload = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -44,8 +44,8 @@ const operationToDataType = ({ }), }); const node = $.type - .alias(symbolWebhookPayload.placeholder) - .export(symbolWebhookPayload.exported) + .alias(symbolWebhookPayload) + .export() .$if(createSchemaComment(operation.body.schema), (t, v) => t.doc(v)) .type( irSchemaToAst({ @@ -54,23 +54,9 @@ const operationToDataType = ({ state, }), ); - plugin.setSymbolValue(symbolWebhookPayload, node); + plugin.addNode(node); - plugin.registerSymbol({ - exported: true, - kind: 'type', - meta: { - category: 'type', - path: state.path.value, - resource: 'definition', - resourceId: symbolWebhookPayload.placeholder, - tags: state.tags?.value, - tool: 'typescript', - }, - name: symbolWebhookPayload.name, - placeholder: symbolWebhookPayload.placeholder, - }); - data.properties.body = { $ref: symbolWebhookPayload.placeholder }; + data.properties.body = { symbolRef: symbolWebhookPayload }; dataRequired.push('body'); } else { data.properties.body = { type: 'never' }; @@ -88,8 +74,6 @@ const operationToDataType = ({ data.required = dataRequired; const symbolWebhookRequest = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -105,8 +89,8 @@ const operationToDataType = ({ }), }); const node = $.type - .alias(symbolWebhookRequest.placeholder) - .export(symbolWebhookRequest.exported) + .alias(symbolWebhookRequest) + .export() .type( irSchemaToAst({ plugin, @@ -114,9 +98,9 @@ const operationToDataType = ({ state, }), ); - plugin.setSymbolValue(symbolWebhookRequest, node); + plugin.addNode(node); - return symbolWebhookRequest.placeholder; + return symbolWebhookRequest; }; export const webhookToType = ({ @@ -125,9 +109,9 @@ export const webhookToType = ({ state, }: IrSchemaToAstOptions & { operation: IR.OperationObject; -}): string => { - const name = operationToDataType({ operation, plugin, state }); - return name; +}): Symbol => { + const symbol = operationToDataType({ operation, plugin, state }); + return symbol; // don't handle webhook responses for now, users only need requestBody }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhooks.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhooks.ts deleted file mode 100644 index 08f22cd118..0000000000 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhooks.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Symbol } from '@hey-api/codegen-core'; - -import { $ } from '~/ts-dsl'; - -import type { HeyApiTypeScriptPlugin } from '../types'; - -export const createWebhooks = ({ - plugin, - symbolWebhooks, - webhookNames, -}: { - plugin: HeyApiTypeScriptPlugin['Instance']; - symbolWebhooks: Symbol; - webhookNames: ReadonlyArray; -}) => { - if (!webhookNames.length) return; - - const node = $.type - .alias(symbolWebhooks.placeholder) - .export(symbolWebhooks.exported) - .type($.type.or(...webhookNames)); - plugin.setSymbolValue(symbolWebhooks, node); -}; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts index f677cf41bc..859fa6a976 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts @@ -1,3 +1,5 @@ +import type { Symbol } from '@hey-api/codegen-core'; + import { deduplicateSchema } from '~/ir/schema'; import type { IR } from '~/ir/types'; import { buildName } from '~/openApi/shared/utils/name'; @@ -5,14 +7,12 @@ import type { SchemaWithType } from '~/plugins'; import { toRefs } from '~/plugins/shared/utils/refs'; import type { MaybeTsDsl, TypeTsDsl } from '~/ts-dsl'; import { $ } from '~/ts-dsl'; -import { pathToJsonPointer, refToName } from '~/utils/ref'; import { createClientOptions } from '../shared/clientOptions'; import { exportType } from '../shared/export'; import { operationToType } from '../shared/operation'; import type { IrSchemaToAstOptions, PluginState } from '../shared/types'; import { webhookToType } from '../shared/webhook'; -import { createWebhooks } from '../shared/webhooks'; import type { HeyApiTypeScriptPlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; @@ -23,13 +23,17 @@ export const irSchemaToAst = ({ }: IrSchemaToAstOptions & { schema: IR.SchemaObject; }): MaybeTsDsl => { + if (schema.symbolRef) { + return $.type(schema.symbolRef); + } + if (schema.$ref) { const symbol = plugin.referenceSymbol({ category: 'type', resource: 'definition', resourceId: schema.$ref, }); - return $.type(symbol.placeholder); + return $.type(symbol); } if (schema.type) { @@ -72,31 +76,10 @@ const handleComponent = ({ schema: IR.SchemaObject; }) => { const type = irSchemaToAst({ plugin, schema, state }); - - // Don't tag enums as 'type' since they export runtime artifacts (values) - const isEnum = schema.type === 'enum' && plugin.config.enums.enabled; - - const $ref = pathToJsonPointer(state.path.value); - const symbol = plugin.registerSymbol({ - exported: true, - kind: isEnum ? undefined : 'type', - meta: { - category: 'type', - path: state.path.value, - resource: 'definition', - resourceId: $ref, - tags: state.tags?.value, - tool: 'typescript', - }, - name: buildName({ - config: plugin.config.definitions, - name: refToName($ref), - }), - }); exportType({ plugin, schema, - symbol, + state, type, }); }; @@ -104,8 +87,6 @@ const handleComponent = ({ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { // reserve identifier for ClientOptions const symbolClientOptions = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', resource: 'client', @@ -121,8 +102,6 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { }); // reserve identifier for Webhooks const symbolWebhooks = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', resource: 'webhook', @@ -138,7 +117,7 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { }); const servers: Array = []; - const webhookNames: Array = []; + const webhooks: Array = []; plugin.forEach( 'operation', @@ -185,7 +164,7 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { servers.push(event.server); break; case 'webhook': - webhookNames.push( + webhooks.push( webhookToType({ operation: event.operation, plugin, @@ -201,5 +180,12 @@ export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { ); createClientOptions({ plugin, servers, symbolClientOptions }); - createWebhooks({ plugin, symbolWebhooks, webhookNames }); + + if (webhooks.length > 0) { + const node = $.type + .alias(symbolWebhooks) + .export() + .type($.type.or(...webhooks)); + plugin.addNode(node); + } }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts index a9f13fa0c2..4edc3287c1 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts @@ -50,25 +50,21 @@ export const stringToAst = ({ if (!plugin.getSymbol(queryTypeId)) { const symbolTypeId = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: queryTypeId, name: 'TypeID', }); const nodeTypeId = $.type - .alias(symbolTypeId.placeholder) - .export(symbolTypeId.exported) + .alias(symbolTypeId) + .export() .generic('T', (g) => g.extends('string')) .type( $.type.template().add($.type('T')).add('_').add($.type('string')), ); - plugin.setSymbolValue(symbolTypeId, nodeTypeId); + plugin.addNode(nodeTypeId); } const symbolTypeId = plugin.referenceSymbol(queryTypeId); const symbolTypeName = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: query, name: stringCase({ case: plugin.config.case, @@ -76,13 +72,13 @@ export const stringToAst = ({ }), }); const node = $.type - .alias(symbolTypeName.placeholder) - .export(symbolTypeName.exported) - .type($.type(symbolTypeId.placeholder).generic($.type.literal(type))); - plugin.setSymbolValue(symbolTypeName, node); + .alias(symbolTypeName) + .export() + .type($.type(symbolTypeId).generic($.type.literal(type))); + plugin.addNode(node); } const symbol = plugin.referenceSymbol(query); - return $.type(symbol.placeholder); + return $.type(symbol); } } diff --git a/packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts b/packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts index 843544c70c..419567dd50 100644 --- a/packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts +++ b/packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts @@ -72,14 +72,13 @@ export const createMutationOptions = ({ o.prop('meta', v), ); const symbolMutationOptions = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.mutationOptions, name: operation.id, }), }); - const statement = $.const(symbolMutationOptions.placeholder) - .export(symbolMutationOptions.exported) + const statement = $.const(symbolMutationOptions) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) @@ -91,5 +90,5 @@ export const createMutationOptions = ({ .returns(mutationType) .do($.return(mutationOpts)), ); - plugin.setSymbolValue(symbolMutationOptions, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts b/packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts index 444c951243..3c9ace0918 100644 --- a/packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts +++ b/packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts @@ -41,9 +41,7 @@ export const createQueryKeyFunction = ({ resource: `${plugin.name}._JSONValue`, }); - const returnType = $.type(symbolQueryKeyType.placeholder) - .generic(TOptionsType) - .idx(0); + const returnType = $.type(symbolQueryKeyType).generic(TOptionsType).idx(0); const baseUrlKey = getClientBaseUrlKey(plugin.context.config); @@ -66,13 +64,13 @@ export const createQueryKeyFunction = ({ name: 'serializeQueryKeyValue', }); - const fn = $.const(symbolCreateQueryKey.placeholder).assign( + const fn = $.const(symbolCreateQueryKey).assign( $.func() .param('id', (p) => p.type('string')) .param('options', (p) => p.optional().type(TOptionsType)) .param('tags', (p) => p.optional().type('ReadonlyArray')) .returns($.type.tuple(returnType)) - .generic(TOptionsType, (g) => g.extends(symbolOptions.placeholder)) + .generic(TOptionsType, (g) => g.extends(symbolOptions)) .do( $.const('params') .type(returnType) @@ -88,13 +86,11 @@ export const createQueryKeyFunction = ({ $.if('tags').do( $('params') .attr('tags') - .assign($('tags').as('unknown').as(symbolJsonValue.placeholder)), + .assign($('tags').as('unknown').as(symbolJsonValue)), ), $.if($('options').attr('body').optional().neq($.id('undefined'))).do( $.const('normalizedBody').assign( - $(symbolSerializeQueryValue.placeholder).call( - $('options').attr('body'), - ), + $(symbolSerializeQueryValue).call($('options').attr('body')), ), $.if($('normalizedBody').neq($.id('undefined'))).do( $('params').attr('body').assign('normalizedBody'), @@ -105,9 +101,7 @@ export const createQueryKeyFunction = ({ ), $.if($('options').attr('query').optional().neq($.id('undefined'))).do( $.const('normalizedQuery').assign( - $(symbolSerializeQueryValue.placeholder).call( - $('options').attr('query'), - ), + $(symbolSerializeQueryValue).call($('options').attr('query')), ), $.if($('normalizedQuery').neq($.id('undefined'))).do( $('params').attr('query').assign('normalizedQuery'), @@ -116,7 +110,7 @@ export const createQueryKeyFunction = ({ $.return($.array($('params'))), ), ); - plugin.setSymbolValue(symbolCreateQueryKey, fn); + plugin.addNode(fn); }; const createQueryKeyLiteral = ({ @@ -139,7 +133,7 @@ const createQueryKeyLiteral = ({ resource: 'createQueryKey', tool: plugin.name, }); - const createQueryKeyCallExpression = $(symbolCreateQueryKey.placeholder).call( + const createQueryKeyCallExpression = $(symbolCreateQueryKey).call( $.literal(id), 'options', tagsExpression, @@ -163,8 +157,6 @@ export const createQueryKeyType = ({ tool: 'sdk', }); const symbolQueryKeyType = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', resource: 'QueryKey', @@ -173,9 +165,9 @@ export const createQueryKeyType = ({ name: 'QueryKey', }); const queryKeyType = $.type - .alias(symbolQueryKeyType.placeholder) - .export(symbolQueryKeyType.exported) - .generic(TOptionsType, (g) => g.extends($.type(symbolOptions.placeholder))) + .alias(symbolQueryKeyType) + .export() + .generic(TOptionsType, (g) => g.extends($.type(symbolOptions))) .type( $.type.tuple( $.type.and( @@ -184,19 +176,15 @@ export const createQueryKeyType = ({ .object() .prop('_id', (p) => p.type('string')) .prop(getClientBaseUrlKey(plugin.context.config), (p) => - p.optional().type(symbolJsonValue.placeholder), + p.optional().type(symbolJsonValue), ) - .prop('body', (p) => p.optional().type(symbolJsonValue.placeholder)) - .prop('query', (p) => - p.optional().type(symbolJsonValue.placeholder), - ) - .prop('tags', (p) => - p.optional().type(symbolJsonValue.placeholder), - ), + .prop('body', (p) => p.optional().type(symbolJsonValue)) + .prop('query', (p) => p.optional().type(symbolJsonValue)) + .prop('tags', (p) => p.optional().type(symbolJsonValue)), ), ), ); - plugin.setSymbolValue(symbolQueryKeyType, queryKeyType); + plugin.addNode(queryKeyType); }; export const queryKeyStatement = ({ @@ -210,8 +198,8 @@ export const queryKeyStatement = ({ }) => { const typeData = useTypeData({ operation, plugin }); const { strippedTypeData } = getPublicTypeData({ plugin, typeData }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .assign( $.func() .param('options', (p) => diff --git a/packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts b/packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts index 54d553926b..6d1e435951 100644 --- a/packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts +++ b/packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts @@ -51,7 +51,6 @@ export const createQueryOptions = ({ let keyExpression: ReturnType; if (plugin.config.queryKeys.enabled) { const symbolQueryKey = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.queryKeys, name: operation.id, @@ -62,8 +61,8 @@ export const createQueryOptions = ({ plugin, symbol: symbolQueryKey, }); - plugin.setSymbolValue(symbolQueryKey, node); - keyExpression = $(symbolQueryKey.placeholder).call(optionsParamName); + plugin.addNode(node); + keyExpression = $(symbolQueryKey).call(optionsParamName); } else { const symbolCreateQueryKey = plugin.referenceSymbol({ category: 'utility', @@ -79,7 +78,7 @@ export const createQueryOptions = ({ ) { tagsExpr = $.array(...operation.tags.map((t) => $.literal(t))); } - keyExpression = $(symbolCreateQueryKey.placeholder).call( + keyExpression = $(symbolCreateQueryKey).call( $.literal(operation.id), optionsParamName, tagsExpr, @@ -125,7 +124,6 @@ export const createQueryOptions = ({ ); const symbolQueryOptionsFn = plugin.registerSymbol({ - exported: true, meta: { category: 'hook', resource: 'operation', @@ -142,13 +140,13 @@ export const createQueryOptions = ({ category: 'external', resource: `${plugin.name}.defineQueryOptions`, }); - const statement = $.const(symbolQueryOptionsFn.placeholder) - .export(symbolQueryOptionsFn.exported) + const statement = $.const(symbolQueryOptionsFn) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) .assign( - $(symbolDefineQueryOptions.placeholder).call( + $(symbolDefineQueryOptions).call( $.func() .param(optionsParamName, (p) => p.required(isRequiredOptions).type(strippedTypeData), @@ -156,5 +154,5 @@ export const createQueryOptions = ({ .do($.return(queryOpts)), ), ); - plugin.setSymbolValue(symbolQueryOptionsFn, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/queryKey.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/queryKey.ts index f4aaa51ff7..c0a01602b9 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/queryKey.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/queryKey.ts @@ -49,17 +49,15 @@ export const createQueryKeyFunction = ({ tool: 'sdk', }); - const returnType = $.type(symbolQueryKeyType.placeholder) - .generic(TOptionsType) - .idx(0); + const returnType = $.type(symbolQueryKeyType).generic(TOptionsType).idx(0); - const fn = $.const(symbolCreateQueryKey.placeholder).assign( + const fn = $.const(symbolCreateQueryKey).assign( $.func() .param('id', (p) => p.type('string')) .param('options', (p) => p.optional().type(TOptionsType)) .param('infinite', (p) => p.optional().type('boolean')) .param('tags', (p) => p.optional().type('ReadonlyArray')) - .generic(TOptionsType, (g) => g.extends(symbolOptions.placeholder)) + .generic(TOptionsType, (g) => g.extends(symbolOptions)) .returns($.type.tuple(returnType)) .do( $.const('params') @@ -90,7 +88,7 @@ export const createQueryKeyFunction = ({ $.return($.array().element($('params'))), ), ); - plugin.setSymbolValue(symbolCreateQueryKey, fn); + plugin.addNode(fn); }; const createQueryKeyLiteral = ({ @@ -116,7 +114,7 @@ const createQueryKeyLiteral = ({ resource: 'createQueryKey', tool: plugin.name, }); - const createQueryKeyCallExpression = $(symbolCreateQueryKey.placeholder).call( + const createQueryKeyCallExpression = $(symbolCreateQueryKey).call( $.literal(id), 'options', isInfinite || tagsArray ? $.literal(Boolean(isInfinite)) : undefined, @@ -132,8 +130,6 @@ export const createQueryKeyType = ({ plugin }: { plugin: PluginInstance }) => { tool: 'sdk', }); const symbolQueryKeyType = plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', resource: 'QueryKey', @@ -142,9 +138,9 @@ export const createQueryKeyType = ({ plugin }: { plugin: PluginInstance }) => { name: 'QueryKey', }); const queryKeyType = $.type - .alias(symbolQueryKeyType.placeholder) - .export(symbolQueryKeyType.exported) - .generic(TOptionsType, (g) => g.extends(symbolOptions.placeholder)) + .alias(symbolQueryKeyType) + .export() + .generic(TOptionsType, (g) => g.extends(symbolOptions)) .type( $.type.tuple( $.type.and( @@ -159,7 +155,7 @@ export const createQueryKeyType = ({ plugin }: { plugin: PluginInstance }) => { ), ), ); - plugin.setSymbolValue(symbolQueryKeyType, queryKeyType); + plugin.addNode(queryKeyType); }; export const queryKeyStatement = ({ @@ -176,8 +172,8 @@ export const queryKeyStatement = ({ typeQueryKey?: string; }) => { const typeData = useTypeData({ operation, plugin }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .assign( $.func() .param('options', (p) => diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/infiniteQueryOptions.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/infiniteQueryOptions.ts index e958e3b768..8179923603 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/infiniteQueryOptions.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/infiniteQueryOptions.ts @@ -36,7 +36,7 @@ const createInfiniteParamsFunction = ({ }), }); - const fn = $.const(symbolCreateInfiniteParams.placeholder).assign( + const fn = $.const(symbolCreateInfiniteParams).assign( $.func() .generic('K', (g) => g.extends( @@ -90,7 +90,7 @@ const createInfiniteParamsFunction = ({ $.return($('params').as('unknown').as($('page').typeofType())), ), ); - plugin.setSymbolValue(symbolCreateInfiniteParams, fn); + plugin.addNode(fn); }; export const createInfiniteQueryOptions = ({ @@ -169,7 +169,6 @@ export const createInfiniteQueryOptions = ({ }); const symbolInfiniteQueryKey = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.infiniteQueryKeys, name: operation.id, @@ -182,7 +181,7 @@ export const createInfiniteQueryOptions = ({ symbol: symbolInfiniteQueryKey, typeQueryKey, }); - plugin.setSymbolValue(symbolInfiniteQueryKey, node); + plugin.addNode(node); const awaitSdkFn = $(queryFn) .call( @@ -217,7 +216,7 @@ export const createInfiniteQueryOptions = ({ ), ), $.const('params').assign( - $(symbolCreateInfiniteParams.placeholder).call('queryKey', 'page'), + $(symbolCreateInfiniteParams).call('queryKey', 'page'), ), ]; @@ -231,14 +230,13 @@ export const createInfiniteQueryOptions = ({ } const symbolInfiniteQueryOptionsFn = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.infiniteQueryOptions, name: operation.id, }), }); - const statement = $.const(symbolInfiniteQueryOptionsFn.placeholder) - .export(symbolInfiniteQueryOptionsFn.exported) + const statement = $.const(symbolInfiniteQueryOptionsFn) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) @@ -247,7 +245,7 @@ export const createInfiniteQueryOptions = ({ .param('options', (p) => p.required(isRequiredOptions).type(typeData)) .do( $.return( - $(symbolInfiniteQueryOptions.placeholder) + $(symbolInfiniteQueryOptions) .call( $.object() .pretty() @@ -259,10 +257,7 @@ export const createInfiniteQueryOptions = ({ .param((p) => p.object('pageParam', 'queryKey', 'signal')) .do(...statements), ) - .prop( - 'queryKey', - $(symbolInfiniteQueryKey.placeholder).call('options'), - ) + .prop('queryKey', $(symbolInfiniteQueryKey).call('options')) .$if( handleMeta(plugin, operation, 'infiniteQueryOptions'), (o, v) => o.prop('meta', v), @@ -279,5 +274,5 @@ export const createInfiniteQueryOptions = ({ ), ), ); - plugin.setSymbolValue(symbolInfiniteQueryOptionsFn, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts index 3dc0fbdd01..6aad6b0f18 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts @@ -51,14 +51,13 @@ export const createMutationOptions = ({ const mutationOptionsFn = 'mutationOptions'; const symbolMutationOptions = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.mutationOptions, name: operation.id, }), }); - const statement = $.const(symbolMutationOptions.placeholder) - .export(symbolMutationOptions.exported) + const statement = $.const(symbolMutationOptions) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) @@ -86,5 +85,5 @@ export const createMutationOptions = ({ $(mutationOptionsFn).return(), ), ); - plugin.setSymbolValue(symbolMutationOptions, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/queryOptions.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/queryOptions.ts index 72c6781529..bec5564343 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/queryOptions.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/queryOptions.ts @@ -54,7 +54,6 @@ export const createQueryOptions = ({ }); const symbolQueryKey = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.queryKeys, name: operation.id, @@ -66,7 +65,7 @@ export const createQueryOptions = ({ plugin, symbol: symbolQueryKey, }); - plugin.setSymbolValue(symbolQueryKey, node); + plugin.addNode(node); const typeData = useTypeData({ operation, plugin }); const typeError = useTypeError({ operation, plugin }); @@ -101,13 +100,12 @@ export const createQueryOptions = ({ .param((p) => p.object('queryKey', 'signal')) .do(...statements), ) - .prop('queryKey', $(symbolQueryKey.placeholder).call(optionsParamName)) + .prop('queryKey', $(symbolQueryKey).call(optionsParamName)) .$if(handleMeta(plugin, operation, 'queryOptions'), (o, v) => o.prop('meta', v), ); const symbolQueryOptionsFn = plugin.registerSymbol({ - exported: plugin.config.queryOptions.exported, meta: { category: 'hook', resource: 'operation', @@ -122,8 +120,8 @@ export const createQueryOptions = ({ }); // TODO: add type error // TODO: AxiosError - const statement = $.const(symbolQueryOptionsFn.placeholder) - .export(symbolQueryOptionsFn.exported) + const statement = $.const(symbolQueryOptionsFn) + .export(plugin.config.queryOptions.exported) .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) @@ -133,16 +131,16 @@ export const createQueryOptions = ({ p.required(isRequiredOptions).type(typeData), ) .do( - $(symbolQueryOptions.placeholder) + $(symbolQueryOptions) .call(queryOptionsObj) .generics( typeResponse, typeError, typeResponse, - $(symbolQueryKey.placeholder).returnType(), + $(symbolQueryKey).returnType(), ) .return(), ), ); - plugin.setSymbolValue(symbolQueryOptionsFn, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/useQuery.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/useQuery.ts index dc5a22a107..201e22deab 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/useQuery.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/useQuery.ts @@ -28,7 +28,6 @@ export const createUseQuery = ({ } const symbolUseQueryFn = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.useQuery, name: operation.id, @@ -53,8 +52,8 @@ export const createUseQuery = ({ role: 'queryOptions', tool: plugin.name, }); - const statement = $.const(symbolUseQueryFn.placeholder) - .export(symbolUseQueryFn.exported) + const statement = $.const(symbolUseQueryFn) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) @@ -64,10 +63,10 @@ export const createUseQuery = ({ p.required(isRequiredOptions).type(typeData), ) .do( - $(symbolUseQuery.placeholder) - .call($(symbolQueryOptionsFn.placeholder).call(optionsParamName)) + $(symbolUseQuery) + .call($(symbolQueryOptionsFn).call(optionsParamName)) .return(), ), ); - plugin.setSymbolValue(symbolUseQueryFn, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/arktype/shared/export.ts b/packages/openapi-ts/src/plugins/arktype/shared/export.ts index b6dfbd4e51..02788cc4ef 100644 --- a/packages/openapi-ts/src/plugins/arktype/shared/export.ts +++ b/packages/openapi-ts/src/plugins/arktype/shared/export.ts @@ -26,31 +26,27 @@ export const exportAst = ({ resource: 'arktype.type', }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v), ) // .type( // ast.typeName // ? (tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: ast.typeName, // }) as unknown as ts.TypeNode) // : undefined, // ) - .assign( - $(type.placeholder).call(ast.def ? $.literal(ast.def) : ast.expression), - ); - plugin.setSymbolValue(symbol, statement); + .assign($(type).call(ast.def ? $.literal(ast.def) : ast.expression)); + plugin.addNode(statement); if (typeInferSymbol) { const inferType = $.type - .alias(typeInferSymbol.placeholder) - .export(typeInferSymbol.exported) - .type( - $.type(symbol.placeholder).attr(identifiers.type.infer).typeofType(), - ); - plugin.setSymbolValue(typeInferSymbol, inferType); + .alias(typeInferSymbol) + .export() + .type($.type(symbol).attr(identifiers.type.infer).typeofType()); + plugin.addNode(inferType); } }; diff --git a/packages/openapi-ts/src/plugins/arktype/v2/api.ts b/packages/openapi-ts/src/plugins/arktype/v2/api.ts index f9551e853b..c1bbc89539 100644 --- a/packages/openapi-ts/src/plugins/arktype/v2/api.ts +++ b/packages/openapi-ts/src/plugins/arktype/v2/api.ts @@ -33,13 +33,7 @@ export const createRequestValidatorV2 = ({ return $.func() .async() .param(dataParameterName) - .do( - $(symbol.placeholder) - .attr('parseAsync') - .call(dataParameterName) - .await() - .return(), - ); + .do($(symbol).attr('parseAsync').call(dataParameterName).await().return()); }; export const createResponseValidatorV2 = ({ @@ -59,11 +53,5 @@ export const createResponseValidatorV2 = ({ return $.func() .async() .param(dataParameterName) - .do( - $(symbol.placeholder) - .attr('parseAsync') - .call(dataParameterName) - .await() - .return(), - ); + .do($(symbol).attr('parseAsync').call(dataParameterName).await().return()); }; diff --git a/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts b/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts index 2392b4537f..c890969d0d 100644 --- a/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts +++ b/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts @@ -43,14 +43,14 @@ export const irSchemaToAst = ({ }; const refSymbol = plugin.referenceSymbol(query); if (plugin.isSymbolRegistered(query)) { - const ref = $(refSymbol.placeholder); + const ref = $(refSymbol); ast.expression = ref; } else { - // expression: z.placeholder, + // expression: z, // name: identifiers.lazy, const lazyExpression = $('TODO') .attr('TODO') - .call($.func().returns('any').do($.return(refSymbol.placeholder))); + .call($.func().returns('any').do($.return(refSymbol))); ast.expression = lazyExpression; ast.hasLazyExpression = true; state.hasLazyExpression.value = true; @@ -74,7 +74,7 @@ export const irSchemaToAst = ({ // }), // parameters: [ // tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: identifiers.globalRegistry, // }), // tsc.objectExpression({ @@ -113,7 +113,7 @@ export const irSchemaToAst = ({ // ) { // ast.expression = tsc.callExpression({ // functionName: tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: identifiers.intersection, // }), // parameters: itemSchemas.map((schema) => schema.expression), @@ -130,7 +130,7 @@ export const irSchemaToAst = ({ // schema.hasCircularReference // ? tsc.callExpression({ // functionName: tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: identifiers.lazy, // }), // parameters: [ @@ -151,7 +151,7 @@ export const irSchemaToAst = ({ // } else { // ast.expression = tsc.callExpression({ // functionName: tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: identifiers.union, // }), // parameters: [ @@ -203,7 +203,7 @@ export const irSchemaToAst = ({ // if (optional) { // ast.expression = tsc.callExpression({ // functionName: tsc.propertyAccessExpression({ - // expression: z.placeholder, + // expression: z, // name: identifiers.optional, // }), // parameters: [ast.expression], @@ -243,7 +243,6 @@ const handleComponent = ({ const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -259,8 +258,6 @@ const handleComponent = ({ }); const typeInferSymbol = plugin.config.definitions.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts b/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts index e3b50b3553..326c2e316b 100644 --- a/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts @@ -90,7 +90,7 @@ export const irSchemaWithTypeToAst = ({ resource: 'arktype.type', }); - const expression = $(type.placeholder).call( + const expression = $(type).call( $.object() .prop('name', $.literal('string')) .prop('platform', $.literal("'android' | 'ios'")) diff --git a/packages/openapi-ts/src/plugins/fastify/plugin.ts b/packages/openapi-ts/src/plugins/fastify/plugin.ts index 5185de5ad6..876961928a 100644 --- a/packages/openapi-ts/src/plugins/fastify/plugin.ts +++ b/packages/openapi-ts/src/plugins/fastify/plugin.ts @@ -26,7 +26,7 @@ const operationToRouteHandler = ({ type.prop('Body', (p) => p .required(operation.body!.required) - .type($.type(symbolDataType.placeholder).idx($.type.literal('body'))), + .type($.type(symbolDataType).idx($.type.literal('body'))), ); } @@ -37,9 +37,7 @@ const operationToRouteHandler = ({ .required( hasParameterGroupObjectRequired(operation.parameters!.header), ) - .type( - $.type(symbolDataType.placeholder).idx($.type.literal('headers')), - ), + .type($.type(symbolDataType).idx($.type.literal('headers'))), ); } @@ -49,9 +47,7 @@ const operationToRouteHandler = ({ .required( hasParameterGroupObjectRequired(operation.parameters!.path), ) - .type( - $.type(symbolDataType.placeholder).idx($.type.literal('path')), - ), + .type($.type(symbolDataType).idx($.type.literal('path'))), ); } @@ -61,9 +57,7 @@ const operationToRouteHandler = ({ .required( hasParameterGroupObjectRequired(operation.parameters!.query), ) - .type( - $.type(symbolDataType.placeholder).idx($.type.literal('query')), - ), + .type($.type(symbolDataType).idx($.type.literal('query'))), ); } } @@ -83,13 +77,10 @@ const operationToRouteHandler = ({ if (keys.length) { const hasDefaultResponse = keys.includes('default'); if (!hasDefaultResponse) { - errorsTypeReference = $.type(symbolErrorType.placeholder); + errorsTypeReference = $.type(symbolErrorType); } else if (keys.length > 1) { errorsTypeReference = $.type('Omit', (t) => - t.generics( - $.type(symbolErrorType.placeholder), - $.type.literal('default'), - ), + t.generics($.type(symbolErrorType), $.type.literal('default')), ); } } @@ -107,13 +98,10 @@ const operationToRouteHandler = ({ if (keys.length) { const hasDefaultResponse = keys.includes('default'); if (!hasDefaultResponse) { - responsesTypeReference = $.type(symbolResponseType.placeholder); + responsesTypeReference = $.type(symbolResponseType); } else if (keys.length > 1) { responsesTypeReference = $.type('Omit', (t) => - t.generics( - $.type(symbolResponseType.placeholder), - $.type.literal('default'), - ), + t.generics($.type(symbolResponseType), $.type.literal('default')), ); } } @@ -137,7 +125,7 @@ const operationToRouteHandler = ({ }); return { name: operation.id, - type: $.type(symbolRouteHandler.placeholder, (t) => t.generic(type)), + type: $.type(symbolRouteHandler, (t) => t.generic(type)), }; }; @@ -154,8 +142,6 @@ export const handler: FastifyPlugin['Handler'] = ({ plugin }) => { }); const symbolRouteHandlers = plugin.registerSymbol({ - exported: true, - kind: 'type', name: 'RouteHandlers', }); @@ -174,9 +160,6 @@ export const handler: FastifyPlugin['Handler'] = ({ plugin }) => { }, ); - const node = $.type - .alias(symbolRouteHandlers.placeholder) - .export(symbolRouteHandlers.exported) - .type(type); - plugin.setSymbolValue(symbolRouteHandlers, node); + const node = $.type.alias(symbolRouteHandlers).export().type(type); + plugin.addNode(node); }; diff --git a/packages/openapi-ts/src/plugins/shared/utils/instance.ts b/packages/openapi-ts/src/plugins/shared/utils/instance.ts index 9f7126c861..66e33f6c6a 100644 --- a/packages/openapi-ts/src/plugins/shared/utils/instance.ts +++ b/packages/openapi-ts/src/plugins/shared/utils/instance.ts @@ -2,6 +2,7 @@ import path from 'node:path'; import type { IProject, + Node, Symbol, SymbolIdentifier, SymbolIn, @@ -105,6 +106,10 @@ export class PluginInstance { this.package = props.context.package; } + addNode(node: Node): void { + return this.gen.nodes.add(node); + } + /** * Iterates over various input elements as specified by the event types, in * a specific order: servers, schemas, parameters, request bodies, then @@ -345,6 +350,9 @@ export class PluginInstance { } } + /** + * @deprecated use addNode + */ setSymbolValue(symbol: Symbol, value: unknown): void { for (const hook of this.eventHooks['symbol:setValue:before']) { hook({ plugin: this, symbol, value }); diff --git a/packages/openapi-ts/src/plugins/swr/v2/useSwr.ts b/packages/openapi-ts/src/plugins/swr/v2/useSwr.ts index ee528a80b7..6a66acb918 100644 --- a/packages/openapi-ts/src/plugins/swr/v2/useSwr.ts +++ b/packages/openapi-ts/src/plugins/swr/v2/useSwr.ts @@ -27,7 +27,6 @@ export const createUseSwr = ({ resource: 'swr', }); const symbolUseQueryFn = plugin.registerSymbol({ - exported: true, name: buildName({ config: plugin.config.useSwr, name: operation.id, @@ -48,14 +47,14 @@ export const createUseSwr = ({ ); } - const statement = $.const(symbolUseQueryFn.placeholder) - .export(symbolUseQueryFn.exported) + const statement = $.const(symbolUseQueryFn) + .export() .$if(plugin.config.comments && createOperationComment(operation), (c, v) => c.doc(v), ) .assign( $.func().do( - $(symbolUseSwr.placeholder) + $(symbolUseSwr) .call( $.literal(operation.path), $.func() @@ -65,5 +64,5 @@ export const createUseSwr = ({ .return(), ), ); - plugin.setSymbolValue(symbolUseQueryFn, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/export.ts b/packages/openapi-ts/src/plugins/valibot/shared/export.ts index bb2c17863c..de503e5bfb 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/export.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/export.ts @@ -24,18 +24,14 @@ export const exportAst = ({ resource: 'valibot.v', }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v), ) .$if(state.hasLazyExpression.value, (c) => - c.type( - $.type(v.placeholder).attr( - ast.typeName || identifiers.types.GenericSchema, - ), - ), + c.type($.type(v).attr(ast.typeName || identifiers.types.GenericSchema)), ) .assign(pipesToAst({ pipes: ast.pipes, plugin })); - plugin.setSymbolValue(symbol, statement); + plugin.addNode(statement); }; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/operation.ts b/packages/openapi-ts/src/plugins/valibot/shared/operation.ts index 8326bc177b..7d36573c94 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/operation.ts @@ -117,7 +117,6 @@ export const irOperationToAst = ({ const ast = getAst(schemaData, state.path.value); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -149,7 +148,6 @@ export const irOperationToAst = ({ const path = [...state.path.value, 'responses']; const ast = getAst(response, path); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path, diff --git a/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts b/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts index b9ea973797..4e5f6b8eb0 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts @@ -18,7 +18,7 @@ export const pipesToAst = ({ category: 'external', resource: 'valibot.v', }); - return $(v.placeholder) + return $(v) .attr(identifiers.methods.pipe) .call(...pipes); }; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/webhook.ts b/packages/openapi-ts/src/plugins/valibot/shared/webhook.ts index 6048db3811..9c6c79bfc8 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/webhook.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/webhook.ts @@ -116,7 +116,6 @@ export const irWebhookToAst = ({ const ast = getAst(schemaData, state.path.value); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/valibot/v1/api.ts b/packages/openapi-ts/src/plugins/valibot/v1/api.ts index 3c987db443..2f14b35712 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/api.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/api.ts @@ -8,11 +8,7 @@ const defaultValidatorResolver = ({ schema, v, }: ValidatorResolverArgs): ReturnType => - $(v.placeholder) - .attr(identifiers.async.parseAsync) - .call(schema.placeholder, 'data') - .await() - .return(); + $(v).attr(identifiers.async.parseAsync).call(schema, 'data').await().return(); export const createRequestValidatorV1 = ({ operation, diff --git a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts index 4361503b62..e10ec90153 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts @@ -50,12 +50,12 @@ export const irSchemaToAst = ({ }; const refSymbol = plugin.referenceSymbol(query); if (plugin.isSymbolRegistered(query)) { - const ref = $(refSymbol.placeholder); + const ref = $(refSymbol); ast.pipes.push(ref); } else { - const lazyExpression = $(v.placeholder) + const lazyExpression = $(v) .attr(identifiers.schemas.lazy) - .call($.func().do($(refSymbol.placeholder).return())); + .call($.func().do($(refSymbol).return())); ast.pipes.push(lazyExpression); state.hasLazyExpression.value = true; } @@ -69,7 +69,7 @@ export const irSchemaToAst = ({ ast.pipes.push(typeAst.expression); if (plugin.config.metadata && schema.description) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.metadata) .call($.object().prop('description', $.literal(schema.description))); ast.pipes.push(expression); @@ -91,12 +91,12 @@ export const irSchemaToAst = ({ }); if (schema.logicalOperator === 'and') { - const intersectExpression = $(v.placeholder) + const intersectExpression = $(v) .attr(identifiers.schemas.intersect) .call($.array(...itemsAst)); ast.pipes.push(intersectExpression); } else { - const unionExpression = $(v.placeholder) + const unionExpression = $(v) .attr(identifiers.schemas.union) .call($.array(...itemsAst)); ast.pipes.push(unionExpression); @@ -120,9 +120,7 @@ export const irSchemaToAst = ({ if (ast.pipes.length) { if (schema.accessScope === 'read') { - const readonlyExpression = $(v.placeholder) - .attr(identifiers.actions.readonly) - .call(); + const readonlyExpression = $(v).attr(identifiers.actions.readonly).call(); ast.pipes.push(readonlyExpression); } @@ -132,7 +130,7 @@ export const irSchemaToAst = ({ const isBigInt = schema.type === 'integer' && schema.format === 'int64'; callParameter = numberParameter({ isBigInt, value: schema.default }); ast.pipes = [ - $(v.placeholder) + $(v) .attr(identifiers.schemas.optional) .call(pipesToAst({ pipes: ast.pipes, plugin }), callParameter), ]; @@ -140,7 +138,7 @@ export const irSchemaToAst = ({ if (optional && !callParameter) { ast.pipes = [ - $(v.placeholder) + $(v) .attr(identifiers.schemas.optional) .call(pipesToAst({ pipes: ast.pipes, plugin })), ]; @@ -161,7 +159,6 @@ const handleComponent = ({ const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts index 8d8bd8a867..2f9a5ac614 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts @@ -24,7 +24,7 @@ export const arrayToAst = ({ category: 'external', resource: 'valibot.v', }); - const functionName = $(v.placeholder).attr(identifiers.schemas.array); + const functionName = $(v).attr(identifiers.schemas.array); if (!schema.items) { const expression = functionName.call( @@ -84,20 +84,20 @@ export const arrayToAst = ({ } if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.length) .call($.fromValue(schema.minItems)); result.pipes.push(expression); } else { if (schema.minItems !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.minLength) .call($.fromValue(schema.minItems)); result.pipes.push(expression); } if (schema.maxItems !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.maxLength) .call($.fromValue(schema.maxItems)); result.pipes.push(expression); diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts index b1b1030690..5e6f3415ae 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts @@ -20,13 +20,11 @@ export const booleanToAst = ({ if (typeof schema.const === 'boolean') { pipes.push( - $(v.placeholder) - .attr(identifiers.schemas.literal) - .call($.literal(schema.const)), + $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)), ); return pipesToAst({ pipes, plugin }); } - pipes.push($(v.placeholder).attr(identifiers.schemas.boolean).call()); + pipes.push($(v).attr(identifiers.schemas.boolean).call()); return pipesToAst({ pipes, plugin }); }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/enum.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/enum.ts index df36da719d..864937b394 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/enum.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/enum.ts @@ -40,12 +40,12 @@ export const enumToAst = ({ resource: 'valibot.v', }); - let resultExpression = $(v.placeholder) + let resultExpression = $(v) .attr(identifiers.schemas.picklist) .call($.array(...enumMembers)); if (isNullable) { - resultExpression = $(v.placeholder) + resultExpression = $(v) .attr(identifiers.schemas.nullable) .call(resultExpression); } diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/never.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/never.ts index f2a79046fe..937e92814d 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/never.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/never.ts @@ -13,6 +13,6 @@ export const neverToAst = ({ category: 'external', resource: 'valibot.v', }); - const expression = $(v.placeholder).attr(identifiers.schemas.never).call(); + const expression = $(v).attr(identifiers.schemas.never).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/null.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/null.ts index 25eb8de333..db4e25f7c6 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/null.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/null.ts @@ -13,6 +13,6 @@ export const nullToAst = ({ category: 'external', resource: 'valibot.v', }); - const expression = $(v.placeholder).attr(identifiers.schemas.null).call(); + const expression = $(v).attr(identifiers.schemas.null).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts index 99e6008b02..6f02cc2d5b 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts @@ -68,42 +68,38 @@ export const numberToAst = ({ literalValue = $.fromValue(constValue); } - return $(v.placeholder) - .attr(identifiers.schemas.literal) - .call(literalValue); + return $(v).attr(identifiers.schemas.literal).call(literalValue); } const pipes: Array> = []; // For bigint formats (int64, uint64), create union of number, string, and bigint with transform if (isBigInt) { - const unionExpression = $(v.placeholder) + const unionExpression = $(v) .attr(identifiers.schemas.union) .call( $.array( - $(v.placeholder).attr(identifiers.schemas.number).call(), - $(v.placeholder).attr(identifiers.schemas.string).call(), - $(v.placeholder).attr(identifiers.schemas.bigInt).call(), + $(v).attr(identifiers.schemas.number).call(), + $(v).attr(identifiers.schemas.string).call(), + $(v).attr(identifiers.schemas.bigInt).call(), ), ); pipes.push(unionExpression); // Add transform to convert to BigInt - const transformExpression = $(v.placeholder) + const transformExpression = $(v) .attr(identifiers.actions.transform) .call($.func().param('x').do($('BigInt').call('x').return())); pipes.push(transformExpression); } else { // For regular number formats, use number schema - const expression = $(v.placeholder).attr(identifiers.schemas.number).call(); + const expression = $(v).attr(identifiers.schemas.number).call(); pipes.push(expression); } // Add integer validation for integer types (except when using bigint union) if (!isBigInt && isInteger) { - const expression = $(v.placeholder) - .attr(identifiers.actions.integer) - .call(); + const expression = $(v).attr(identifiers.actions.integer).call(); pipes.push(expression); } @@ -115,7 +111,7 @@ export const numberToAst = ({ const maxErrorMessage = formatInfo.maxError; // Add minimum value validation - const minExpression = $(v.placeholder) + const minExpression = $(v) .attr(identifiers.actions.minValue) .call( isBigInt ? $('BigInt').call($.literal(minValue)) : $.literal(minValue), @@ -124,7 +120,7 @@ export const numberToAst = ({ pipes.push(minExpression); // Add maximum value validation - const maxExpression = $(v.placeholder) + const maxExpression = $(v) .attr(identifiers.actions.maxValue) .call( isBigInt ? $('BigInt').call($.literal(maxValue)) : $.literal(maxValue), @@ -134,24 +130,24 @@ export const numberToAst = ({ } if (schema.exclusiveMinimum !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.gtValue) .call(numberParameter({ isBigInt, value: schema.exclusiveMinimum })); pipes.push(expression); } else if (schema.minimum !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.minValue) .call(numberParameter({ isBigInt, value: schema.minimum })); pipes.push(expression); } if (schema.exclusiveMaximum !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.ltValue) .call(numberParameter({ isBigInt, value: schema.exclusiveMaximum })); pipes.push(expression); } else if (schema.maximum !== undefined) { - const expression = $(v.placeholder) + const expression = $(v) .attr(identifiers.actions.maxValue) .call(numberParameter({ isBigInt, value: schema.maximum })); pipes.push(expression); diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts index da80e6f042..2c749d9330 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts @@ -21,36 +21,27 @@ function defaultObjectBaseResolver({ // Handle `additionalProperties: { type: 'never' }` → v.strictObject() if (additional === null) { - return pipes.push( - $(v.placeholder).attr(identifiers.schemas.strictObject).call(shape), - ); + return pipes.push($(v).attr(identifiers.schemas.strictObject).call(shape)); } // Handle additionalProperties as schema → v.record() or v.objectWithRest() if (additional) { if (shape.isEmpty) { return pipes.push( - $(v.placeholder) + $(v) .attr(identifiers.schemas.record) - .call( - $(v.placeholder).attr(identifiers.schemas.string).call(), - additional, - ), + .call($(v).attr(identifiers.schemas.string).call(), additional), ); } // If there are named properties, use v.objectWithRest() to validate both return pipes.push( - $(v.placeholder) - .attr(identifiers.schemas.objectWithRest) - .call(shape, additional), + $(v).attr(identifiers.schemas.objectWithRest).call(shape, additional), ); } // Default case → v.object() - return pipes.push( - $(v.placeholder).attr(identifiers.schemas.object).call(shape), - ); + return pipes.push($(v).attr(identifiers.schemas.object).call(shape)); } export const objectToAst = ({ diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts index 1b41cf8db2..5a03003352 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts @@ -18,28 +18,20 @@ const defaultFormatResolver = ({ switch (schema.format) { case 'date': - return pipes.push( - $(v.placeholder).attr(identifiers.actions.isoDate).call(), - ); + return pipes.push($(v).attr(identifiers.actions.isoDate).call()); case 'date-time': - return pipes.push( - $(v.placeholder).attr(identifiers.actions.isoTimestamp).call(), - ); + return pipes.push($(v).attr(identifiers.actions.isoTimestamp).call()); case 'email': - return pipes.push( - $(v.placeholder).attr(identifiers.actions.email).call(), - ); + return pipes.push($(v).attr(identifiers.actions.email).call()); case 'ipv4': case 'ipv6': - return pipes.push($(v.placeholder).attr(identifiers.actions.ip).call()); + return pipes.push($(v).attr(identifiers.actions.ip).call()); case 'time': - return pipes.push( - $(v.placeholder).attr(identifiers.actions.isoTimeSecond).call(), - ); + return pipes.push($(v).attr(identifiers.actions.isoTimeSecond).call()); case 'uri': - return pipes.push($(v.placeholder).attr(identifiers.actions.url).call()); + return pipes.push($(v).attr(identifiers.actions.url).call()); case 'uuid': - return pipes.push($(v.placeholder).attr(identifiers.actions.uuid).call()); + return pipes.push($(v).attr(identifiers.actions.uuid).call()); } return true; @@ -60,14 +52,12 @@ export const stringToAst = ({ if (typeof schema.const === 'string') { pipes.push( - $(v.placeholder) - .attr(identifiers.schemas.literal) - .call($.literal(schema.const)), + $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)), ); return pipesToAst({ pipes, plugin }); } - pipes.push($(v.placeholder).attr(identifiers.schemas.string).call()); + pipes.push($(v).attr(identifiers.schemas.string).call()); if (schema.format) { const args: FormatResolverArgs = { $, pipes, plugin, schema }; @@ -78,14 +68,12 @@ export const stringToAst = ({ if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { pipes.push( - $(v.placeholder) - .attr(identifiers.actions.length) - .call($.literal(schema.minLength)), + $(v).attr(identifiers.actions.length).call($.literal(schema.minLength)), ); } else { if (schema.minLength !== undefined) { pipes.push( - $(v.placeholder) + $(v) .attr(identifiers.actions.minLength) .call($.literal(schema.minLength)), ); @@ -93,7 +81,7 @@ export const stringToAst = ({ if (schema.maxLength !== undefined) { pipes.push( - $(v.placeholder) + $(v) .attr(identifiers.actions.maxLength) .call($.literal(schema.maxLength)), ); @@ -102,9 +90,7 @@ export const stringToAst = ({ if (schema.pattern) { pipes.push( - $(v.placeholder) - .attr(identifiers.actions.regex) - .call($.regexp(schema.pattern)), + $(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)), ); } diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts index 346d280fbe..aa9e92bb70 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts @@ -24,12 +24,10 @@ export const tupleToAst = ({ if (schema.const && Array.isArray(schema.const)) { const tupleElements = schema.const.map((value) => - $(v.placeholder) - .attr(identifiers.schemas.literal) - .call($.fromValue(value)), + $(v).attr(identifiers.schemas.literal).call($.fromValue(value)), ); result.pipes = [ - $(v.placeholder) + $(v) .attr(identifiers.schemas.tuple) .call($.array(...tupleElements)), ]; @@ -52,7 +50,7 @@ export const tupleToAst = ({ return pipesToAst({ pipes: schemaPipes.pipes, plugin }); }); result.pipes = [ - $(v.placeholder) + $(v) .attr(identifiers.schemas.tuple) .call($.array(...tupleElements)), ]; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/undefined.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/undefined.ts index bd729b699d..9d7a9f41da 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/undefined.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/undefined.ts @@ -14,8 +14,6 @@ export const undefinedToAst = ({ resource: 'valibot.v', }); - const expression = $(v.placeholder) - .attr(identifiers.schemas.undefined) - .call(); + const expression = $(v).attr(identifiers.schemas.undefined).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/unknown.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/unknown.ts index ae147c96d4..c8765bcf26 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/unknown.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/unknown.ts @@ -14,6 +14,6 @@ export const unknownToAst = ({ resource: 'valibot.v', }); - const expression = $(v.placeholder).attr(identifiers.schemas.unknown).call(); + const expression = $(v).attr(identifiers.schemas.unknown).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/void.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/void.ts index 539687ab9c..375a020a44 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/void.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/void.ts @@ -14,6 +14,6 @@ export const voidToAst = ({ resource: 'valibot.v', }); - const expression = $(v.placeholder).attr(identifiers.schemas.void).call(); + const expression = $(v).attr(identifiers.schemas.void).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/api.ts b/packages/openapi-ts/src/plugins/zod/mini/api.ts index d1245f0e6c..41755b8c54 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/api.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/api.ts @@ -7,11 +7,7 @@ import type { ValidatorResolverArgs } from '../types'; const defaultValidatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => - $(schema.placeholder) - .attr(identifiers.parseAsync) - .call('data') - .await() - .return(); + $(schema).attr(identifiers.parseAsync).call('data').await().return(); export const createRequestValidatorMini = ({ operation, diff --git a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts index 2df4a9ec03..b588e9374d 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts @@ -48,11 +48,11 @@ export const irSchemaToAst = ({ }; const refSymbol = plugin.referenceSymbol(query); if (plugin.isSymbolRegistered(query)) { - ast.expression = $(refSymbol.placeholder); + ast.expression = $(refSymbol); } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.lazy) - .call($.func().returns('any').do($(refSymbol.placeholder).return())); + .call($.func().returns('any').do($(refSymbol).return())); ast.hasLazyExpression = true; state.hasLazyExpression.value = true; } @@ -69,7 +69,7 @@ export const irSchemaToAst = ({ ast.expression = ast.expression .attr(identifiers.register) .call( - $(z.placeholder).attr(identifiers.globalRegistry), + $(z).attr(identifiers.globalRegistry), $.object() .pretty() .prop('description', $.literal(schema.description)), @@ -99,18 +99,18 @@ export const irSchemaToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.intersection) .call(...itemSchemas.map((schema) => schema.expression)); } else { ast.expression = itemSchemas[0]!.expression; itemSchemas.slice(1).forEach((schema) => { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.intersection) .call( ast.expression, schema.hasLazyExpression - ? $(z.placeholder) + ? $(z) .attr(identifiers.lazy) .call($.func().do(schema.expression.return())) : schema.expression, @@ -118,7 +118,7 @@ export const irSchemaToAst = ({ }); } } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.union) .call( $.array() @@ -143,21 +143,17 @@ export const irSchemaToAst = ({ if (ast.expression) { if (schema.accessScope === 'read') { - ast.expression = $(z.placeholder) - .attr(identifiers.readonly) - .call(ast.expression); + ast.expression = $(z).attr(identifiers.readonly).call(ast.expression); } if (optional) { - ast.expression = $(z.placeholder) - .attr(identifiers.optional) - .call(ast.expression); + ast.expression = $(z).attr(identifiers.optional).call(ast.expression); ast.typeName = identifiers.ZodMiniOptional; } if (schema.default !== undefined) { const isBigInt = schema.type === 'integer' && schema.format === 'int64'; - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers._default) .call( ast.expression, @@ -183,7 +179,6 @@ const handleComponent = ({ const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -199,8 +194,6 @@ const handleComponent = ({ }); const typeInferSymbol = plugin.config.definitions.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts index c5a04d8bf3..6fe307e978 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts @@ -22,7 +22,7 @@ export const arrayToAst = ({ const result: Partial> = {}; - const functionName = $(z.placeholder).attr(identifiers.array); + const functionName = $(z).attr(identifiers.array); if (!schema.items) { result.expression = functionName.call( @@ -66,13 +66,13 @@ export const arrayToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - intersectionExpression = $(z.placeholder) + intersectionExpression = $(z) .attr(identifiers.intersection) .call(...itemExpressions); } else { intersectionExpression = itemExpressions[0]!; for (let i = 1; i < itemExpressions.length; i++) { - intersectionExpression = $(z.placeholder) + intersectionExpression = $(z) .attr(identifiers.intersection) .call(intersectionExpression, itemExpressions[i]); } @@ -80,10 +80,10 @@ export const arrayToAst = ({ result.expression = functionName.call(intersectionExpression); } else { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.array) .call( - $(z.placeholder) + $(z) .attr(identifiers.union) .call($.array(...itemExpressions)), ); @@ -95,24 +95,18 @@ export const arrayToAst = ({ if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.length) - .call($.fromValue(schema.minItems)), + $(z).attr(identifiers.length).call($.fromValue(schema.minItems)), ); } else { if (schema.minItems !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.minLength) - .call($.fromValue(schema.minItems)), + $(z).attr(identifiers.minLength).call($.fromValue(schema.minItems)), ); } if (schema.maxItems !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.maxLength) - .call($.fromValue(schema.maxItems)), + $(z).attr(identifiers.maxLength).call($.fromValue(schema.maxItems)), ); } } diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/boolean.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/boolean.ts index 43a4ec1760..065f6f7bdb 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/boolean.ts @@ -19,14 +19,12 @@ export const booleanToAst = ({ }); if (typeof schema.const === 'boolean') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); result.expression = chain; return result as Omit; } - chain = $(z.placeholder).attr(identifiers.boolean).call(); + chain = $(z).attr(identifiers.boolean).call(); result.expression = chain; return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/enum.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/enum.ts index 49642b944f..aabcd77afe 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/enum.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/enum.ts @@ -30,24 +30,18 @@ export const enumToAst = ({ if (item.type === 'string' && typeof item.const === 'string') { const literal = $.literal(item.const); enumMembers.push(literal); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if ( (item.type === 'number' || item.type === 'integer') && typeof item.const === 'number' ) { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'boolean' && typeof item.const === 'boolean') { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'null' || item.const === null) { isNullable = true; } @@ -65,22 +59,20 @@ export const enumToAst = ({ // Use z.enum() for pure string enums, z.union() for mixed or non-string types if (allStrings && enumMembers.length > 0) { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.enum) .call($.array(...enumMembers)); } else if (literalMembers.length === 1) { // For single-member unions, use the member directly instead of wrapping in z.union() result.expression = literalMembers[0]!; } else { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.union) .call($.array(...literalMembers)); } if (isNullable) { - result.expression = $(z.placeholder) - .attr(identifiers.nullable) - .call(result.expression); + result.expression = $(z).attr(identifiers.nullable).call(result.expression); } return result as Omit; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/never.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/never.ts index ff41a62499..05bda512fd 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/never.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/never.ts @@ -14,6 +14,6 @@ export const neverToAst = ({ resource: 'zod.z', }); const result: Partial> = {}; - result.expression = $(z.placeholder).attr(identifiers.never).call(); + result.expression = $(z).attr(identifiers.never).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/null.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/null.ts index ab3ff037d5..52d1a877d9 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/null.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/null.ts @@ -14,6 +14,6 @@ export const nullToAst = ({ resource: 'zod.z', }); const result: Partial> = {}; - result.expression = $(z.placeholder).attr(identifiers.null).call(); + result.expression = $(z).attr(identifiers.null).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts index 856ab802eb..feee198665 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts @@ -22,31 +22,31 @@ export const numberToAst = ({ if (typeof schema.const === 'number') { // TODO: parser - handle bigint constants - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.literal) .call($.literal(schema.const)); return result as Omit; } result.expression = isBigInt - ? $(z.placeholder).attr(identifiers.coerce).attr(identifiers.bigint).call() - : $(z.placeholder).attr(identifiers.number).call(); + ? $(z).attr(identifiers.coerce).attr(identifiers.bigint).call() + : $(z).attr(identifiers.number).call(); if (!isBigInt && schema.type === 'integer') { - result.expression = $(z.placeholder).attr(identifiers.int).call(); + result.expression = $(z).attr(identifiers.int).call(); } const checks: Array> = []; if (schema.exclusiveMinimum !== undefined) { checks.push( - $(z.placeholder) + $(z) .attr(identifiers.gt) .call(numberParameter({ isBigInt, value: schema.exclusiveMinimum })), ); } else if (schema.minimum !== undefined) { checks.push( - $(z.placeholder) + $(z) .attr(identifiers.gte) .call(numberParameter({ isBigInt, value: schema.minimum })), ); @@ -54,13 +54,13 @@ export const numberToAst = ({ if (schema.exclusiveMaximum !== undefined) { checks.push( - $(z.placeholder) + $(z) .attr(identifiers.lt) .call(numberParameter({ isBigInt, value: schema.exclusiveMaximum })), ); } else if (schema.maximum !== undefined) { checks.push( - $(z.placeholder) + $(z) .attr(identifiers.lte) .call(numberParameter({ isBigInt, value: schema.maximum })), ); diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts index a86d8639d9..da7f10c664 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts @@ -18,12 +18,12 @@ function defaultObjectBaseResolver({ }); if (additional) { - return $(z.placeholder) + return $(z) .attr(identifiers.record) - .call($(z.placeholder).attr(identifiers.string).call(), additional); + .call($(z).attr(identifiers.string).call(), additional); } - return $(z.placeholder).attr(identifiers.object).call(shape); + return $(z).attr(identifiers.object).call(shape); } export const objectToAst = ({ diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts index c1ca8c8b3d..9f6bd204d0 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts @@ -17,10 +17,7 @@ const defaultFormatResolver = ({ switch (schema.format) { case 'date': - return $(z.placeholder) - .attr(identifiers.iso) - .attr(identifiers.date) - .call(); + return $(z).attr(identifiers.iso).attr(identifiers.date).call(); case 'date-time': { const obj = $.object() .$if(plugin.config.dates.offset, (o) => @@ -29,26 +26,23 @@ const defaultFormatResolver = ({ .$if(plugin.config.dates.local, (o) => o.prop('local', $.literal(true)), ); - return $(z.placeholder) + return $(z) .attr(identifiers.iso) .attr(identifiers.datetime) .call(obj.hasProps() ? obj : undefined); } case 'email': - return $(z.placeholder).attr(identifiers.email).call(); + return $(z).attr(identifiers.email).call(); case 'ipv4': - return $(z.placeholder).attr(identifiers.ipv4).call(); + return $(z).attr(identifiers.ipv4).call(); case 'ipv6': - return $(z.placeholder).attr(identifiers.ipv6).call(); + return $(z).attr(identifiers.ipv6).call(); case 'time': - return $(z.placeholder) - .attr(identifiers.iso) - .attr(identifiers.time) - .call(); + return $(z).attr(identifiers.iso).attr(identifiers.time).call(); case 'uri': - return $(z.placeholder).attr(identifiers.url).call(); + return $(z).attr(identifiers.url).call(); case 'uuid': - return $(z.placeholder).attr(identifiers.uuid).call(); + return $(z).attr(identifiers.uuid).call(); default: return chain; } @@ -69,14 +63,12 @@ export const stringToAst = ({ }); if (typeof schema.const === 'string') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); result.expression = chain; return result as Omit; } - chain = $(z.placeholder).attr(identifiers.string).call(); + chain = $(z).attr(identifiers.string).call(); if (schema.format) { const args: FormatResolverArgs = { $, chain, plugin, schema }; @@ -89,32 +81,24 @@ export const stringToAst = ({ if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.length) - .call($.literal(schema.minLength)), + $(z).attr(identifiers.length).call($.literal(schema.minLength)), ); } else { if (schema.minLength !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.minLength) - .call($.literal(schema.minLength)), + $(z).attr(identifiers.minLength).call($.literal(schema.minLength)), ); } if (schema.maxLength !== undefined) { checks.push( - $(z.placeholder) - .attr(identifiers.maxLength) - .call($.literal(schema.maxLength)), + $(z).attr(identifiers.maxLength).call($.literal(schema.maxLength)), ); } } if (schema.pattern) { - checks.push( - $(z.placeholder).attr(identifiers.regex).call($.regexp(schema.pattern)), - ); + checks.push($(z).attr(identifiers.regex).call($.regexp(schema.pattern))); } if (checks.length) { diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/tuple.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/tuple.ts index 922b5592ca..35b684fcb4 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/tuple.ts @@ -22,9 +22,9 @@ export const tupleToAst = ({ if (schema.const && Array.isArray(schema.const)) { const tupleElements = schema.const.map((value) => - $(z.placeholder).attr(identifiers.literal).call($.fromValue(value)), + $(z).attr(identifiers.literal).call($.fromValue(value)), ); - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); return result as Omit; @@ -49,7 +49,7 @@ export const tupleToAst = ({ }); } - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/undefined.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/undefined.ts index 2648e32f45..f3b2726bed 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/undefined.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/undefined.ts @@ -14,6 +14,6 @@ export const undefinedToAst = ({ resource: 'zod.z', }); const result: Partial> = {}; - result.expression = $(z.placeholder).attr(identifiers.undefined).call(); + result.expression = $(z).attr(identifiers.undefined).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/unknown.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/unknown.ts index d620f87d76..a09101768a 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/unknown.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/unknown.ts @@ -14,6 +14,6 @@ export const unknownToAst = ({ resource: 'zod.z', }); const result: Partial> = {}; - result.expression = $(z.placeholder).attr(identifiers.unknown).call(); + result.expression = $(z).attr(identifiers.unknown).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/void.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/void.ts index 7fe90947e7..cd0ac49ae5 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/void.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/void.ts @@ -14,6 +14,6 @@ export const voidToAst = ({ resource: 'zod.z', }); const result: Partial> = {}; - result.expression = $(z.placeholder).attr(identifiers.void).call(); + result.expression = $(z).attr(identifiers.void).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/shared/export.ts b/packages/openapi-ts/src/plugins/zod/shared/export.ts index 23eb65ce8a..13ea8e5af2 100644 --- a/packages/openapi-ts/src/plugins/zod/shared/export.ts +++ b/packages/openapi-ts/src/plugins/zod/shared/export.ts @@ -26,24 +26,20 @@ export const exportAst = ({ resource: 'zod.z', }); - const statement = $.const(symbol.placeholder) - .export(symbol.exported) + const statement = $.const(symbol) + .export() .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v), ) - .$if(ast.typeName, (c, v) => c.type($.type(z.placeholder).attr(v))) + .$if(ast.typeName, (c, v) => c.type($.type(z).attr(v))) .assign(ast.expression); - plugin.setSymbolValue(symbol, statement); + plugin.addNode(statement); if (typeInferSymbol) { const inferType = $.type - .alias(typeInferSymbol.placeholder) - .export(typeInferSymbol.exported) - .type( - $.type(z.placeholder) - .attr(identifiers.infer) - .generic($(symbol.placeholder).typeofType()), - ); - plugin.setSymbolValue(typeInferSymbol, inferType); + .alias(typeInferSymbol) + .export() + .type($.type(z).attr(identifiers.infer).generic($(symbol).typeofType())); + plugin.addNode(inferType); } }; diff --git a/packages/openapi-ts/src/plugins/zod/shared/operation.ts b/packages/openapi-ts/src/plugins/zod/shared/operation.ts index 1be803f1c9..4a04978856 100644 --- a/packages/openapi-ts/src/plugins/zod/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/zod/shared/operation.ts @@ -117,7 +117,6 @@ export const irOperationToAst = ({ const ast = getAst(schemaData, state.path.value); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -134,8 +133,6 @@ export const irOperationToAst = ({ }); const typeInferSymbol = plugin.config.requests.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, @@ -169,7 +166,6 @@ export const irOperationToAst = ({ const path = [...state.path.value, 'responses']; const ast = getAst(response, path); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path, @@ -186,8 +182,6 @@ export const irOperationToAst = ({ }); const typeInferSymbol = plugin.config.responses.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path, diff --git a/packages/openapi-ts/src/plugins/zod/shared/webhook.ts b/packages/openapi-ts/src/plugins/zod/shared/webhook.ts index eb17392637..86e581cd92 100644 --- a/packages/openapi-ts/src/plugins/zod/shared/webhook.ts +++ b/packages/openapi-ts/src/plugins/zod/shared/webhook.ts @@ -116,7 +116,6 @@ export const irWebhookToAst = ({ const ast = getAst(schemaData, state.path.value); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -133,8 +132,6 @@ export const irWebhookToAst = ({ }); const typeInferSymbol = plugin.config.webhooks.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/zod/v3/api.ts b/packages/openapi-ts/src/plugins/zod/v3/api.ts index 5ce221165e..5da1117b57 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/api.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/api.ts @@ -7,11 +7,7 @@ import type { ValidatorResolverArgs } from '../types'; const defaultValidatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => - $(schema.placeholder) - .attr(identifiers.parseAsync) - .call('data') - .await() - .return(); + $(schema).attr(identifiers.parseAsync).call('data').await().return(); export const createRequestValidatorV3 = ({ operation, diff --git a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts index 03ef0ac0a0..ab5838c41c 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts @@ -48,11 +48,11 @@ export const irSchemaToAst = ({ }; const refSymbol = plugin.referenceSymbol(query); if (plugin.isSymbolRegistered(query)) { - ast.expression = $(refSymbol.placeholder); + ast.expression = $(refSymbol); } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.lazy) - .call($.func().do($(refSymbol.placeholder).return())); + .call($.func().do($(refSymbol).return())); ast.hasLazyExpression = true; state.hasLazyExpression.value = true; } @@ -95,7 +95,7 @@ export const irSchemaToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.intersection) .call(...itemTypes); } else { @@ -105,7 +105,7 @@ export const irSchemaToAst = ({ }); } } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.union) .call( $.array() @@ -171,7 +171,6 @@ const handleComponent = ({ const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -187,8 +186,6 @@ const handleComponent = ({ }); const typeInferSymbol = plugin.config.definitions.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/array.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/array.ts index 4967128b6d..c5a4f25552 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/array.ts @@ -22,7 +22,7 @@ export const arrayToAst = ({ resource: 'zod.z', }); - const functionName = $(z.placeholder).attr(identifiers.array); + const functionName = $(z).attr(identifiers.array); let arrayExpression: ReturnType | undefined; let hasLazyExpression = false; @@ -69,7 +69,7 @@ export const arrayToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - intersectionExpression = $(z.placeholder) + intersectionExpression = $(z) .attr(identifiers.intersection) .call(...itemExpressions); } else { @@ -83,10 +83,10 @@ export const arrayToAst = ({ arrayExpression = functionName.call(intersectionExpression); } else { - arrayExpression = $(z.placeholder) + arrayExpression = $(z) .attr(identifiers.array) .call( - $(z.placeholder) + $(z) .attr(identifiers.union) .call($.array(...itemExpressions)), ); diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/boolean.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/boolean.ts index 5706720392..10a649541a 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/boolean.ts @@ -18,12 +18,10 @@ export const booleanToAst = ({ }); if (typeof schema.const === 'boolean') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); return chain; } - chain = $(z.placeholder).attr(identifiers.boolean).call(); + chain = $(z).attr(identifiers.boolean).call(); return chain; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/enum.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/enum.ts index 67534af348..76d4dafb95 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/enum.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/enum.ts @@ -28,24 +28,18 @@ export const enumToAst = ({ if (item.type === 'string' && typeof item.const === 'string') { const literal = $.literal(item.const); enumMembers.push(literal); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if ( (item.type === 'number' || item.type === 'integer') && typeof item.const === 'number' ) { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'boolean' && typeof item.const === 'boolean') { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'null' || item.const === null) { isNullable = true; } @@ -64,14 +58,14 @@ export const enumToAst = ({ // Use z.enum() for pure string enums, z.union() for mixed or non-string types let enumExpression: ReturnType; if (allStrings && enumMembers.length > 0) { - enumExpression = $(z.placeholder) + enumExpression = $(z) .attr(identifiers.enum) .call($.array(...enumMembers)); } else if (literalMembers.length === 1) { // For single-member unions, use the member directly instead of wrapping in z.union() enumExpression = literalMembers[0]!; } else { - enumExpression = $(z.placeholder) + enumExpression = $(z) .attr(identifiers.union) .call($.array(...literalMembers)); } diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/never.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/never.ts index 1d9fc68fad..0e71d32f61 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/never.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/never.ts @@ -13,6 +13,6 @@ export const neverToAst = ({ category: 'external', resource: 'zod.z', }); - const expression = $(z.placeholder).attr(identifiers.never).call(); + const expression = $(z).attr(identifiers.never).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/null.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/null.ts index 7a72f476e0..91969aebf8 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/null.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/null.ts @@ -13,6 +13,6 @@ export const nullToAst = ({ category: 'external', resource: 'zod.z', }); - const expression = $(z.placeholder).attr(identifiers.null).call(); + const expression = $(z).attr(identifiers.null).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts index 75ce22ec7e..1e9bb51982 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts @@ -20,15 +20,15 @@ export const numberToAst = ({ if (typeof schema.const === 'number') { // TODO: parser - handle bigint constants - const expression = $(z.placeholder) + const expression = $(z) .attr(identifiers.literal) .call($.literal(schema.const)); return expression; } let numberExpression = isBigInt - ? $(z.placeholder).attr(identifiers.coerce).attr(identifiers.bigint).call() - : $(z.placeholder).attr(identifiers.number).call(); + ? $(z).attr(identifiers.coerce).attr(identifiers.bigint).call() + : $(z).attr(identifiers.number).call(); if (!isBigInt && schema.type === 'integer') { numberExpression = numberExpression.attr(identifiers.int).call(); diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts index 8d78122e0f..8be0fb3e5c 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts @@ -18,10 +18,10 @@ function defaultObjectBaseResolver({ }); if (additional) { - return $(z.placeholder).attr(identifiers.record).call(additional); + return $(z).attr(identifiers.record).call(additional); } - return $(z.placeholder).attr(identifiers.object).call(shape); + return $(z).attr(identifiers.object).call(shape); } export const objectToAst = ({ diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts index 9ae100e2f1..d147b44453 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts @@ -55,13 +55,11 @@ export const stringToAst = ({ }); if (typeof schema.const === 'string') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); return chain; } - chain = $(z.placeholder).attr(identifiers.string).call(); + chain = $(z).attr(identifiers.string).call(); if (schema.format) { const args: FormatResolverArgs = { $, chain, plugin, schema }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/tuple.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/tuple.ts index 72fdf4e481..b6c1856312 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/tuple.ts @@ -24,9 +24,9 @@ export const tupleToAst = ({ if (schema.const && Array.isArray(schema.const)) { const tupleElements = schema.const.map((value) => - $(z.placeholder).attr(identifiers.literal).call($.fromValue(value)), + $(z).attr(identifiers.literal).call($.fromValue(value)), ); - const expression = $(z.placeholder) + const expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); return { @@ -54,7 +54,7 @@ export const tupleToAst = ({ }); } - const expression = $(z.placeholder) + const expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/undefined.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/undefined.ts index 60545a2e09..207ec6d3c8 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/undefined.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/undefined.ts @@ -13,6 +13,6 @@ export const undefinedToAst = ({ category: 'external', resource: 'zod.z', }); - const expression = $(z.placeholder).attr(identifiers.undefined).call(); + const expression = $(z).attr(identifiers.undefined).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/unknown.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/unknown.ts index 9afc011c28..8327f2df18 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/unknown.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/unknown.ts @@ -13,6 +13,6 @@ export const unknownToAst = ({ category: 'external', resource: 'zod.z', }); - const expression = $(z.placeholder).attr(identifiers.unknown).call(); + const expression = $(z).attr(identifiers.unknown).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/void.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/void.ts index 79e069cab4..9f6d53c806 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/void.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/void.ts @@ -13,6 +13,6 @@ export const voidToAst = ({ category: 'external', resource: 'zod.z', }); - const expression = $(z.placeholder).attr(identifiers.void).call(); + const expression = $(z).attr(identifiers.void).call(); return expression; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/api.ts b/packages/openapi-ts/src/plugins/zod/v4/api.ts index 9991fd0d09..8e82ee81f3 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/api.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/api.ts @@ -7,11 +7,7 @@ import type { ValidatorResolverArgs } from '../types'; const defaultValidatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => - $(schema.placeholder) - .attr(identifiers.parseAsync) - .call('data') - .await() - .return(); + $(schema).attr(identifiers.parseAsync).call('data').await().return(); export const createRequestValidatorV4 = ({ operation, diff --git a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts index db46ae0da6..9c0c45e964 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts @@ -48,11 +48,11 @@ export const irSchemaToAst = ({ }; const refSymbol = plugin.referenceSymbol(query); if (plugin.isSymbolRegistered(query)) { - ast.expression = $(refSymbol.placeholder); + ast.expression = $(refSymbol); } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.lazy) - .call($.func().returns('any').do($(refSymbol.placeholder).return())); + .call($.func().returns('any').do($(refSymbol).return())); ast.hasLazyExpression = true; state.hasLazyExpression.value = true; } @@ -69,7 +69,7 @@ export const irSchemaToAst = ({ ast.expression = ast.expression .attr(identifiers.register) .call( - $(z.placeholder).attr(identifiers.globalRegistry), + $(z).attr(identifiers.globalRegistry), $.object() .pretty() .prop('description', $.literal(schema.description)), @@ -99,7 +99,7 @@ export const irSchemaToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.intersection) .call(...itemSchemas.map((schema) => schema.expression)); } else { @@ -109,7 +109,7 @@ export const irSchemaToAst = ({ .expression!.attr(identifiers.and) .call( schema.hasLazyExpression - ? $(z.placeholder) + ? $(z) .attr(identifiers.lazy) .call($.func().do(schema.expression.return())) : schema.expression, @@ -117,7 +117,7 @@ export const irSchemaToAst = ({ }); } } else { - ast.expression = $(z.placeholder) + ast.expression = $(z) .attr(identifiers.union) .call( $.array() @@ -150,9 +150,7 @@ export const irSchemaToAst = ({ } if (optional) { - ast.expression = $(z.placeholder) - .attr(identifiers.optional) - .call(ast.expression); + ast.expression = $(z).attr(identifiers.optional).call(ast.expression); ast.typeName = identifiers.ZodOptional; } @@ -181,7 +179,6 @@ const handleComponent = ({ const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); const symbol = plugin.registerSymbol({ - exported: true, meta: { category: 'schema', path: state.path.value, @@ -197,8 +194,6 @@ const handleComponent = ({ }); const typeInferSymbol = plugin.config.definitions.types.infer.enabled ? plugin.registerSymbol({ - exported: true, - kind: 'type', meta: { category: 'type', path: state.path.value, diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/array.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/array.ts index d4652b9a67..c364d7213c 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/array.ts @@ -22,7 +22,7 @@ export const arrayToAst = ({ resource: 'zod.z', }); - const functionName = $(z.placeholder).attr(identifiers.array); + const functionName = $(z).attr(identifiers.array); if (!schema.items) { result.expression = functionName.call( @@ -66,7 +66,7 @@ export const arrayToAst = ({ firstSchema.logicalOperator === 'or' || (firstSchema.type && firstSchema.type !== 'object') ) { - intersectionExpression = $(z.placeholder) + intersectionExpression = $(z) .attr(identifiers.intersection) .call(...itemExpressions); } else { @@ -80,10 +80,10 @@ export const arrayToAst = ({ result.expression = functionName.call(intersectionExpression); } else { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.array) .call( - $(z.placeholder) + $(z) .attr(identifiers.union) .call($.array(...itemExpressions)), ); diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/boolean.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/boolean.ts index 43a4ec1760..065f6f7bdb 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/boolean.ts @@ -19,14 +19,12 @@ export const booleanToAst = ({ }); if (typeof schema.const === 'boolean') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); result.expression = chain; return result as Omit; } - chain = $(z.placeholder).attr(identifiers.boolean).call(); + chain = $(z).attr(identifiers.boolean).call(); result.expression = chain; return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/enum.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/enum.ts index 729fb23d08..4abee084a2 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/enum.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/enum.ts @@ -30,24 +30,18 @@ export const enumToAst = ({ if (item.type === 'string' && typeof item.const === 'string') { const literal = $.literal(item.const); enumMembers.push(literal); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if ( (item.type === 'number' || item.type === 'integer') && typeof item.const === 'number' ) { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'boolean' && typeof item.const === 'boolean') { allStrings = false; const literal = $.literal(item.const); - literalMembers.push( - $(z.placeholder).attr(identifiers.literal).call(literal), - ); + literalMembers.push($(z).attr(identifiers.literal).call(literal)); } else if (item.type === 'null' || item.const === null) { isNullable = true; } @@ -65,22 +59,20 @@ export const enumToAst = ({ // Use z.enum() for pure string enums, z.union() for mixed or non-string types if (allStrings && enumMembers.length > 0) { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.enum) .call($.array(...enumMembers)); } else if (literalMembers.length === 1) { // For single-member unions, use the member directly instead of wrapping in z.union() result.expression = literalMembers[0]!; } else { - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.union) .call($.array(...literalMembers)); } if (isNullable) { - result.expression = $(z.placeholder) - .attr(identifiers.nullable) - .call(result.expression); + result.expression = $(z).attr(identifiers.nullable).call(result.expression); } return result as Omit; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/never.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/never.ts index 855b2747c4..3c684d4641 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/never.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/never.ts @@ -14,6 +14,6 @@ export const neverToAst = ({ category: 'external', resource: 'zod.z', }); - result.expression = $(z.placeholder).attr(identifiers.never).call(); + result.expression = $(z).attr(identifiers.never).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/null.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/null.ts index f4f11bec3f..79785a5b42 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/null.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/null.ts @@ -14,6 +14,6 @@ export const nullToAst = ({ category: 'external', resource: 'zod.z', }); - result.expression = $(z.placeholder).attr(identifiers.null).call(); + result.expression = $(z).attr(identifiers.null).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts index 738e16bb72..0ff204b324 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts @@ -22,18 +22,18 @@ export const numberToAst = ({ if (typeof schema.const === 'number') { // TODO: parser - handle bigint constants - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.literal) .call($.literal(schema.const)); return result as Omit; } result.expression = isBigInt - ? $(z.placeholder).attr(identifiers.coerce).attr(identifiers.bigint).call() - : $(z.placeholder).attr(identifiers.number).call(); + ? $(z).attr(identifiers.coerce).attr(identifiers.bigint).call() + : $(z).attr(identifiers.number).call(); if (!isBigInt && schema.type === 'integer') { - result.expression = $(z.placeholder).attr(identifiers.int).call(); + result.expression = $(z).attr(identifiers.int).call(); } if (schema.exclusiveMinimum !== undefined) { diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts index 58bf30b083..1290d4fd64 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts @@ -18,12 +18,12 @@ function defaultObjectBaseResolver({ }); if (additional) { - return $(z.placeholder) + return $(z) .attr(identifiers.record) - .call($(z.placeholder).attr(identifiers.string).call(), additional); + .call($(z).attr(identifiers.string).call(), additional); } - return $(z.placeholder).attr(identifiers.object).call(shape); + return $(z).attr(identifiers.object).call(shape); } export const objectToAst = ({ diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts index 525a44034e..84eda4bc11 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts @@ -17,10 +17,7 @@ const defaultFormatResolver = ({ switch (schema.format) { case 'date': - return $(z.placeholder) - .attr(identifiers.iso) - .attr(identifiers.date) - .call(); + return $(z).attr(identifiers.iso).attr(identifiers.date).call(); case 'date-time': { const obj = $.object() .$if(plugin.config.dates.offset, (o) => @@ -29,26 +26,23 @@ const defaultFormatResolver = ({ .$if(plugin.config.dates.local, (o) => o.prop('local', $.literal(true)), ); - return $(z.placeholder) + return $(z) .attr(identifiers.iso) .attr(identifiers.datetime) .call(obj.hasProps() ? obj : undefined); } case 'email': - return $(z.placeholder).attr(identifiers.email).call(); + return $(z).attr(identifiers.email).call(); case 'ipv4': - return $(z.placeholder).attr(identifiers.ipv4).call(); + return $(z).attr(identifiers.ipv4).call(); case 'ipv6': - return $(z.placeholder).attr(identifiers.ipv6).call(); + return $(z).attr(identifiers.ipv6).call(); case 'time': - return $(z.placeholder) - .attr(identifiers.iso) - .attr(identifiers.time) - .call(); + return $(z).attr(identifiers.iso).attr(identifiers.time).call(); case 'uri': - return $(z.placeholder).attr(identifiers.url).call(); + return $(z).attr(identifiers.url).call(); case 'uuid': - return $(z.placeholder).attr(identifiers.uuid).call(); + return $(z).attr(identifiers.uuid).call(); default: return chain; } @@ -69,14 +63,12 @@ export const stringToAst = ({ }); if (typeof schema.const === 'string') { - chain = $(z.placeholder) - .attr(identifiers.literal) - .call($.literal(schema.const)); + chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); result.expression = chain; return result as Omit; } - chain = $(z.placeholder).attr(identifiers.string).call(); + chain = $(z).attr(identifiers.string).call(); if (schema.format) { const args: FormatResolverArgs = { $, chain, plugin, schema }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/tuple.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/tuple.ts index b653411067..732db6e024 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/tuple.ts @@ -22,9 +22,9 @@ export const tupleToAst = ({ if (schema.const && Array.isArray(schema.const)) { const tupleElements = schema.const.map((value) => - $(z.placeholder).attr(identifiers.literal).call($.fromValue(value)), + $(z).attr(identifiers.literal).call($.fromValue(value)), ); - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); return result as Omit; @@ -49,7 +49,7 @@ export const tupleToAst = ({ }); } - result.expression = $(z.placeholder) + result.expression = $(z) .attr(identifiers.tuple) .call($.array(...tupleElements)); diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/undefined.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/undefined.ts index a64c94b3c2..aa5e63b40d 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/undefined.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/undefined.ts @@ -14,6 +14,6 @@ export const undefinedToAst = ({ category: 'external', resource: 'zod.z', }); - result.expression = $(z.placeholder).attr(identifiers.undefined).call(); + result.expression = $(z).attr(identifiers.undefined).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/unknown.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/unknown.ts index d4491f70bf..a0294359cf 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/unknown.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/unknown.ts @@ -14,6 +14,6 @@ export const unknownToAst = ({ category: 'external', resource: 'zod.z', }); - result.expression = $(z.placeholder).attr(identifiers.unknown).call(); + result.expression = $(z).attr(identifiers.unknown).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/void.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/void.ts index fce6b07ec8..5ee35e976b 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/void.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/void.ts @@ -14,6 +14,6 @@ export const voidToAst = ({ category: 'external', resource: 'zod.z', }); - result.expression = $(z.placeholder).attr(identifiers.void).call(); + result.expression = $(z).attr(identifiers.void).call(); return result as Omit; }; diff --git a/packages/openapi-ts/src/ts-dsl/base.ts b/packages/openapi-ts/src/ts-dsl/base.ts index 6290cede7c..e25c269e33 100644 --- a/packages/openapi-ts/src/ts-dsl/base.ts +++ b/packages/openapi-ts/src/ts-dsl/base.ts @@ -1,13 +1,27 @@ +// TODO: symbol should be protected, but needs to be public to satisfy types +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import { Symbol } from '@hey-api/codegen-core'; +import { debug } from '@hey-api/codegen-core'; import ts from 'typescript'; export type MaybeArray = T | ReadonlyArray; -export interface ITsDsl { +export interface ITsDsl extends Node { + /** Render this node into a concrete TypeScript AST. */ $render(): T; } +export const tsDslBrand = globalThis.Symbol('ts-dsl'); + export abstract class TsDsl implements ITsDsl { - abstract $render(): T; + /** Render this node into a concrete TypeScript AST. */ + protected abstract _render(): T; + + readonly '~brand': symbol = tsDslBrand; + + parent?: Node; + + symbol?: Symbol; /** Conditionally applies a callback to this builder. */ $if( @@ -84,6 +98,19 @@ export abstract class TsDsl implements ITsDsl { return this; } + /** Render this node into a concrete TypeScript AST. */ + $render(): T { + if (!this.parent) { + this._validate(); + } + return this._render(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + analyze(_: AnalysisContext): void { + // noop + } + protected $maybeId( expr: T, ): T extends string ? ts.Identifier : T { @@ -96,8 +123,11 @@ export abstract class TsDsl implements ITsDsl { if (value === undefined) { return undefined as NodeOfMaybe; } + if (value instanceof Symbol) { + return this.$maybeId(value.finalName) as NodeOfMaybe; + } if (typeof value === 'string') { - return ts.factory.createIdentifier(value) as NodeOfMaybe; + return this.$maybeId(value) as NodeOfMaybe; } if (value instanceof Array) { return value.map((item) => this.unwrap(item)) as NodeOfMaybe; @@ -112,6 +142,12 @@ export abstract class TsDsl implements ITsDsl { if (value === undefined) { return undefined as TypeOfMaybe; } + if (value instanceof Symbol) { + return ts.factory.createTypeReferenceNode( + value.finalName, + args, + ) as TypeOfMaybe; + } if (typeof value === 'string') { return ts.factory.createTypeReferenceNode(value, args) as TypeOfMaybe; } @@ -132,10 +168,22 @@ export abstract class TsDsl implements ITsDsl { return this.unwrap(value as any) as TypeOfMaybe; } + /** Unwraps nested nodes into raw TypeScript AST. */ protected unwrap(value: I): I extends TsDsl ? N : I { - return ( - value instanceof TsDsl ? value.$render() : value - ) as I extends TsDsl ? N : I; + return (isTsDsl(value) ? value._render() : value) as I extends TsDsl< + infer N + > + ? N + : I; + } + + /** Validate invariants. */ + protected _validate(): void { + if (this.symbol && this.symbol.canonical !== this.symbol) { + const message = `${this.constructor.name}: node is holding a non-canonical (stub) symbol`; + debug(message, 'dsl'); + throw new Error(message); + } } } @@ -186,3 +234,9 @@ type TypeOf = : I extends ts.TypeNode ? I : never; + +export function isTsDsl(value: unknown): value is TsDsl { + if (!value || typeof value !== 'object' || Array.isArray(value)) return false; + const obj = value as { '~brand'?: unknown }; + return obj['~brand'] === tsDslBrand && Object.hasOwn(obj, '~brand'); +} diff --git a/packages/openapi-ts/src/ts-dsl/decl/class.ts b/packages/openapi-ts/src/ts-dsl/decl/class.ts index 1659d422e7..a20b717c0f 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/class.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/class.ts @@ -1,52 +1,62 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; import { NewlineTsDsl } from '../layout/newline'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { DocMixin } from '../mixins/doc'; -import { - AbstractMixin, - createModifierAccessor, - DefaultMixin, - ExportMixin, -} from '../mixins/modifiers'; +import { AbstractMixin, DefaultMixin, ExportMixin } from '../mixins/modifiers'; import { TypeParamsMixin } from '../mixins/type-params'; import { FieldTsDsl } from './field'; import { InitTsDsl } from './init'; import { MethodTsDsl } from './method'; -export class ClassTsDsl extends TsDsl { - protected heritageClauses: Array = []; - protected body: Array> = []; - protected modifiers = createModifierAccessor(this); - protected name: string; +type Base = Symbol | string; +type Name = Symbol | string; +type Body = Array>; - constructor(name: string) { +const Mixed = AbstractMixin( + DecoratorMixin( + DefaultMixin( + DocMixin(ExportMixin(TypeParamsMixin(TsDsl))), + ), + ), +); + +export class ClassTsDsl extends Mixed { + protected baseClass?: Base; + protected body: Body = []; + protected name: Name; + + constructor(name: Name) { super(); this.name = name; + if (isSymbol(name)) { + name.setKind('class'); + name.setNode(this); + } + } + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.baseClass)) ctx.addDependency(this.baseClass); + if (isSymbol(this.name)) ctx.addDependency(this.name); + for (const item of this.body) { + if (isTsDsl(item)) item.analyze(ctx); + } } /** Adds one or more class members (fields, methods, etc.). */ - do(...items: ReadonlyArray>): this { - // @ts-expect-error --- IGNORE --- + do(...items: Body): this { this.body.push(...items); return this; } - /** Adds a base class to extend from. */ - extends(base?: string | ts.Expression | false | null): this { - if (!base) return this; - this.heritageClauses.push( - ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ - ts.factory.createExpressionWithTypeArguments( - this.$maybeId(base), - undefined, - ), - ]), - ); + /** Records a base class to extend from. */ + extends(base?: Base): this { + this.baseClass = base; return this; } @@ -77,32 +87,28 @@ export class ClassTsDsl extends TsDsl { return this; } - /** Builds the `ClassDeclaration` node. */ - $render(): ts.ClassDeclaration { + protected override _render() { const body = this.$node(this.body) as ReadonlyArray; return ts.factory.createClassDeclaration( - [...this.$decorators(), ...this.modifiers.list()], - this.name, + [...this.$decorators(), ...this.modifiers], + // @ts-expect-error need to improve types + this.$node(this.name), this.$generics(), - this.heritageClauses, + this._renderHeritage(), body, ); } -} -export interface ClassTsDsl - extends AbstractMixin, - DecoratorMixin, - DefaultMixin, - DocMixin, - ExportMixin, - TypeParamsMixin {} -mixin( - ClassTsDsl, - AbstractMixin, - DecoratorMixin, - DefaultMixin, - DocMixin, - ExportMixin, - TypeParamsMixin, -); + /** Builds heritage clauses (extends). */ + private _renderHeritage(): ReadonlyArray { + if (!this.baseClass) return []; + return [ + ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ + ts.factory.createExpressionWithTypeArguments( + this.$node(this.baseClass), + undefined, + ), + ]), + ]; + } +} diff --git a/packages/openapi-ts/src/ts-dsl/decl/decorator.ts b/packages/openapi-ts/src/ts-dsl/decl/decorator.ts index 4a77c30e84..be0c1390ae 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/decorator.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/decorator.ts @@ -1,16 +1,20 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { ArgsMixin } from '../mixins/args'; -export class DecoratorTsDsl extends TsDsl { - protected name: string | ts.Expression; +export type DecoratorName = Symbol | string | MaybeTsDsl; + +const Mixed = ArgsMixin(TsDsl); + +export class DecoratorTsDsl extends Mixed { + protected name: DecoratorName; constructor( - name: string | ts.Expression, + name: DecoratorName, ...args: ReadonlyArray> ) { super(); @@ -18,19 +22,22 @@ export class DecoratorTsDsl extends TsDsl { this.args(...args); } - $render(): ts.Decorator { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.name)) { + ctx.addDependency(this.name); + } else if (isTsDsl(this.name)) { + this.name.analyze(ctx); + } + } + + protected override _render() { + const target = this.$node(this.name); const args = this.$args(); return ts.factory.createDecorator( args.length - ? ts.factory.createCallExpression( - this.$maybeId(this.name), - undefined, - args, - ) - : this.$maybeId(this.name), + ? ts.factory.createCallExpression(target, undefined, args) + : target, ); } } - -export interface DecoratorTsDsl extends ArgsMixin {} -mixin(DecoratorTsDsl, ArgsMixin); diff --git a/packages/openapi-ts/src/ts-dsl/decl/enum.ts b/packages/openapi-ts/src/ts-dsl/decl/enum.ts index f53300966e..69661bc025 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/enum.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/enum.ts @@ -1,31 +1,41 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DocMixin } from '../mixins/doc'; -import { - ConstMixin, - createModifierAccessor, - ExportMixin, -} from '../mixins/modifiers'; +import { ConstMixin, ExportMixin } from '../mixins/modifiers'; import { EnumMemberTsDsl } from './member'; +export type EnumName = Symbol | string; type Value = string | number | MaybeTsDsl; type ValueFn = Value | ((m: EnumMemberTsDsl) => void); -export class EnumTsDsl extends TsDsl { +const Mixed = ConstMixin(DocMixin(ExportMixin(TsDsl))); + +export class EnumTsDsl extends Mixed { private _members: Array = []; - private _name: string | ts.Identifier; - protected modifiers = createModifierAccessor(this); + private _name: EnumName; - constructor(name: string | ts.Identifier, fn?: (e: EnumTsDsl) => void) { + constructor(name: EnumName, fn?: (e: EnumTsDsl) => void) { super(); this._name = name; + if (isSymbol(name)) { + name.setKind('enum'); + name.setNode(this); + } fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._name)) ctx.addDependency(this._name); + for (const member of this._members) { + member.analyze(ctx); + } + } + /** Adds an enum member. */ member(name: string, value?: ValueFn): this { const m = new EnumMemberTsDsl(name, value); @@ -39,15 +49,12 @@ export class EnumTsDsl extends TsDsl { return this; } - /** Renders the enum declaration. */ - $render(): ts.EnumDeclaration { + protected override _render() { return ts.factory.createEnumDeclaration( - this.modifiers.list(), - this._name, + this.modifiers, + // @ts-expect-error need to improve types + this.$node(this._name), this.$node(this._members), ); } } - -export interface EnumTsDsl extends ConstMixin, DocMixin, ExportMixin {} -mixin(EnumTsDsl, ConstMixin, DocMixin, ExportMixin); diff --git a/packages/openapi-ts/src/ts-dsl/decl/field.ts b/packages/openapi-ts/src/ts-dsl/decl/field.ts index 452d17df3b..6c81f393f3 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/field.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/field.ts @@ -1,12 +1,11 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TsDsl, TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl, TypeTsDsl } from '../base'; import { DecoratorMixin } from '../mixins/decorator'; import { DocMixin } from '../mixins/doc'; import { - createModifierAccessor, PrivateMixin, ProtectedMixin, PublicMixin, @@ -14,10 +13,24 @@ import { StaticMixin, } from '../mixins/modifiers'; import { ValueMixin } from '../mixins/value'; +import type { TypeExprName } from '../type/expr'; import { TypeExprTsDsl } from '../type/expr'; -export class FieldTsDsl extends TsDsl { - protected modifiers = createModifierAccessor(this); +export type FieldType = TypeExprName | TypeTsDsl; + +const Mixed = DecoratorMixin( + DocMixin( + PrivateMixin( + ProtectedMixin( + PublicMixin( + ReadonlyMixin(StaticMixin(ValueMixin(TsDsl))), + ), + ), + ), + ), +); + +export class FieldTsDsl extends Mixed { protected name: string; protected _type?: TypeTsDsl; @@ -27,16 +40,24 @@ export class FieldTsDsl extends TsDsl { fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._type)) { + ctx.addDependency(this._type); + } else if (isTsDsl(this._type)) { + this._type.analyze(ctx); + } + } + /** Sets the field type. */ - type(type: string | TypeTsDsl): this { + type(type: FieldType): this { this._type = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); return this; } - /** Builds the `PropertyDeclaration` node. */ - $render(): ts.PropertyDeclaration { + protected override _render() { return ts.factory.createPropertyDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], this.name, undefined, this.$type(this._type), @@ -44,24 +65,3 @@ export class FieldTsDsl extends TsDsl { ); } } - -export interface FieldTsDsl - extends DecoratorMixin, - DocMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - ReadonlyMixin, - StaticMixin, - ValueMixin {} -mixin( - FieldTsDsl, - DecoratorMixin, - DocMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - ReadonlyMixin, - StaticMixin, - ValueMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/decl/func.ts b/packages/openapi-ts/src/ts-dsl/decl/func.ts index 680b1d486a..300233cd3d 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/func.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/func.ts @@ -1,8 +1,8 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl, TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { AsMixin } from '../mixins/as'; import { DecoratorMixin } from '../mixins/decorator'; import { DoMixin } from '../mixins/do'; @@ -10,7 +10,6 @@ import { DocMixin } from '../mixins/doc'; import { AbstractMixin, AsyncMixin, - createModifierAccessor, PrivateMixin, ProtectedMixin, PublicMixin, @@ -20,39 +19,65 @@ import { ParamMixin } from '../mixins/param'; import { TypeParamsMixin } from '../mixins/type-params'; import { TypeExprTsDsl } from '../type/expr'; -type FuncMode = 'arrow' | 'decl' | 'expr'; +export type FuncMode = 'arrow' | 'decl' | 'expr'; +export type FuncName = Symbol | string; -class ImplFuncTsDsl extends TsDsl< - M extends 'decl' - ? ts.FunctionDeclaration - : M extends 'expr' - ? ts.FunctionExpression - : ts.ArrowFunction -> { - protected mode: FuncMode; - protected modifiers = createModifierAccessor(this); - protected name?: string; +const Mixed = AbstractMixin( + AsMixin( + AsyncMixin( + DecoratorMixin( + DoMixin( + DocMixin( + ParamMixin( + PrivateMixin( + ProtectedMixin( + PublicMixin( + StaticMixin(TypeParamsMixin(TsDsl)), + ), + ), + ), + ), + ), + ), + ), + ), + ), +); + +class ImplFuncTsDsl extends Mixed { + protected mode?: FuncMode; + protected name?: FuncName; protected _returns?: TypeTsDsl; constructor(); constructor(fn: (f: ImplFuncTsDsl<'arrow'>) => void); - constructor(name: string); - constructor(name: string, fn: (f: ImplFuncTsDsl<'decl'>) => void); + constructor(name: FuncName); + constructor(name: FuncName, fn: (f: ImplFuncTsDsl<'decl'>) => void); constructor( - nameOrFn?: string | ((f: ImplFuncTsDsl<'arrow'>) => void), + name?: FuncName | ((f: ImplFuncTsDsl<'arrow'>) => void), fn?: (f: ImplFuncTsDsl<'decl'>) => void, ) { super(); - if (typeof nameOrFn === 'string') { - this.name = nameOrFn; + if (typeof name === 'function') { + this.mode = 'arrow'; + name(this as unknown as FuncTsDsl<'arrow'>); + } else if (name) { this.mode = 'decl'; + this.name = name; + if (isSymbol(name)) { + name.setKind('function'); + name.setNode(this); + } fn?.(this as unknown as FuncTsDsl<'decl'>); - } else { - this.mode = 'arrow'; - nameOrFn?.(this as unknown as FuncTsDsl<'arrow'>); } } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.name)) ctx.addDependency(this.name); + this._returns?.analyze(ctx); + } + /** Switches the function to an arrow function form. */ arrow(): FuncTsDsl<'arrow'> { this.mode = 'arrow'; @@ -77,7 +102,8 @@ class ImplFuncTsDsl extends TsDsl< return this; } - $render(): M extends 'decl' + // @ts-expect-error --- need to fix types --- + protected override _render(): M extends 'decl' ? ts.FunctionDeclaration : M extends 'expr' ? ts.FunctionExpression @@ -85,9 +111,10 @@ class ImplFuncTsDsl extends TsDsl< if (this.mode === 'decl') { if (!this.name) throw new Error('Function declaration requires a name'); return ts.factory.createFunctionDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], undefined, - this.name, + // @ts-expect-error need to improve types + this.$node(this.name), this.$generics(), this.$params(), this.$type(this._returns), @@ -97,9 +124,10 @@ class ImplFuncTsDsl extends TsDsl< if (this.mode === 'expr') { return ts.factory.createFunctionExpression( - this.modifiers.list(), + this.modifiers, undefined, - this.name, + // @ts-expect-error need to improve types + this.$node(this.name), this.$generics(), this.$params(), this.$type(this._returns), @@ -114,7 +142,7 @@ class ImplFuncTsDsl extends TsDsl< : ts.factory.createBlock(body, true); return ts.factory.createArrowFunction( - this.modifiers.list(), + this.modifiers, this.$generics(), this.$params(), this.$type(this._returns), @@ -124,35 +152,6 @@ class ImplFuncTsDsl extends TsDsl< } } -interface ImplFuncTsDsl - extends AbstractMixin, - AsMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, - TypeParamsMixin {} -mixin( - ImplFuncTsDsl, - AbstractMixin, - AsMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, - TypeParamsMixin, -); - export const FuncTsDsl = ImplFuncTsDsl as { new (): FuncTsDsl<'arrow'>; new (fn: (f: FuncTsDsl<'arrow'>) => void): FuncTsDsl<'arrow'>; diff --git a/packages/openapi-ts/src/ts-dsl/decl/getter.ts b/packages/openapi-ts/src/ts-dsl/decl/getter.ts index 2d1be95e3c..8edf951ebb 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/getter.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/getter.ts @@ -1,15 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { DoMixin } from '../mixins/do'; import { DocMixin } from '../mixins/doc'; -import type { AsyncMixin } from '../mixins/modifiers'; import { AbstractMixin, - createModifierAccessor, + AsyncMixin, PrivateMixin, ProtectedMixin, PublicMixin, @@ -17,8 +15,25 @@ import { } from '../mixins/modifiers'; import { ParamMixin } from '../mixins/param'; -export class GetterTsDsl extends TsDsl { - protected modifiers = createModifierAccessor(this); +const Mixed = AbstractMixin( + AsyncMixin( + DecoratorMixin( + DoMixin( + DocMixin( + ParamMixin( + PrivateMixin( + ProtectedMixin( + PublicMixin(StaticMixin(TsDsl)), + ), + ), + ), + ), + ), + ), + ), +); + +export class GetterTsDsl extends Mixed { protected name: string | ts.PropertyName; constructor(name: string | ts.PropertyName, fn?: (g: GetterTsDsl) => void) { @@ -27,9 +42,13 @@ export class GetterTsDsl extends TsDsl { fn?.(this); } - $render(): ts.GetAccessorDeclaration { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { return ts.factory.createGetAccessorDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], this.name, this.$params(), undefined, @@ -37,27 +56,3 @@ export class GetterTsDsl extends TsDsl { ); } } - -export interface GetterTsDsl - extends AbstractMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin {} -mixin( - GetterTsDsl, - AbstractMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/decl/init.ts b/packages/openapi-ts/src/ts-dsl/decl/init.ts index 7e9e59504e..4358814120 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/init.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/init.ts @@ -1,52 +1,40 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { DoMixin } from '../mixins/do'; import { DocMixin } from '../mixins/doc'; -import { - createModifierAccessor, - PrivateMixin, - ProtectedMixin, - PublicMixin, -} from '../mixins/modifiers'; +import { PrivateMixin, ProtectedMixin, PublicMixin } from '../mixins/modifiers'; import { ParamMixin } from '../mixins/param'; -export class InitTsDsl extends TsDsl { - protected modifiers = createModifierAccessor(this); +const Mixed = DecoratorMixin( + DoMixin( + DocMixin( + ParamMixin( + PrivateMixin( + ProtectedMixin(PublicMixin(TsDsl)), + ), + ), + ), + ), +); +export class InitTsDsl extends Mixed { constructor(fn?: (i: InitTsDsl) => void) { super(); fn?.(this); } - /** Builds the `ConstructorDeclaration` node. */ - $render(): ts.ConstructorDeclaration { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { return ts.factory.createConstructorDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], this.$params(), ts.factory.createBlock(this.$do(), true), ); } } - -export interface InitTsDsl - extends DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin {} -mixin( - InitTsDsl, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/decl/member.ts b/packages/openapi-ts/src/ts-dsl/decl/member.ts index b0cfe68bf2..3fa06adeca 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/member.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/member.ts @@ -1,16 +1,17 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { DocMixin } from '../mixins/doc'; import { safeMemberName } from '../utils/prop'; type Value = string | number | MaybeTsDsl; type ValueFn = Value | ((m: EnumMemberTsDsl) => void); -export class EnumMemberTsDsl extends TsDsl { +const Mixed = DocMixin(TsDsl); + +export class EnumMemberTsDsl extends Mixed { private _name: string; private _value?: Value; @@ -24,19 +25,21 @@ export class EnumMemberTsDsl extends TsDsl { } } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._value)) this._value.analyze(ctx); + } + /** Sets the enum member value. */ value(value?: Value): this { this._value = value; return this; } - $render(): ts.EnumMember { + protected override _render() { return ts.factory.createEnumMember( - safeMemberName(this._name), + this.$node(safeMemberName(this._name)), this.$node(this._value), ); } } - -export interface EnumMemberTsDsl extends DocMixin {} -mixin(EnumMemberTsDsl, DocMixin); diff --git a/packages/openapi-ts/src/ts-dsl/decl/method.ts b/packages/openapi-ts/src/ts-dsl/decl/method.ts index 4c4e3b2d4f..723b621906 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/method.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/method.ts @@ -1,15 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl, TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { DoMixin } from '../mixins/do'; import { DocMixin } from '../mixins/doc'; import { AbstractMixin, AsyncMixin, - createModifierAccessor, PrivateMixin, ProtectedMixin, PublicMixin, @@ -21,8 +19,29 @@ import { TypeParamsMixin } from '../mixins/type-params'; import { TokenTsDsl } from '../token'; import { TypeExprTsDsl } from '../type/expr'; -export class MethodTsDsl extends TsDsl { - protected modifiers = createModifierAccessor(this); +const Mixed = AbstractMixin( + AsyncMixin( + DecoratorMixin( + DoMixin( + DocMixin( + OptionalMixin( + ParamMixin( + PrivateMixin( + ProtectedMixin( + PublicMixin( + StaticMixin(TypeParamsMixin(TsDsl)), + ), + ), + ), + ), + ), + ), + ), + ), + ), +); + +export class MethodTsDsl extends Mixed { protected name: string; protected _returns?: TypeTsDsl; @@ -32,16 +51,20 @@ export class MethodTsDsl extends TsDsl { fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + this._returns?.analyze(ctx); + } + /** Sets the return type. */ returns(type: string | TypeTsDsl): this { this._returns = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); return this; } - /** Builds the `MethodDeclaration` node. */ - $render(): ts.MethodDeclaration { + protected override _render() { return ts.factory.createMethodDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], undefined, this.name, this._optional ? this.$node(new TokenTsDsl().optional()) : undefined, @@ -52,32 +75,3 @@ export class MethodTsDsl extends TsDsl { ); } } - -export interface MethodTsDsl - extends AbstractMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - OptionalMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, - TypeParamsMixin {} -mixin( - MethodTsDsl, - AbstractMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - OptionalMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, - TypeParamsMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/decl/param.ts b/packages/openapi-ts/src/ts-dsl/decl/param.ts index 61229f0438..78376211a0 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/param.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/param.ts @@ -1,8 +1,7 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl, TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { OptionalMixin } from '../mixins/optional'; import { PatternMixin } from '../mixins/pattern'; @@ -10,7 +9,11 @@ import { ValueMixin } from '../mixins/value'; import { TokenTsDsl } from '../token'; import { TypeExprTsDsl } from '../type/expr'; -export class ParamTsDsl extends TsDsl { +const Mixed = DecoratorMixin( + OptionalMixin(PatternMixin(ValueMixin(TsDsl))), +); + +export class ParamTsDsl extends Mixed { protected name?: string; protected _type?: TypeTsDsl; @@ -27,13 +30,18 @@ export class ParamTsDsl extends TsDsl { } } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + this._type?.analyze(ctx); + } + /** Sets the parameter type. */ type(type: string | TypeTsDsl): this { this._type = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); return this; } - $render(): ts.ParameterDeclaration { + protected override _render() { const name = this.$pattern() ?? this.name; if (!name) throw new Error( @@ -49,10 +57,3 @@ export class ParamTsDsl extends TsDsl { ); } } - -export interface ParamTsDsl - extends DecoratorMixin, - OptionalMixin, - PatternMixin, - ValueMixin {} -mixin(ParamTsDsl, DecoratorMixin, OptionalMixin, PatternMixin, ValueMixin); diff --git a/packages/openapi-ts/src/ts-dsl/decl/pattern.ts b/packages/openapi-ts/src/ts-dsl/decl/pattern.ts index 83a7b37ecd..f24596f945 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/pattern.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/pattern.ts @@ -1,3 +1,4 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeArray } from '../base'; @@ -5,15 +6,21 @@ import { TsDsl } from '../base'; import { IdTsDsl } from '../expr/id'; import { TokenTsDsl } from '../token'; +const Mixed = TsDsl; + /** * Builds binding patterns (e.g. `{ foo, bar }`, `[a, b, ...rest]`). */ -export class PatternTsDsl extends TsDsl { +export class PatternTsDsl extends Mixed { protected pattern?: | { kind: 'array'; values: ReadonlyArray } | { kind: 'object'; values: Record }; protected _spread?: string; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + /** Defines an array pattern (e.g. `[a, b, c]`). */ array(...props: ReadonlyArray | [ReadonlyArray]): this { const values = @@ -44,8 +51,7 @@ export class PatternTsDsl extends TsDsl { return this; } - /** Builds and returns a BindingName (ObjectBindingPattern, ArrayBindingPattern, or Identifier). */ - $render(): ts.BindingName { + protected override _render() { if (!this.pattern) { throw new Error('PatternTsDsl requires object() or array() pattern'); } diff --git a/packages/openapi-ts/src/ts-dsl/decl/setter.ts b/packages/openapi-ts/src/ts-dsl/decl/setter.ts index 99c6341921..f7647aa601 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/setter.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/setter.ts @@ -1,15 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DecoratorMixin } from '../mixins/decorator'; import { DoMixin } from '../mixins/do'; import { DocMixin } from '../mixins/doc'; -import type { AsyncMixin } from '../mixins/modifiers'; import { AbstractMixin, - createModifierAccessor, + AsyncMixin, PrivateMixin, ProtectedMixin, PublicMixin, @@ -17,8 +15,25 @@ import { } from '../mixins/modifiers'; import { ParamMixin } from '../mixins/param'; -export class SetterTsDsl extends TsDsl { - protected modifiers = createModifierAccessor(this); +const Mixed = AbstractMixin( + AsyncMixin( + DecoratorMixin( + DoMixin( + DocMixin( + ParamMixin( + PrivateMixin( + ProtectedMixin( + PublicMixin(StaticMixin(TsDsl)), + ), + ), + ), + ), + ), + ), + ), +); + +export class SetterTsDsl extends Mixed { protected name: string | ts.PropertyName; constructor(name: string | ts.PropertyName, fn?: (s: SetterTsDsl) => void) { @@ -27,36 +42,16 @@ export class SetterTsDsl extends TsDsl { fn?.(this); } - $render(): ts.SetAccessorDeclaration { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { return ts.factory.createSetAccessorDeclaration( - [...this.$decorators(), ...this.modifiers.list()], + [...this.$decorators(), ...this.modifiers], this.name, this.$params(), ts.factory.createBlock(this.$do(), true), ); } } - -export interface SetterTsDsl - extends AbstractMixin, - AsyncMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin {} -mixin( - SetterTsDsl, - AbstractMixin, - DecoratorMixin, - DoMixin, - DocMixin, - ParamMixin, - PrivateMixin, - ProtectedMixin, - PublicMixin, - StaticMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/expr/array.ts b/packages/openapi-ts/src/ts-dsl/expr/array.ts index 6d0f095d9e..b96a58ffac 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/array.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/array.ts @@ -1,14 +1,15 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { AsMixin } from '../mixins/as'; import { LayoutMixin } from '../mixins/layout'; import { LiteralTsDsl } from './literal'; -export class ArrayTsDsl extends TsDsl { +const Mixed = AsMixin(LayoutMixin(TsDsl)); + +export class ArrayTsDsl extends Mixed { protected _elements: Array< | { expr: MaybeTsDsl; kind: 'element' } | { expr: MaybeTsDsl; kind: 'spread' } @@ -21,6 +22,13 @@ export class ArrayTsDsl extends TsDsl { this.elements(...exprs); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const item of this._elements) { + if (isTsDsl(item.expr)) item.expr.analyze(ctx); + } + } + /** Adds a single array element. */ element(expr: string | number | boolean | MaybeTsDsl): this { const node = @@ -49,7 +57,7 @@ export class ArrayTsDsl extends TsDsl { return this; } - $render(): ts.ArrayLiteralExpression { + protected override _render() { const elements = this._elements.map((item) => { const node = this.$node(item.expr); return item.kind === 'spread' @@ -63,6 +71,3 @@ export class ArrayTsDsl extends TsDsl { ); } } - -export interface ArrayTsDsl extends AsMixin, LayoutMixin {} -mixin(ArrayTsDsl, AsMixin, LayoutMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/as.ts b/packages/openapi-ts/src/ts-dsl/expr/as.ts index fa1d37fab3..13765267d0 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/as.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/as.ts @@ -1,26 +1,43 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl, TypeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; -import { AsMixin, registerLazyAccessAsFactory } from '../mixins/as'; +import { isTsDsl, TsDsl } from '../base'; +import { AsMixin, setAsFactory } from '../mixins/as'; import { ExprMixin } from '../mixins/expr'; -export class AsTsDsl extends TsDsl { - protected expr: string | MaybeTsDsl; - protected type: string | TypeTsDsl; +export type AsExpr = Symbol | string | MaybeTsDsl; +export type AsType = Symbol | string | TypeTsDsl; +export type AsCtor = (expr: AsExpr, type: AsType) => AsTsDsl; - constructor( - expr: string | MaybeTsDsl, - type: string | TypeTsDsl, - ) { +const Mixed = AsMixin(ExprMixin(TsDsl)); + +export class AsTsDsl extends Mixed { + protected expr: AsExpr; + protected type: AsType; + + constructor(expr: AsExpr, type: AsType) { super(); this.expr = expr; this.type = type; } - $render() { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.expr)) { + ctx.addDependency(this.expr); + } else if (isTsDsl(this.expr)) { + this.expr.analyze(ctx); + } + if (isSymbol(this.type)) { + ctx.addDependency(this.type); + } else if (isTsDsl(this.type)) { + this.type.analyze(ctx); + } + } + + protected override _render() { return ts.factory.createAsExpression( this.$node(this.expr), this.$type(this.type), @@ -28,7 +45,4 @@ export class AsTsDsl extends TsDsl { } } -export interface AsTsDsl extends AsMixin, ExprMixin {} -mixin(AsTsDsl, AsMixin, ExprMixin); - -registerLazyAccessAsFactory((...args) => new AsTsDsl(...args)); +setAsFactory((...args) => new AsTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/expr/attr.ts b/packages/openapi-ts/src/ts-dsl/expr/attr.ts index ad39e8817b..6dfce2267f 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/attr.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/attr.ts @@ -1,34 +1,53 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import { validTypescriptIdentifierRegExp } from '~/utils/regexp'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { AsMixin } from '../mixins/as'; -import { ExprMixin, registerLazyAccessAttrFactory } from '../mixins/expr'; +import { ExprMixin, setAttrFactory } from '../mixins/expr'; import { OperatorMixin } from '../mixins/operator'; import { OptionalMixin } from '../mixins/optional'; import { TokenTsDsl } from '../token'; import { LiteralTsDsl } from './literal'; -export class AttrTsDsl extends TsDsl< - ts.PropertyAccessExpression | ts.ElementAccessExpression -> { - protected left: string | MaybeTsDsl; - protected right: string | ts.MemberName | number; +export type AttrLeft = Symbol | string | MaybeTsDsl; +export type AttrRight = Symbol | string | ts.MemberName | number; +export type AttrCtor = (left: AttrLeft, right: AttrRight) => AttrTsDsl; - constructor( - left: string | MaybeTsDsl, - right: string | ts.MemberName | number, - ) { +const Mixed = AsMixin( + ExprMixin( + OperatorMixin( + OptionalMixin( + TsDsl, + ), + ), + ), +); + +export class AttrTsDsl extends Mixed { + protected left: AttrLeft; + protected right: AttrRight; + + constructor(left: AttrLeft, right: AttrRight) { super(); this.left = left; this.right = right; } - $render(): ts.PropertyAccessExpression | ts.ElementAccessExpression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.left)) { + ctx.addDependency(this.left); + } else if (isTsDsl(this.left)) { + this.left.analyze(ctx); + } + if (isSymbol(this.right)) ctx.addDependency(this.right); + } + + protected override _render() { const leftNode = this.$node(this.left); validTypescriptIdentifierRegExp.lastIndex = 0; if ( @@ -52,21 +71,16 @@ export class AttrTsDsl extends TsDsl< return ts.factory.createPropertyAccessChain( leftNode, this.$node(new TokenTsDsl().questionDot()), - this.$maybeId(this.right), + // @ts-expect-error ts.MemberName is not properly recognized here + this.$node(this.right), ); } return ts.factory.createPropertyAccessExpression( leftNode, - this.$maybeId(this.right), + // @ts-expect-error ts.MemberName is not properly recognized here + this.$node(this.right), ); } } -export interface AttrTsDsl - extends AsMixin, - ExprMixin, - OperatorMixin, - OptionalMixin {} -mixin(AttrTsDsl, AsMixin, ExprMixin, OperatorMixin, OptionalMixin); - -registerLazyAccessAttrFactory((...args) => new AttrTsDsl(...args)); +setAttrFactory((...args) => new AttrTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/expr/await.ts b/packages/openapi-ts/src/ts-dsl/expr/await.ts index 6bca784951..0841077f68 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/await.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/await.ts @@ -1,25 +1,36 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; -import { ExprMixin, registerLazyAccessAwaitFactory } from '../mixins/expr'; +import { isTsDsl, TsDsl } from '../base'; +import { ExprMixin, setAwaitFactory } from '../mixins/expr'; -export class AwaitTsDsl extends TsDsl { - protected _awaitExpr: string | MaybeTsDsl; +export type AwaitExpr = Symbol | string | MaybeTsDsl; +export type AwaitCtor = (expr: AwaitExpr) => AwaitTsDsl; - constructor(expr: string | MaybeTsDsl) { +const Mixed = ExprMixin(TsDsl); + +export class AwaitTsDsl extends Mixed { + protected _awaitExpr: AwaitExpr; + + constructor(expr: AwaitExpr) { super(); this._awaitExpr = expr; } - $render(): ts.AwaitExpression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._awaitExpr)) { + ctx.addDependency(this._awaitExpr); + } else if (isTsDsl(this._awaitExpr)) { + this._awaitExpr.analyze(ctx); + } + } + + protected override _render() { return ts.factory.createAwaitExpression(this.$node(this._awaitExpr)); } } -export interface AwaitTsDsl extends ExprMixin {} -mixin(AwaitTsDsl, ExprMixin); - -registerLazyAccessAwaitFactory((...args) => new AwaitTsDsl(...args)); +setAwaitFactory((...args) => new AwaitTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/expr/binary.ts b/packages/openapi-ts/src/ts-dsl/expr/binary.ts index b87eb7f7b6..e366c56979 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/binary.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/binary.ts @@ -1,13 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { AsMixin } from '../mixins/as'; import { ExprMixin } from '../mixins/expr'; -type Expr = string | MaybeTsDsl; +type Expr = Symbol | string | MaybeTsDsl; type Op = Operator | ts.BinaryOperator; type Operator = | '!=' @@ -27,7 +27,9 @@ type Operator = | '??' | '||'; -export class BinaryTsDsl extends TsDsl { +const Mixed = AsMixin(ExprMixin(TsDsl)); + +export class BinaryTsDsl extends Mixed { protected _base: Expr; protected _expr?: Expr; protected _op?: Op; @@ -39,6 +41,20 @@ export class BinaryTsDsl extends TsDsl { this._expr = expr; } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._base)) { + ctx.addDependency(this._base); + } else if (isTsDsl(this._base)) { + this._base.analyze(ctx); + } + if (isSymbol(this._expr)) { + ctx.addDependency(this._expr); + } else if (isTsDsl(this._expr)) { + this._expr.analyze(ctx); + } + } + /** Logical AND — `this && expr` */ and(expr: Expr): this { return this.opAndExpr('&&', expr); @@ -119,7 +135,7 @@ export class BinaryTsDsl extends TsDsl { return this.opAndExpr('*', expr); } - $render(): ts.BinaryExpression { + protected override _render() { if (!this._op) { throw new Error('BinaryTsDsl: missing operator'); } @@ -166,6 +182,3 @@ export class BinaryTsDsl extends TsDsl { return token; } } - -export interface BinaryTsDsl extends AsMixin, ExprMixin {} -mixin(BinaryTsDsl, AsMixin, ExprMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/call.ts b/packages/openapi-ts/src/ts-dsl/expr/call.ts index 77dfe4a6cd..49282bb9e0 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/call.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/call.ts @@ -1,29 +1,37 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { ArgsMixin } from '../mixins/args'; import { AsMixin } from '../mixins/as'; -import { ExprMixin, registerLazyAccessCallFactory } from '../mixins/expr'; +import { ExprMixin, setCallFactory } from '../mixins/expr'; import { TypeArgsMixin } from '../mixins/type-args'; -export class CallTsDsl extends TsDsl { - protected _callee: string | MaybeTsDsl; +export type CallCallee = string | MaybeTsDsl; +export type CallArg = Symbol | string | MaybeTsDsl; +export type CallArgs = ReadonlyArray; +export type CallCtor = (callee: CallCallee, ...args: CallArgs) => CallTsDsl; - constructor( - callee: string | MaybeTsDsl, - ...args: ReadonlyArray | undefined> - ) { +const Mixed = ArgsMixin( + AsMixin(ExprMixin(TypeArgsMixin(TsDsl))), +); + +export class CallTsDsl extends Mixed { + protected _callee: CallCallee; + + constructor(callee: CallCallee, ...args: CallArgs) { super(); this._callee = callee; - this.args( - ...args.filter((a): a is NonNullable => a !== undefined), - ); + this.args(...args); } - $render(): ts.CallExpression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._callee)) this._callee.analyze(ctx); + } + + protected override _render() { return ts.factory.createCallExpression( this.$node(this._callee), this.$generics(), @@ -32,11 +40,4 @@ export class CallTsDsl extends TsDsl { } } -export interface CallTsDsl - extends ArgsMixin, - AsMixin, - ExprMixin, - TypeArgsMixin {} -mixin(CallTsDsl, ArgsMixin, AsMixin, ExprMixin, TypeArgsMixin); - -registerLazyAccessCallFactory((expr, args) => new CallTsDsl(expr, ...args)); +setCallFactory((...args) => new CallTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/expr/expr.ts b/packages/openapi-ts/src/ts-dsl/expr/expr.ts index 768fbb6492..fd5d6cade4 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/expr.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/expr.ts @@ -1,30 +1,38 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { AsMixin } from '../mixins/as'; import { ExprMixin } from '../mixins/expr'; import { OperatorMixin } from '../mixins/operator'; import { TypeExprMixin } from '../mixins/type-expr'; -export class ExprTsDsl extends TsDsl { - protected _exprInput: string | MaybeTsDsl; +type Id = Symbol | string | MaybeTsDsl; - constructor(id: string | MaybeTsDsl) { +const Mixed = AsMixin( + ExprMixin(OperatorMixin(TypeExprMixin(TsDsl))), +); + +export class ExprTsDsl extends Mixed { + protected _exprInput: Id; + + constructor(id: Id) { super(); this._exprInput = id; } - $render(): ts.Expression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._exprInput)) { + ctx.addDependency(this._exprInput); + } else if (isTsDsl(this._exprInput)) { + this._exprInput.analyze(ctx); + } + } + + protected override _render() { return this.$node(this._exprInput); } } - -export interface ExprTsDsl - extends AsMixin, - ExprMixin, - OperatorMixin, - TypeExprMixin {} -mixin(ExprTsDsl, AsMixin, ExprMixin, OperatorMixin, TypeExprMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/fromValue.ts b/packages/openapi-ts/src/ts-dsl/expr/fromValue.ts index 90269e84f8..4aca12a6d5 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/fromValue.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/fromValue.ts @@ -1,6 +1,7 @@ import type ts from 'typescript'; -import { TsDsl } from '../base'; +import type { TsDsl } from '../base'; +import { isTsDsl } from '../base'; import { ArrayTsDsl } from './array'; import { LiteralTsDsl } from './literal'; import { ObjectTsDsl } from './object'; @@ -11,8 +12,8 @@ export const fromValue = ( layout?: 'pretty'; }, ): TsDsl => { - if (input instanceof TsDsl) { - return input; + if (isTsDsl(input)) { + return input as TsDsl; } if (input === null) { diff --git a/packages/openapi-ts/src/ts-dsl/expr/id.ts b/packages/openapi-ts/src/ts-dsl/expr/id.ts index a4da00e5bd..09d848b6f9 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/id.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/id.ts @@ -1,8 +1,11 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; -export class IdTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class IdTsDsl extends Mixed { protected name: string; constructor(name: string) { @@ -10,7 +13,11 @@ export class IdTsDsl extends TsDsl { this.name = name; } - $render(): ts.Identifier { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { return ts.factory.createIdentifier(this.name); } } diff --git a/packages/openapi-ts/src/ts-dsl/expr/literal.ts b/packages/openapi-ts/src/ts-dsl/expr/literal.ts index f239d91517..9620d0792a 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/literal.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/literal.ts @@ -1,12 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/no-empty-object-type */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; import { PrefixTsDsl } from '../expr/prefix'; -import { mixin } from '../mixins/apply'; import { AsMixin } from '../mixins/as'; -export class LiteralTsDsl extends TsDsl { +const Mixed = AsMixin(TsDsl); + +export class LiteralTsDsl extends Mixed { protected value: string | number | boolean | null; constructor(value: string | number | boolean | null) { @@ -14,7 +15,11 @@ export class LiteralTsDsl extends TsDsl { this.value = value; } - $render(): ts.LiteralTypeNode['literal'] { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { if (typeof this.value === 'boolean') { return this.value ? ts.factory.createTrue() : ts.factory.createFalse(); } @@ -31,6 +36,3 @@ export class LiteralTsDsl extends TsDsl { throw new Error(`Unsupported literal: ${String(this.value)}`); } } - -export interface LiteralTsDsl extends AsMixin {} -mixin(LiteralTsDsl, AsMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/new.ts b/packages/openapi-ts/src/ts-dsl/expr/new.ts index e557a3f2ac..ebfdc400c4 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/new.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/new.ts @@ -1,27 +1,36 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { ArgsMixin } from '../mixins/args'; import { ExprMixin } from '../mixins/expr'; import { TypeArgsMixin } from '../mixins/type-args'; -export class NewTsDsl extends TsDsl { - protected classExpr: string | MaybeTsDsl; +export type NewExpr = Symbol | string | MaybeTsDsl; - constructor( - classExpr: string | MaybeTsDsl, - ...args: ReadonlyArray> - ) { +const Mixed = ArgsMixin(ExprMixin(TypeArgsMixin(TsDsl))); + +export class NewTsDsl extends Mixed { + protected classExpr: NewExpr; + + constructor(classExpr: NewExpr, ...args: ReadonlyArray) { super(); this.classExpr = classExpr; this.args(...args); } - /** Builds the `NewExpression` node. */ - $render(): ts.NewExpression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.classExpr)) { + ctx.addDependency(this.classExpr); + } else if (isTsDsl(this.classExpr)) { + this.classExpr.analyze(ctx); + } + } + + protected override _render() { return ts.factory.createNewExpression( this.$node(this.classExpr), this.$generics(), @@ -29,6 +38,3 @@ export class NewTsDsl extends TsDsl { ); } } - -export interface NewTsDsl extends ArgsMixin, ExprMixin, TypeArgsMixin {} -mixin(NewTsDsl, ArgsMixin, ExprMixin, TypeArgsMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/object.ts b/packages/openapi-ts/src/ts-dsl/expr/object.ts index 7dc91e3c56..cd16a1d376 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/object.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/object.ts @@ -1,21 +1,24 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { AsMixin } from '../mixins/as'; import { ExprMixin } from '../mixins/expr'; import { HintMixin } from '../mixins/hint'; import { LayoutMixin } from '../mixins/layout'; import { ObjectPropTsDsl } from './prop'; -type Expr = string | MaybeTsDsl; -type Stmt = string | MaybeTsDsl; +type Expr = Symbol | string | MaybeTsDsl; +type Stmt = Symbol | string | MaybeTsDsl; type ExprFn = Expr | ((p: ObjectPropTsDsl) => void); type StmtFn = Stmt | ((p: ObjectPropTsDsl) => void); -export class ObjectTsDsl extends TsDsl { +const Mixed = AsMixin( + ExprMixin(HintMixin(LayoutMixin(TsDsl))), +); + +export class ObjectTsDsl extends Mixed { protected _props: Array = []; constructor(...props: Array) { @@ -23,6 +26,13 @@ export class ObjectTsDsl extends TsDsl { this.props(...props); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const prop of this._props) { + prop.analyze(ctx); + } + } + /** Adds a computed property (e.g. `{ [expr]: value }`). */ computed(name: string, expr: ExprFn): this { this._props.push( @@ -71,18 +81,10 @@ export class ObjectTsDsl extends TsDsl { return this; } - /** Builds and returns the object literal expression. */ - $render(): ts.ObjectLiteralExpression { + protected override _render() { return ts.factory.createObjectLiteralExpression( this.$node(this._props), this.$multiline(this._props.length), ); } } - -export interface ObjectTsDsl - extends AsMixin, - ExprMixin, - HintMixin, - LayoutMixin {} -mixin(ObjectTsDsl, AsMixin, ExprMixin, HintMixin, LayoutMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/prefix.ts b/packages/openapi-ts/src/ts-dsl/expr/prefix.ts index b13203d10b..b6d886a448 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/prefix.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/prefix.ts @@ -1,9 +1,12 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; -export class PrefixTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class PrefixTsDsl extends Mixed { protected _expr?: string | MaybeTsDsl; protected _op?: ts.PrefixUnaryOperator; @@ -16,6 +19,11 @@ export class PrefixTsDsl extends TsDsl { this._op = op; } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._expr)) this._expr.analyze(ctx); + } + /** Sets the operand (the expression being prefixed). */ expr(expr: string | MaybeTsDsl): this { this._expr = expr; @@ -40,8 +48,7 @@ export class PrefixTsDsl extends TsDsl { return this; } - /** Renders the prefix unary expression node. */ - $render(): ts.PrefixUnaryExpression { + protected override _render() { if (!this._expr) { throw new Error('Missing expression for prefix unary expression'); } diff --git a/packages/openapi-ts/src/ts-dsl/expr/prop.ts b/packages/openapi-ts/src/ts-dsl/expr/prop.ts index 16130fe38d..5cfc599260 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/prop.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/prop.ts @@ -1,17 +1,17 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; import { GetterTsDsl } from '../decl/getter'; import { SetterTsDsl } from '../decl/setter'; -import { mixin } from '../mixins/apply'; import { DocMixin } from '../mixins/doc'; import { safePropName } from '../utils/prop'; import { IdTsDsl } from './id'; -type Expr = string | MaybeTsDsl; -type Stmt = string | MaybeTsDsl; +type Expr = Symbol | string | MaybeTsDsl; +type Stmt = Symbol | string | MaybeTsDsl; type Kind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread'; type Meta = @@ -21,7 +21,9 @@ type Meta = | { kind: 'setter'; name: string } | { kind: 'spread'; name?: undefined }; -export class ObjectPropTsDsl extends TsDsl { +const Mixed = DocMixin(TsDsl); + +export class ObjectPropTsDsl extends Mixed { protected _value?: Expr | Stmt; protected meta: Meta; @@ -30,6 +32,15 @@ export class ObjectPropTsDsl extends TsDsl { this.meta = meta; } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._value)) { + ctx.addDependency(this._value); + } else if (isTsDsl(this._value)) { + this._value.analyze(ctx); + } + } + /** Returns true when all required builder calls are present. */ get isValid(): boolean { return this.missingRequiredCalls().length === 0; @@ -44,7 +55,7 @@ export class ObjectPropTsDsl extends TsDsl { return this; } - $render(): ts.ObjectLiteralElementLike { + protected override _render() { this.$validate(); const node = this.$node(this._value); if (this.meta.kind === 'spread') { @@ -56,11 +67,15 @@ export class ObjectPropTsDsl extends TsDsl { return ts.factory.createSpreadAssignment(node); } if (this.meta.kind === 'getter') { - const getter = new GetterTsDsl(safePropName(this.meta.name)).do(node); + const getter = new GetterTsDsl( + this.$node(safePropName(this.meta.name)), + ).do(node); return this.$node(getter); } if (this.meta.kind === 'setter') { - const setter = new SetterTsDsl(safePropName(this.meta.name)).do(node); + const setter = new SetterTsDsl( + this.$node(safePropName(this.meta.name)), + ).do(node); return this.$node(setter); } if (ts.isIdentifier(node) && node.text === this.meta.name) { @@ -76,7 +91,7 @@ export class ObjectPropTsDsl extends TsDsl { ? ts.factory.createComputedPropertyName( this.$node(new IdTsDsl(this.meta.name)), ) - : safePropName(this.meta.name), + : this.$node(safePropName(this.meta.name)), node, ); } @@ -98,6 +113,3 @@ export class ObjectPropTsDsl extends TsDsl { return missing; } } - -export interface ObjectPropTsDsl extends DocMixin {} -mixin(ObjectPropTsDsl, DocMixin); diff --git a/packages/openapi-ts/src/ts-dsl/expr/regexp.ts b/packages/openapi-ts/src/ts-dsl/expr/regexp.ts index d690e17e86..ee3ce52920 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/regexp.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/regexp.ts @@ -1,3 +1,4 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl } from '../base'; @@ -10,7 +11,9 @@ type RegexFlags = [K in Avail]: `${K}${RegexFlags>}`; }[Avail]; -export class RegExpTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class RegExpTsDsl extends Mixed { protected pattern: string; protected flags?: RegexFlags; @@ -20,8 +23,11 @@ export class RegExpTsDsl extends TsDsl { this.flags = flags; } - /** Emits a RegularExpressionLiteral node. */ - $render(): ts.RegularExpressionLiteral { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { const patternContent = this.pattern.startsWith('/') && this.pattern.endsWith('/') ? this.pattern.slice(1, -1) diff --git a/packages/openapi-ts/src/ts-dsl/expr/template.ts b/packages/openapi-ts/src/ts-dsl/expr/template.ts index 90286cc5c9..847381d410 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/template.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/template.ts @@ -1,11 +1,12 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; -export class TemplateTsDsl extends TsDsl< - ts.TemplateExpression | ts.NoSubstitutionTemplateLiteral -> { +const Mixed = TsDsl; + +export class TemplateTsDsl extends Mixed { protected parts: Array> = []; constructor(value?: string | MaybeTsDsl) { @@ -13,12 +14,19 @@ export class TemplateTsDsl extends TsDsl< if (value !== undefined) this.add(value); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const part of this.parts) { + if (isTsDsl(part)) part.analyze(ctx); + } + } + add(value: string | MaybeTsDsl): this { this.parts.push(value); return this; } - $render(): ts.TemplateExpression | ts.NoSubstitutionTemplateLiteral { + protected override _render() { const parts = this.$node(this.parts); const normalized: Array = []; diff --git a/packages/openapi-ts/src/ts-dsl/expr/ternary.ts b/packages/openapi-ts/src/ts-dsl/expr/ternary.ts index 3f841ee41c..b189ab491b 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/ternary.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/ternary.ts @@ -1,9 +1,12 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; -export class TernaryTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class TernaryTsDsl extends Mixed { protected _condition?: string | MaybeTsDsl; protected _then?: string | MaybeTsDsl; protected _else?: string | MaybeTsDsl; @@ -13,6 +16,13 @@ export class TernaryTsDsl extends TsDsl { if (condition) this.condition(condition); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._condition)) this._condition.analyze(ctx); + if (isTsDsl(this._then)) this._then.analyze(ctx); + if (isTsDsl(this._else)) this._else.analyze(ctx); + } + condition(condition: string | MaybeTsDsl) { this._condition = condition; return this; @@ -28,7 +38,7 @@ export class TernaryTsDsl extends TsDsl { return this; } - $render(): ts.ConditionalExpression { + protected override _render() { if (!this._condition) throw new Error('Missing condition in ternary'); if (!this._then) throw new Error('Missing then expression in ternary'); if (!this._else) throw new Error('Missing else expression in ternary'); diff --git a/packages/openapi-ts/src/ts-dsl/expr/typeof.ts b/packages/openapi-ts/src/ts-dsl/expr/typeof.ts index 4574610be1..ac9034eaad 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/typeof.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/typeof.ts @@ -1,13 +1,14 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { OperatorMixin } from '../mixins/operator'; import { registerLazyAccessTypeOfExprFactory } from '../mixins/type-expr'; -export class TypeOfExprTsDsl extends TsDsl { +const Mixed = OperatorMixin(TsDsl); + +export class TypeOfExprTsDsl extends Mixed { protected _expr: string | MaybeTsDsl; constructor(expr: string | MaybeTsDsl) { @@ -15,12 +16,14 @@ export class TypeOfExprTsDsl extends TsDsl { this._expr = expr; } - $render(): ts.TypeOfExpression { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._expr)) this._expr.analyze(ctx); + } + + protected override _render() { return ts.factory.createTypeOfExpression(this.$node(this._expr)); } } -export interface TypeOfExprTsDsl extends OperatorMixin {} -mixin(TypeOfExprTsDsl, OperatorMixin); - registerLazyAccessTypeOfExprFactory((...args) => new TypeOfExprTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/index.ts b/packages/openapi-ts/src/ts-dsl/index.ts index 23df6a92ed..86233117ac 100644 --- a/packages/openapi-ts/src/ts-dsl/index.ts +++ b/packages/openapi-ts/src/ts-dsl/index.ts @@ -338,4 +338,4 @@ export type DollarTsDsl = { }; export type { MaybeTsDsl, TypeTsDsl } from './base'; -export { TsDsl } from './base'; +export { isTsDsl, TsDsl, tsDslBrand } from './base'; diff --git a/packages/openapi-ts/src/ts-dsl/layout/doc.ts b/packages/openapi-ts/src/ts-dsl/layout/doc.ts index 9845dcf03f..cf60ebff7d 100644 --- a/packages/openapi-ts/src/ts-dsl/layout/doc.ts +++ b/packages/openapi-ts/src/ts-dsl/layout/doc.ts @@ -1,3 +1,4 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeArray } from '../base'; @@ -19,6 +20,10 @@ export class DocTsDsl extends TsDsl { fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + add(...lines: ReadonlyArray): this { this._lines.push(...lines); return this; @@ -58,7 +63,7 @@ export class DocTsDsl extends TsDsl { return node; } - $render(): ts.Node { + protected override _render(): ts.Node { // this class does not build a standalone node; // it modifies other nodes via `apply()`. // Return a dummy comment node for compliance. diff --git a/packages/openapi-ts/src/ts-dsl/layout/hint.ts b/packages/openapi-ts/src/ts-dsl/layout/hint.ts index 1d7eadbc87..dae2221500 100644 --- a/packages/openapi-ts/src/ts-dsl/layout/hint.ts +++ b/packages/openapi-ts/src/ts-dsl/layout/hint.ts @@ -1,3 +1,4 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeArray } from '../base'; @@ -19,6 +20,10 @@ export class HintTsDsl extends TsDsl { fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + add(...lines: ReadonlyArray): this { this._lines.push(...lines); return this; @@ -40,7 +45,7 @@ export class HintTsDsl extends TsDsl { return node; } - $render(): ts.Node { + protected override _render(): ts.Node { // this class does not build a standalone node; // it modifies other nodes via `apply()`. // Return a dummy comment node for compliance. diff --git a/packages/openapi-ts/src/ts-dsl/layout/newline.ts b/packages/openapi-ts/src/ts-dsl/layout/newline.ts index 64df6f21c7..fe859d9fa9 100644 --- a/packages/openapi-ts/src/ts-dsl/layout/newline.ts +++ b/packages/openapi-ts/src/ts-dsl/layout/newline.ts @@ -1,10 +1,15 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import type ts from 'typescript'; import { TsDsl } from '../base'; import { IdTsDsl } from '../expr/id'; export class NewlineTsDsl extends TsDsl { - $render(): ts.Identifier { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render(): ts.Identifier { return this.$node(new IdTsDsl('\n')); } } diff --git a/packages/openapi-ts/src/ts-dsl/layout/note.ts b/packages/openapi-ts/src/ts-dsl/layout/note.ts index 5c264ef969..68cf554d17 100644 --- a/packages/openapi-ts/src/ts-dsl/layout/note.ts +++ b/packages/openapi-ts/src/ts-dsl/layout/note.ts @@ -1,3 +1,4 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeArray } from '../base'; @@ -19,6 +20,10 @@ export class NoteTsDsl extends TsDsl { fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + add(...lines: ReadonlyArray): this { this._lines.push(...lines); return this; @@ -38,7 +43,7 @@ export class NoteTsDsl extends TsDsl { return node; } - $render(): ts.Node { + protected override _render(): ts.Node { // this class does not build a standalone node; // it modifies other nodes via `apply()`. // Return a dummy comment node for compliance. diff --git a/packages/openapi-ts/src/ts-dsl/mixins/apply.ts b/packages/openapi-ts/src/ts-dsl/mixins/apply.ts deleted file mode 100644 index 442c720bb2..0000000000 --- a/packages/openapi-ts/src/ts-dsl/mixins/apply.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -export function mixin(target: Function, ...sources: ReadonlyArray) { - const targetProto = target.prototype; - for (const src of sources) { - let resolvedSource = src; - if (typeof src === 'function') { - try { - const candidate = src(target); - if (candidate?.prototype) { - resolvedSource = candidate; - } - } catch { - // noop - } - } - const sourceProto = resolvedSource.prototype; - if (!sourceProto) continue; - for (const [key, descriptor] of Object.entries( - Object.getOwnPropertyDescriptors(sourceProto), - )) { - if (key === 'constructor') continue; - if (key === '$render' && !descriptor.value.mixin) continue; - Object.defineProperty(targetProto, key, descriptor); - } - } -} diff --git a/packages/openapi-ts/src/ts-dsl/mixins/args.ts b/packages/openapi-ts/src/ts-dsl/mixins/args.ts index 4cd88f8403..44bd0eac0c 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/args.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/args.ts @@ -1,33 +1,57 @@ +import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; +import type { BaseCtor, MixinCtor } from './types'; + +type Arg = Symbol | string | MaybeTsDsl; + +export interface ArgsMethods extends Node { + /** Renders the arguments into an array of `Expression`s. */ + $args(): ReadonlyArray; + /** Adds a single expression argument. */ + arg(arg: Arg | undefined): this; + /** Adds one or more expression arguments. */ + args(...args: ReadonlyArray): this; +} /** * Adds `.arg()` and `.args()` for managing expression arguments in call-like nodes. */ -export class ArgsMixin extends TsDsl { - protected _args?: Array>; +export function ArgsMixin>( + Base: TBase, +) { + abstract class Args extends Base { + protected _args: Array = []; - /** Adds a single expression argument. */ - arg(arg: string | MaybeTsDsl): this { - (this._args ??= []).push(arg); - return this; - } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const arg of this._args) { + if (isSymbol(arg)) { + ctx.addDependency(arg); + } else if (isTsDsl(arg)) { + arg.analyze(ctx); + } + } + } - /** Adds one or more expression arguments. */ - args(...args: ReadonlyArray>): this { - (this._args ??= []).push(...args); - return this; - } + protected arg(arg: Arg | undefined): this { + if (arg !== undefined) this._args.push(arg); + return this; + } - /** Renders the arguments into an array of `Expression`s. */ - protected $args(): ReadonlyArray { - if (!this._args) return []; - return this.$node(this._args).map((arg) => this.$maybeId(arg)); - } + protected args(...args: ReadonlyArray): this { + this._args.push( + ...args.filter((a): a is NonNullable => a !== undefined), + ); + return this; + } - $render(): ts.Node { - throw new Error('noop'); + protected $args(): ReadonlyArray { + return this.$node(this._args).map((arg) => this.$node(arg)); + } } + + return Args as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/as.ts b/packages/openapi-ts/src/ts-dsl/mixins/as.ts index 5097fe0fcc..d529113146 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/as.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/as.ts @@ -1,24 +1,32 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl, TypeTsDsl } from '../base'; -import type { AsTsDsl } from '../expr/as'; +import type { AsCtor, AsTsDsl, AsType } from '../expr/as'; +import type { BaseCtor, MixinCtor } from './types'; -type AsFactory = ( - expr: string | MaybeTsDsl, - type: string | TypeTsDsl, -) => AsTsDsl; -let asFactory: AsFactory | undefined; -/** Registers the As DSL factory after its module has finished evaluating. */ -export function registerLazyAccessAsFactory(factory: AsFactory): void { +let asFactory: AsCtor | undefined; +/** Lazy register the factory to avoid circular imports. */ +export function setAsFactory(factory: AsCtor): void { asFactory = factory; } -export class AsMixin { +export interface AsMethods extends Node { /** Creates an `as` type assertion expression (e.g. `value as Type`). */ - as( - this: string | MaybeTsDsl, - type: string | TypeTsDsl, - ): AsTsDsl { - return asFactory!(this, type); + as(type: AsType): AsTsDsl; +} + +export function AsMixin>( + Base: TBase, +) { + abstract class As extends Base { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected as(type: AsType): AsTsDsl { + return asFactory!(this, type); + } } + + return As as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/decorator.ts b/packages/openapi-ts/src/ts-dsl/mixins/decorator.ts index 9a414918ab..ce5a5ba368 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/decorator.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/decorator.ts @@ -1,28 +1,45 @@ +import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; import { DecoratorTsDsl } from '../decl/decorator'; +import type { BaseCtor, MixinCtor } from './types'; -export class DecoratorMixin extends TsDsl { - protected decorators?: Array; - +export interface DecoratorMethods extends Node { + /** Renders the decorators into an array of `ts.Decorator`s. */ + $decorators(): ReadonlyArray; /** Adds a decorator (e.g. `@sealed({ in: 'root' })`). */ decorator( - name: string | ts.Expression, + name: Symbol | string | MaybeTsDsl, ...args: ReadonlyArray> - ): this { - (this.decorators ??= []).push(new DecoratorTsDsl(name, ...args)); - return this; - } + ): this; +} - /** Renders the decorators into an array of `ts.Decorator`s. */ - protected $decorators(): ReadonlyArray { - if (!this.decorators) return []; - return this.$node(this.decorators); - } +export function DecoratorMixin>( + Base: TBase, +) { + abstract class Decorator extends Base { + protected decorators: Array = []; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const decorator of this.decorators) { + if (isTsDsl(decorator)) decorator.analyze(ctx); + } + } - $render(): ts.Node { - throw new Error('noop'); + protected decorator( + name: Symbol | string | MaybeTsDsl, + ...args: ReadonlyArray> + ): this { + this.decorators.push(new DecoratorTsDsl(name, ...args)); + return this; + } + + protected $decorators(): ReadonlyArray { + return this.$node(this.decorators); + } } + + return Decorator as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/do.ts b/packages/openapi-ts/src/ts-dsl/mixins/do.ts index e3873777a1..67e8e3c28b 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/do.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/do.ts @@ -1,28 +1,44 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; import { StmtTsDsl } from '../stmt/stmt'; +import type { BaseCtor, MixinCtor } from './types'; + +export interface DoMethods extends Node { + /** Renders the collected `.do()` calls into an array of `Statement` nodes. */ + $do(): ReadonlyArray; + /** Adds one or more expressions/statements to the body. */ + do(...items: ReadonlyArray>): this; +} /** * Adds `.do()` for appending statements or expressions to a body. */ -export class DoMixin extends TsDsl { - protected _do?: Array>; +export function DoMixin>( + Base: TBase, +) { + abstract class Do extends Base { + protected _do: Array> = []; - /** Adds one or more expressions/statements to the body. */ - do(...items: ReadonlyArray>): this { - (this._do ??= []).push(...items); - return this; - } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const item of this._do) { + if (isTsDsl(item)) item.analyze(ctx); + } + } - /** Renders the collected `.do()` calls into an array of `Statement` nodes. */ - protected $do(): ReadonlyArray { - if (!this._do) return []; - return this.$node(this._do.map((item) => new StmtTsDsl(item))); - } + protected do( + ...items: ReadonlyArray> + ): this { + this._do.push(...items); + return this; + } - $render(): ts.Node { - throw new Error('noop'); + protected $do(): ReadonlyArray { + return this.$node(this._do.map((item) => new StmtTsDsl(item))); + } } + + return Do as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/doc.ts b/packages/openapi-ts/src/ts-dsl/mixins/doc.ts index 3e9159296c..080b30bb65 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/doc.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/doc.ts @@ -1,29 +1,37 @@ -import type { ITsDsl, MaybeArray } from '../base'; +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import type ts from 'typescript'; + +import type { MaybeArray } from '../base'; import { DocTsDsl } from '../layout/doc'; +import type { BaseCtor, MixinCtor } from './types'; + +export interface DocMethods extends Node { + doc(lines?: MaybeArray, fn?: (d: DocTsDsl) => void): this; +} -export function DocMixin< - TBase extends new (...args: ReadonlyArray) => ITsDsl, ->(Base: TBase) { - const $renderBase = Base.prototype.$render; +export function DocMixin>( + Base: TBase, +) { + abstract class Doc extends Base { + protected _doc?: DocTsDsl; - class Mixin extends Base { - _doc?: DocTsDsl; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } - doc(lines?: MaybeArray, fn?: (d: DocTsDsl) => void): this { + protected doc( + lines?: MaybeArray, + fn?: (d: DocTsDsl) => void, + ): this { this._doc = new DocTsDsl(lines, fn); return this; } - override $render(...args: Parameters) { - const node = $renderBase.apply(this, args); + protected override _render() { + const node = this.$render(); return this._doc ? this._doc.apply(node) : node; } } - // @ts-expect-error - Mixin.prototype.$render.mixin = true; - - return Mixin; + return Doc as unknown as MixinCtor; } - -export type DocMixin = InstanceType>; diff --git a/packages/openapi-ts/src/ts-dsl/mixins/expr.ts b/packages/openapi-ts/src/ts-dsl/mixins/expr.ts index 9a0d373521..469a115449 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/expr.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/expr.ts @@ -1,69 +1,71 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import type { AttrTsDsl } from '../expr/attr'; -import type { AwaitTsDsl } from '../expr/await'; -import type { CallTsDsl } from '../expr/call'; -import type { ReturnTsDsl } from '../stmt/return'; +import type { AttrCtor, AttrRight, AttrTsDsl } from '../expr/attr'; +import type { AwaitCtor, AwaitTsDsl } from '../expr/await'; +import type { CallArgs, CallCtor, CallTsDsl } from '../expr/call'; +import type { ReturnCtor, ReturnTsDsl } from '../stmt/return'; +import type { BaseCtor, MixinCtor } from './types'; -type AttrFactory = ( - expr: string | MaybeTsDsl, - name: string | ts.MemberName | number, -) => AttrTsDsl; -let attrFactory: AttrFactory | undefined; -/** Registers the Attr DSL factory after its module has finished evaluating. */ -export function registerLazyAccessAttrFactory(factory: AttrFactory): void { - attrFactory = factory; +let attrFactory: AttrCtor | undefined; +/** Lazy register the factory to avoid circular imports. */ +export function setAttrFactory(fn: AttrCtor): void { + attrFactory = fn; } -type AwaitFactory = (expr: string | MaybeTsDsl) => AwaitTsDsl; -let awaitFactory: AwaitFactory | undefined; -/** Registers the Await DSL factory after its module has finished evaluating. */ -export function registerLazyAccessAwaitFactory(factory: AwaitFactory): void { - awaitFactory = factory; +let awaitFactory: AwaitCtor | undefined; +/** Lazy register the factory to avoid circular imports. */ +export function setAwaitFactory(fn: AwaitCtor): void { + awaitFactory = fn; } -type CallFactory = ( - expr: string | MaybeTsDsl, - args: ReadonlyArray | undefined>, -) => CallTsDsl; -let callFactory: CallFactory | undefined; -/** Registers the Call DSL factory after its module has finished evaluating. */ -export function registerLazyAccessCallFactory(factory: CallFactory): void { - callFactory = factory; +let callFactory: CallCtor | undefined; +/** Lazy register the factory to avoid circular imports. */ +export function setCallFactory(fn: CallCtor): void { + callFactory = fn; } -type ReturnFactory = (expr: string | MaybeTsDsl) => ReturnTsDsl; -let returnFactory: ReturnFactory | undefined; -/** Registers the Return DSL factory after its module has finished evaluating. */ -export function registerLazyAccessReturnFactory(factory: ReturnFactory): void { - returnFactory = factory; +let returnFactory: ReturnCtor | undefined; +/** Lazy register the factory to avoid circular imports. */ +export function setReturnFactory(fn: ReturnCtor): void { + returnFactory = fn; } -export class ExprMixin { +export interface ExprMethods extends Node { /** Accesses a property on the current expression (e.g. `this.foo`). */ - attr( - this: string | MaybeTsDsl, - name: string | ts.MemberName | number, - ): AttrTsDsl { - return attrFactory!(this, name); - } - + attr(name: AttrRight): AttrTsDsl; /** Awaits the current expression (e.g. `await expr`). */ - await(this: string | MaybeTsDsl): AwaitTsDsl { - return awaitFactory!(this); - } - + await(): AwaitTsDsl; /** Calls the current expression (e.g. `fn(arg1, arg2)`). */ - call( - this: string | MaybeTsDsl, - ...args: ReadonlyArray | undefined> - ): CallTsDsl { - return callFactory!(this, args); - } - + call(...args: CallArgs): CallTsDsl; /** Produces a `return` statement returning the current expression. */ - return(this: string | MaybeTsDsl): ReturnTsDsl { - return returnFactory!(this); + return(): ReturnTsDsl; +} + +export function ExprMixin>( + Base: TBase, +) { + abstract class Expr extends Base { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected attr(name: AttrRight): AttrTsDsl { + return attrFactory!(this, name); + } + + protected await(): AwaitTsDsl { + return awaitFactory!(this); + } + + protected call(...args: CallArgs): CallTsDsl { + return callFactory!(this, ...args); + } + + protected return(): ReturnTsDsl { + return returnFactory!(this); + } } + + return Expr as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/hint.ts b/packages/openapi-ts/src/ts-dsl/mixins/hint.ts index 62cb761f1d..214504f68b 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/hint.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/hint.ts @@ -1,29 +1,37 @@ -import type { ITsDsl, MaybeArray } from '../base'; +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import type ts from 'typescript'; + +import type { MaybeArray } from '../base'; import { HintTsDsl } from '../layout/hint'; +import type { BaseCtor, MixinCtor } from './types'; + +export interface HintMethods extends Node { + hint(lines?: MaybeArray, fn?: (h: HintTsDsl) => void): this; +} -export function HintMixin< - TBase extends new (...args: ReadonlyArray) => ITsDsl, ->(Base: TBase) { - const $renderBase = Base.prototype.$render; +export function HintMixin>( + Base: TBase, +) { + abstract class Hint extends Base { + protected _hint?: HintTsDsl; - class Mixin extends Base { - _hint?: HintTsDsl; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } - hint(lines?: MaybeArray, fn?: (h: HintTsDsl) => void): this { + protected hint( + lines?: MaybeArray, + fn?: (h: HintTsDsl) => void, + ): this { this._hint = new HintTsDsl(lines, fn); return this; } - override $render(...args: Parameters) { - const node = $renderBase.apply(this, args); + protected override _render() { + const node = this.$render(); return this._hint ? this._hint.apply(node) : node; } } - // @ts-expect-error - Mixin.prototype.$render.mixin = true; - - return Mixin; + return Hint as unknown as MixinCtor; } - -export type HintMixin = InstanceType>; diff --git a/packages/openapi-ts/src/ts-dsl/mixins/layout.ts b/packages/openapi-ts/src/ts-dsl/mixins/layout.ts index c452802269..8da34667a5 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/layout.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/layout.ts @@ -1,30 +1,54 @@ -export class LayoutMixin { - protected static readonly DEFAULT_THRESHOLD = 3; - protected layout: boolean | number | undefined; +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import type ts from 'typescript'; - /** Sets automatic line output with optional threshold (default: 3). */ - auto(threshold: number = LayoutMixin.DEFAULT_THRESHOLD): this { - this.layout = threshold; - return this; - } +import type { BaseCtor, MixinCtor } from './types'; +export interface LayoutMethods extends Node { + /** Computes whether output should be multiline based on layout setting and element count. */ + $multiline(count: number): boolean; + /** Sets automatic line output with optional threshold (default: 3). */ + auto(threshold?: number): this; /** Sets single line output. */ - inline(): this { - this.layout = false; - return this; - } - + inline(): this; /** Sets multi line output. */ - pretty(): this { - this.layout = true; - return this; - } + pretty(): this; +} - /** Computes whether output should be multiline based on layout setting and element count. */ - protected $multiline(count: number): boolean { - if (this.layout === undefined) { - this.layout = LayoutMixin.DEFAULT_THRESHOLD; +export function LayoutMixin>( + Base: TBase, +) { + abstract class Layout extends Base { + protected static readonly DEFAULT_THRESHOLD = 3; + protected layout: boolean | number | undefined; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected auto(threshold: number = Layout.DEFAULT_THRESHOLD): this { + this.layout = threshold; + return this; + } + + protected inline(): this { + this.layout = false; + return this; + } + + protected pretty(): this { + this.layout = true; + return this; + } + + protected $multiline(count: number): boolean { + if (this.layout === undefined) { + this.layout = Layout.DEFAULT_THRESHOLD; + } + return typeof this.layout === 'number' + ? count >= this.layout + : this.layout; } - return typeof this.layout === 'number' ? count >= this.layout : this.layout; } + + return Layout as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/modifiers.ts b/packages/openapi-ts/src/ts-dsl/mixins/modifiers.ts index 4fc8847b8d..bd008c7d57 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/modifiers.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/modifiers.ts @@ -1,224 +1,387 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; -import type { TsDsl } from '../base'; +import type { BaseCtor, MixinCtor } from './types'; -/** - * Creates an accessor for adding TypeScript modifiers to a parent DSL node. - * - * @param parent - The parent DSL node to which modifiers will be added. - * @returns An object with a `list` method that returns the collected modifiers. - */ -export function createModifierAccessor(parent: Parent) { - const modifiers: Array = []; +export type Modifiers = { + modifiers: Array; +}; +export interface ModifierMethods extends Modifiers { /** * Adds a modifier of the specified kind to the modifiers list if the condition is true. * * @param kind - The syntax kind of the modifier to add. - * @param condition - Whether to add the modifier (default: true). - * @returns The parent DSL node for chaining. + * @param condition - Whether to add the modifier. + * @returns The parent node for chaining. */ - function _m(kind: ts.ModifierSyntaxKind, condition = true): Parent { - if (condition) { - modifiers.push(ts.factory.createModifier(kind)); - } - return parent; - } + _m(kind: ts.ModifierSyntaxKind, condition: boolean): this; +} - Object.assign(parent, { _m }); // attaches to parent +function ModifiersMixin>( + Base: TBase, +) { + abstract class Modifiers extends Base { + protected modifiers: Array = []; - /** - * Returns the list of collected modifiers. - * - * @returns Array of TypeScript modifiers. - */ - function list() { - return modifiers; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected _m(kind: ts.ModifierSyntaxKind, condition: boolean): this { + if (condition) this.modifiers.push(ts.factory.createModifier(kind)); + return this; + } } - return { list }; + return Modifiers as unknown as MixinCtor; } -type Target = object & { - _m?(kind: ts.ModifierSyntaxKind, condition?: boolean): unknown; -}; - -/** - * Mixin that adds an `abstract` modifier to a node. - */ -export class AbstractMixin { +export interface AbstractMethods extends Modifiers { /** * Adds the `abstract` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - abstract(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.AbstractKeyword, condition) as T; - } + abstract(condition?: boolean): this; } /** - * Mixin that adds an `async` modifier to a node. + * Mixin that adds an `abstract` modifier to a node. */ -export class AsyncMixin { +export function AbstractMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Abstract extends Mixed { + protected abstract(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.AbstractKeyword, cond); + } + } + + return Abstract as unknown as MixinCtor; +} + +export interface AsyncMethods extends Modifiers { /** * Adds the `async` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - async(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.AsyncKeyword, condition) as T; - } + async(condition?: boolean): this; } /** - * Mixin that adds a `const` modifier to a node. + * Mixin that adds an `async` modifier to a node. */ -export class ConstMixin { +export function AsyncMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Async extends Mixed { + protected async(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.AsyncKeyword, cond); + } + } + + return Async as unknown as MixinCtor; +} + +export interface ConstMethods extends Modifiers { /** * Adds the `const` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - const(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.ConstKeyword, condition) as T; - } + const(condition?: boolean): this; } /** - * Mixin that adds a `declare` modifier to a node. + * Mixin that adds a `const` modifier to a node. */ -export class DeclareMixin { +export function ConstMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Const extends Mixed { + protected const(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.ConstKeyword, cond); + } + } + + return Const as unknown as MixinCtor; +} + +export interface DeclareMethods extends Modifiers { /** * Adds the `declare` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - declare(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.DeclareKeyword, condition) as T; - } + declare(condition?: boolean): this; } /** - * Mixin that adds a `default` modifier to a node. + * Mixin that adds a `declare` modifier to a node. */ -export class DefaultMixin { +export function DeclareMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Declare extends Mixed { + protected declare(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.DeclareKeyword, cond); + } + } + + return Declare as unknown as MixinCtor; +} + +export interface DefaultMethods extends Modifiers { /** * 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(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.DefaultKeyword, condition) as T; - } + default(condition?: boolean): this; } /** - * Mixin that adds an `export` modifier to a node. + * Mixin that adds a `default` modifier to a node. */ -export class ExportMixin { +export function DefaultMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract 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. + */ + protected default(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.DefaultKeyword, cond); + } + } + + return Default as unknown as MixinCtor; +} + +export interface ExportMethods extends Modifiers { /** * 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(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.ExportKeyword, condition) as T; - } + export(condition?: boolean): this; } /** - * Mixin that adds an `override` modifier to a node. + * Mixin that adds an `export` modifier to a node. */ -export class OverrideMixin { +export function ExportMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract 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. + */ + protected export(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + if (this.symbol) this.symbol.setExported(cond); + return this._m(ts.SyntaxKind.ExportKeyword, cond); + } + } + + return Export as unknown as MixinCtor; +} + +export interface OverrideMethods extends Modifiers { /** * Adds the `override` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - override(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.OverrideKeyword, condition) as T; - } + override(condition?: boolean): this; } /** - * Mixin that adds a `private` modifier to a node. + * Mixin that adds an `override` modifier to a node. */ -export class PrivateMixin { +export function OverrideMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Override extends Mixed { + protected override(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.OverrideKeyword, cond); + } + } + + return Override as unknown as MixinCtor; +} + +export interface PrivateMethods extends Modifiers { /** * Adds the `private` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - private(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.PrivateKeyword, condition) as T; - } + private(condition?: boolean): this; } /** - * Mixin that adds a `protected` modifier to a node. + * Mixin that adds a `private` modifier to a node. */ -export class ProtectedMixin { +export function PrivateMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Private extends Mixed { + protected private(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.PrivateKeyword, cond); + } + } + + return Private as unknown as MixinCtor; +} + +export interface ProtectedMethods extends Modifiers { /** * Adds the `protected` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - protected(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.ProtectedKeyword, condition) as T; - } + protected(condition?: boolean): this; } /** - * Mixin that adds a `public` modifier to a node. + * Mixin that adds a `protected` modifier to a node. */ -export class PublicMixin { +export function ProtectedMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Protected extends Mixed { + protected protected(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.ProtectedKeyword, cond); + } + } + + return Protected as unknown as MixinCtor; +} + +export interface PublicMethods extends Modifiers { /** * Adds the `public` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - public(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.PublicKeyword, condition) as T; - } + public(condition?: boolean): this; } /** - * Mixin that adds a `readonly` modifier to a node. + * Mixin that adds a `public` modifier to a node. */ -export class ReadonlyMixin { +export function PublicMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Public extends Mixed { + protected public(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.PublicKeyword, cond); + } + } + + return Public as unknown as MixinCtor; +} + +export interface ReadonlyMethods extends Modifiers { /** * Adds the `readonly` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - readonly(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.ReadonlyKeyword, condition) as T; - } + readonly(condition?: boolean): this; } /** - * Mixin that adds a `static` modifier to a node. + * Mixin that adds a `readonly` modifier to a node. */ -export class StaticMixin { +export function ReadonlyMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Readonly extends Mixed { + protected readonly(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.ReadonlyKeyword, cond); + } + } + + return Readonly as unknown as MixinCtor; +} + +export interface StaticMethods extends Modifiers { /** * Adds the `static` keyword modifier if the condition is true. * * @param condition - Whether to add the modifier (default: true). * @returns The target object for chaining. */ - static(this: T, condition: boolean = true): T { - return this._m!(ts.SyntaxKind.StaticKeyword, condition) as T; + static(condition?: boolean): this; +} + +/** + * Mixin that adds a `static` modifier to a node. + */ +export function StaticMixin>( + Base: TBase, +) { + const Mixed = ModifiersMixin(Base as BaseCtor); + + abstract class Static extends Mixed { + protected static(condition?: boolean): this { + const cond = arguments.length === 0 ? true : Boolean(condition); + return this._m(ts.SyntaxKind.StaticKeyword, cond); + } } + + return Static as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/note.ts b/packages/openapi-ts/src/ts-dsl/mixins/note.ts index 72f2901cbe..c9dfe971ee 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/note.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/note.ts @@ -1,29 +1,37 @@ -import type { ITsDsl, MaybeArray } from '../base'; +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import type ts from 'typescript'; + +import type { MaybeArray } from '../base'; import { NoteTsDsl } from '../layout/note'; +import type { BaseCtor, MixinCtor } from './types'; + +export interface NoteMethods extends Node { + note(lines?: MaybeArray, fn?: (h: NoteTsDsl) => void): this; +} -export function NoteMixin< - TBase extends new (...args: ReadonlyArray) => ITsDsl, ->(Base: TBase) { - const $renderBase = Base.prototype.$render; +export function NoteMixin>( + Base: TBase, +) { + abstract class Note extends Base { + protected _note?: NoteTsDsl; - class Mixin extends Base { - _note?: NoteTsDsl; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } - note(lines?: MaybeArray, fn?: (h: NoteTsDsl) => void): this { + protected note( + lines?: MaybeArray, + fn?: (h: NoteTsDsl) => void, + ): this { this._note = new NoteTsDsl(lines, fn); return this; } - override $render(...args: Parameters) { - const node = $renderBase.apply(this, args); + protected override _render() { + const node = this.$render(); return this._note ? this._note.apply(node) : node; } } - // @ts-expect-error - Mixin.prototype.$render.mixin = true; - - return Mixin; + return Note as unknown as MixinCtor; } - -export type NoteMixin = InstanceType>; diff --git a/packages/openapi-ts/src/ts-dsl/mixins/operator.ts b/packages/openapi-ts/src/ts-dsl/mixins/operator.ts index ca0491476d..2bc7de200c 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/operator.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/operator.ts @@ -1,89 +1,120 @@ +import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; import type { MaybeTsDsl } from '../base'; import { BinaryTsDsl } from '../expr/binary'; +import type { BaseCtor, MixinCtor } from './types'; -type This = string | MaybeTsDsl; -type Expr = string | MaybeTsDsl; +type Expr = Symbol | string | MaybeTsDsl; -export class OperatorMixin { +export interface OperatorMethods extends Node { /** Logical AND — `this && expr` */ - and(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).and(expr); - } - + and(expr: Expr): BinaryTsDsl; /** Creates an assignment expression (e.g. `this = expr`). */ - assign(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this, '=', expr); - } - + assign(expr: Expr): BinaryTsDsl; /** Nullish coalescing — `this ?? expr` */ - coalesce(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).coalesce(expr); - } - + coalesce(expr: Expr): BinaryTsDsl; /** Division — `this / expr` */ - div(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).div(expr); - } - + div(expr: Expr): BinaryTsDsl; /** Strict equality — `this === expr` */ - eq(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).eq(expr); - } - + eq(expr: Expr): BinaryTsDsl; /** Greater than — `this > expr` */ - gt(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).gt(expr); - } - + gt(expr: Expr): BinaryTsDsl; /** Greater than or equal — `this >= expr` */ - gte(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).gte(expr); - } - + gte(expr: Expr): BinaryTsDsl; /** Loose equality — `this == expr` */ - looseEq(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).looseEq(expr); - } - + looseEq(expr: Expr): BinaryTsDsl; /** Loose inequality — `this != expr` */ - looseNeq(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).looseNeq(expr); - } - + looseNeq(expr: Expr): BinaryTsDsl; /** Less than — `this < expr` */ - lt(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).lt(expr); - } - + lt(expr: Expr): BinaryTsDsl; /** Less than or equal — `this <= expr` */ - lte(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).lte(expr); - } - + lte(expr: Expr): BinaryTsDsl; /** Subtraction — `this - expr` */ - minus(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).minus(expr); - } - + minus(expr: Expr): BinaryTsDsl; /** Strict inequality — `this !== expr` */ - neq(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).neq(expr); - } - + neq(expr: Expr): BinaryTsDsl; /** Logical OR — `this || expr` */ - or(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).or(expr); - } - + or(expr: Expr): BinaryTsDsl; /** Addition — `this + expr` */ - plus(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).plus(expr); - } - + plus(expr: Expr): BinaryTsDsl; /** Multiplication — `this * expr` */ - times(this: This, expr: Expr): BinaryTsDsl { - return new BinaryTsDsl(this).times(expr); + times(expr: Expr): BinaryTsDsl; +} + +export function OperatorMixin< + T extends ts.Expression, + TBase extends BaseCtor, +>(Base: TBase) { + abstract class Operator extends Base { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected and(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).and(expr); + } + + protected assign(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this, '=', expr); + } + + protected coalesce(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).coalesce(expr); + } + + protected div(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).div(expr); + } + + protected eq(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).eq(expr); + } + + protected gt(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).gt(expr); + } + + protected gte(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).gte(expr); + } + + protected looseEq(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).looseEq(expr); + } + + protected looseNeq(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).looseNeq(expr); + } + + protected lt(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).lt(expr); + } + + protected lte(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).lte(expr); + } + + protected minus(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).minus(expr); + } + + protected neq(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).neq(expr); + } + + protected or(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).or(expr); + } + + protected plus(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).plus(expr); + } + + protected times(expr: Expr): BinaryTsDsl { + return new BinaryTsDsl(this).times(expr); + } } + + return Operator as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/optional.ts b/packages/openapi-ts/src/ts-dsl/mixins/optional.ts index 257790eb45..3a89c3acdd 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/optional.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/optional.ts @@ -1,15 +1,36 @@ -export class OptionalMixin { - protected _optional?: boolean; +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; +import type ts from 'typescript'; - /** Marks the node as optional when the condition is true. */ - optional(this: T, condition?: boolean): T { - this._optional = arguments.length === 0 ? true : Boolean(condition); - return this; - } +import type { BaseCtor, MixinCtor } from './types'; +export interface OptionalMethods extends Node { + _optional?: boolean; + /** Marks the node as optional when the condition is true. */ + optional(condition?: boolean): this; /** Marks the node as required when the condition is true. */ - required(this: T, condition?: boolean): T { - this._optional = arguments.length === 0 ? false : !condition; - return this; + required(condition?: boolean): this; +} + +export function OptionalMixin>( + Base: TBase, +) { + abstract class Optional extends Base { + protected _optional?: boolean; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected optional(condition?: boolean): this { + this._optional = arguments.length === 0 ? true : Boolean(condition); + return this; + } + + protected required(condition?: boolean): this { + this._optional = arguments.length === 0 ? false : !condition; + return this; + } } + + return Optional as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/param.ts b/packages/openapi-ts/src/ts-dsl/mixins/param.ts index 3372d63684..f50e89b140 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/param.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/param.ts @@ -1,34 +1,55 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import { type MaybeTsDsl, TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; import { ParamTsDsl } from '../decl/param'; +import type { BaseCtor, MixinCtor } from './types'; -export class ParamMixin extends TsDsl { - protected _params?: Array>; - +export interface ParamMethods extends Node { + /** Renders the parameters into an array of `ParameterDeclaration`s. */ + $params(): ReadonlyArray; /** Adds a parameter. */ param( name: string | ((p: ParamTsDsl) => void), fn?: (p: ParamTsDsl) => void, - ): this { - const p = new ParamTsDsl(name, fn); - (this._params ??= []).push(p); - return this; - } - + ): this; /** Adds multiple parameters. */ - params(...params: ReadonlyArray>): this { - (this._params ??= []).push(...params); - return this; - } + params(...params: ReadonlyArray>): this; +} - /** Renders the parameters into an array of `ParameterDeclaration`s. */ - protected $params(): ReadonlyArray { - if (!this._params) return []; - return this.$node(this._params); - } +export function ParamMixin>( + Base: TBase, +) { + abstract class Param extends Base { + protected _params: Array> = []; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const p of this._params) { + if (isTsDsl(p)) p.analyze(ctx); + } + } - $render(): ts.Node { - throw new Error('noop'); + protected param( + name: string | ((p: ParamTsDsl) => void), + fn?: (p: ParamTsDsl) => void, + ): this { + const p = new ParamTsDsl(name, fn); + this._params.push(p); + return this; + } + + protected params( + ...params: ReadonlyArray> + ): this { + this._params.push(...params); + return this; + } + + protected $params(): ReadonlyArray { + return this.$node(this._params); + } } + + return Param as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/pattern.ts b/packages/openapi-ts/src/ts-dsl/mixins/pattern.ts index d479437524..408a69d36d 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/pattern.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/pattern.ts @@ -1,41 +1,63 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeArray } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeArray } from '../base'; import { PatternTsDsl } from '../decl/pattern'; +import type { BaseCtor, MixinCtor } from './types'; + +export interface PatternMethods extends Node { + /** Renders the pattern into a `BindingName`. */ + $pattern(): ts.BindingName | undefined; + /** Defines an array binding pattern. */ + array(...props: ReadonlyArray | [ReadonlyArray]): this; + /** Defines an object binding pattern. */ + object( + ...props: ReadonlyArray | Record> + ): this; + /** Adds a spread element (e.g. `...args`, `...options`) to the pattern. */ + spread(name: string): this; +} /** * Mixin providing `.array()`, `.object()`, and `.spread()` methods for defining destructuring patterns. */ -export class PatternMixin extends TsDsl { - protected pattern?: PatternTsDsl; +export function PatternMixin>( + Base: TBase, +) { + abstract class Pattern extends Base { + protected pattern?: PatternTsDsl; - /** Defines an array binding pattern. */ - array(...props: ReadonlyArray | [ReadonlyArray]): this { - (this.pattern ??= new PatternTsDsl()).array(...props); - return this; - } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this.pattern)) this.pattern.analyze(ctx); + } - /** Defines an object binding pattern. */ - object( - ...props: ReadonlyArray | Record> - ): this { - (this.pattern ??= new PatternTsDsl()).object(...props); - return this; - } + protected array( + ...props: ReadonlyArray | [ReadonlyArray] + ): this { + (this.pattern ??= new PatternTsDsl()).array(...props); + return this; + } - /** Adds a spread element (e.g. `...args`, `...options`) to the pattern. */ - spread(name: string): this { - (this.pattern ??= new PatternTsDsl()).spread(name); - return this; - } + protected object( + ...props: ReadonlyArray | Record> + ): this { + (this.pattern ??= new PatternTsDsl()).object(...props); + return this; + } - /** Renders the pattern into a `BindingName`. */ - protected $pattern(): ts.BindingName | undefined { - return this.$node(this.pattern); - } + /** Adds a spread element (e.g. `...args`, `...options`) to the pattern. */ + protected spread(name: string): this { + (this.pattern ??= new PatternTsDsl()).spread(name); + return this; + } - $render(): ts.Node { - throw new Error('noop'); + /** Renders the pattern into a `BindingName`. */ + protected $pattern(): ts.BindingName | undefined { + if (!this.pattern) return; + return this.$node(this.pattern); + } } + + return Pattern as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/type-args.ts b/packages/openapi-ts/src/ts-dsl/mixins/type-args.ts index 146bb9ba0d..e2ae62ea67 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/type-args.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/type-args.ts @@ -1,29 +1,53 @@ +import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; import type { MaybeTsDsl, TypeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl } from '../base'; +import type { BaseCtor, MixinCtor } from './types'; -export class TypeArgsMixin extends TsDsl { - protected _generics?: Array>; +type Arg = Symbol | string | MaybeTsDsl; +export interface TypeArgsMethods extends Node { + /** Returns the type arguments as an array of ts.TypeNode nodes. */ + $generics(): ReadonlyArray | undefined; /** Adds a single type argument (e.g. `string` in `Foo`). */ - generic(arg: string | MaybeTsDsl): this { - (this._generics ??= []).push(arg); - return this; - } - + generic(arg: Arg): this; /** Adds type arguments (e.g. `Map`). */ - generics(...args: ReadonlyArray>): this { - (this._generics ??= []).push(...args); - return this; - } + generics(...args: ReadonlyArray): this; +} - /** Returns the type arguments as an array of ts.TypeNode nodes. */ - protected $generics(): ReadonlyArray | undefined { - return this.$type(this._generics); - } +export function TypeArgsMixin>( + Base: TBase, +) { + abstract class TypeArgs extends Base { + protected _generics: Array = []; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const g of this._generics) { + if (isSymbol(g)) { + ctx.addDependency(g); + } else if (isTsDsl(g)) { + g.analyze(ctx); + } + } + } + + protected generic(arg: Arg): this { + this._generics.push(arg); + return this; + } - $render(): ts.Node { - throw new Error('noop'); + protected generics(...args: ReadonlyArray): this { + this._generics.push(...args); + return this; + } + + protected $generics(): ReadonlyArray | undefined { + return this.$type(this._generics); + } } + + return TypeArgs as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/type-expr.ts b/packages/openapi-ts/src/ts-dsl/mixins/type-expr.ts index 7ded21cedf..8f5d558fda 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/type-expr.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/type-expr.ts @@ -1,12 +1,14 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import ts from 'typescript'; -import type { MaybeTsDsl, TypeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import type { MaybeTsDsl, TsDsl, TypeTsDsl } from '../base'; +import { isTsDsl } from '../base'; import type { TypeOfExprTsDsl } from '../expr/typeof'; import type { TypeExprTsDsl } from '../type/expr'; import type { TypeIdxTsDsl } from '../type/idx'; import type { TypeOperatorTsDsl } from '../type/operator'; import type { TypeQueryTsDsl } from '../type/query'; +import type { BaseCtor, MixinCtor } from './types'; type TypeExprFactory = ( nameOrFn?: string | ((t: TypeExprTsDsl) => void), @@ -63,40 +65,18 @@ export function registerLazyAccessTypeQueryFactory( typeQueryFactory = factory; } -export class TypeExprMixin { +export interface TypeExprMethods extends Node { /** Creates an indexed-access type (e.g. `Foo[K]`). */ idx( this: MaybeTsDsl, index: string | number | MaybeTsDsl, - ): TypeIdxTsDsl { - return typeIdxFactory!(this, index); - } - + ): TypeIdxTsDsl; /** Shorthand: builds `keyof T`. */ - keyof(this: MaybeTsDsl): TypeOperatorTsDsl { - return typeOperatorFactory!().keyof(this); - } - + keyof(this: MaybeTsDsl): TypeOperatorTsDsl; /** Shorthand: builds `readonly T`. */ - readonly(this: MaybeTsDsl): TypeOperatorTsDsl { - return typeOperatorFactory!().readonly(this); - } - - /** Create a TypeExpr DSL node representing ReturnType. */ - returnType(this: MaybeTsDsl): TypeExprTsDsl { - return typeExprFactory!('ReturnType').generic(typeQueryFactory!(this)); - } - - /** Create a TypeOfExpr DSL node representing typeof this. */ - typeofExpr(this: MaybeTsDsl): TypeOfExprTsDsl { - return typeOfExprFactory!(this); - } - - /** Create a TypeQuery DSL node representing typeof this. */ - typeofType(this: MaybeTsDsl): TypeQueryTsDsl { - return typeQueryFactory!(this); - } - + readonly(this: MaybeTsDsl): TypeOperatorTsDsl; + /** Create a TypeExpr node representing ReturnType. */ + returnType(this: MaybeTsDsl): TypeExprTsDsl; /** * Create a `typeof` operator that narrows its return type based on the receiver. * @@ -111,23 +91,80 @@ export class TypeExprMixin { ? TypeOfExprTsDsl : T extends MaybeTsDsl ? TypeQueryTsDsl - : TypeQueryTsDsl | TypeOfExprTsDsl { - if (this instanceof TsDsl) { - const node = this.$render(); - return ts.isExpression(node) - ? (typeOfExprFactory!(this) as any) - : (typeQueryFactory!(this) as any); + : TypeQueryTsDsl | TypeOfExprTsDsl; + /** Create a TypeOfExpr node representing typeof this. */ + typeofExpr(this: MaybeTsDsl): TypeOfExprTsDsl; + /** Create a TypeQuery node representing typeof this. */ + typeofType(this: MaybeTsDsl): TypeQueryTsDsl; + /** Shorthand: builds `unique T`. */ + unique(this: MaybeTsDsl): TypeOperatorTsDsl; +} + +export function TypeExprMixin>( + Base: TBase, +) { + abstract class TypeExpr extends Base { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); } - if (ts.isExpression(this as any)) { - return typeOfExprFactory!(this as ts.Expression) as any; + protected idx( + this: TypeTsDsl, + index: string | number | MaybeTsDsl, + ): TypeIdxTsDsl { + return typeIdxFactory!(this, index); } - return typeQueryFactory!(this) as any; - } + protected keyof(this: TypeTsDsl): TypeOperatorTsDsl { + return typeOperatorFactory!().keyof(this); + } - /** Shorthand: builds `unique T`. */ - unique(this: MaybeTsDsl): TypeOperatorTsDsl { - return typeOperatorFactory!().unique(this); + protected readonly(this: TypeTsDsl): TypeOperatorTsDsl { + return typeOperatorFactory!().readonly(this); + } + + protected returnType(this: TsDsl): TypeExprTsDsl { + return typeExprFactory!('ReturnType').generic(typeQueryFactory!(this)); + } + + protected typeofExpr(this: TsDsl): TypeOfExprTsDsl { + return typeOfExprFactory!(this); + } + + protected typeofType( + this: TypeTsDsl | TsDsl, + ): TypeQueryTsDsl { + return typeQueryFactory!(this); + } + + protected typeof>( + this: T, + ): T extends TsDsl + ? TypeOfExprTsDsl + : T extends TypeTsDsl + ? TypeQueryTsDsl + : TypeQueryTsDsl | TypeOfExprTsDsl { + if (isTsDsl(this)) { + // @ts-expect-error + const node = this._render(); + return ( + ts.isExpression(node) + ? typeOfExprFactory!(this as any) + : typeQueryFactory!(this) + ) as any; + } + + if (ts.isExpression(this as any)) { + return typeOfExprFactory!(this as ts.Expression) as any; + } + + return typeQueryFactory!(this) as any; + } + + protected unique(this: TypeTsDsl): TypeOperatorTsDsl { + return typeOperatorFactory!().unique(this); + } } + + return TypeExpr as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/type-params.ts b/packages/openapi-ts/src/ts-dsl/mixins/type-params.ts index bd497ecf54..afec41f659 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/type-params.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/type-params.ts @@ -1,36 +1,61 @@ +import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; import { TypeParamTsDsl } from '../type/param'; +import type { BaseCtor, MixinCtor } from './types'; -export class TypeParamsMixin extends TsDsl { - protected _generics?: Array>; - +export interface TypeParamsMethods extends Node { + /** Returns the type parameters as an array of ts.TypeParameterDeclaration nodes. */ + $generics(): ReadonlyArray | undefined; /** Adds a single type parameter (e.g. `T` in `Array`). */ - generic(...args: ConstructorParameters): this { - const g = new TypeParamTsDsl(...args); - (this._generics ??= []).push(g); - return this; - } - + generic(...args: ConstructorParameters): this; /** Adds type parameters (e.g. `Map`). */ - generics(...args: ReadonlyArray>): this { - (this._generics ??= []).push(...args); - return this; - } + generics( + ...args: ReadonlyArray> + ): this; +} - /** Returns the type parameters as an array of ts.TypeParameterDeclaration nodes. */ - protected $generics(): - | ReadonlyArray - | undefined { - return this._generics?.map((g) => { - const node = typeof g === 'string' ? new TypeParamTsDsl(g) : g; - return this.$node(node); - }); - } +export function TypeParamsMixin>( + Base: TBase, +) { + abstract class TypeParams extends Base { + protected _generics: Array> = []; + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const g of this._generics) { + if (isTsDsl(g)) g.analyze(ctx); + } + } - $render(): ts.Node { - throw new Error('noop'); + protected generic( + ...args: ConstructorParameters + ): this { + const g = new TypeParamTsDsl(...args); + this._generics.push(g); + return this; + } + + protected generics( + ...args: ReadonlyArray> + ): this { + for (let arg of args) { + if (typeof arg === 'string' || isSymbol(arg)) { + arg = new TypeParamTsDsl(arg); + } + this._generics.push(arg); + } + return this; + } + + protected $generics(): + | ReadonlyArray + | undefined { + return this.$node(this._generics); + } } + + return TypeParams as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/mixins/types.d.ts b/packages/openapi-ts/src/ts-dsl/mixins/types.d.ts new file mode 100644 index 0000000000..ce642c48dd --- /dev/null +++ b/packages/openapi-ts/src/ts-dsl/mixins/types.d.ts @@ -0,0 +1,6 @@ +import type { TsDsl } from '../base'; + +export type BaseCtor = abstract new (...args: any[]) => TsDsl; +export type MixinCtor, K> = abstract new ( + ...args: any[] +) => InstanceType & K; diff --git a/packages/openapi-ts/src/ts-dsl/mixins/value.ts b/packages/openapi-ts/src/ts-dsl/mixins/value.ts index 02baf96849..06e0a7caad 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/value.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/value.ts @@ -1,22 +1,37 @@ +import type { AnalysisContext, Node } from '@hey-api/codegen-core'; import type ts from 'typescript'; -import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, type MaybeTsDsl } from '../base'; +import type { BaseCtor, MixinCtor } from './types'; -export class ValueMixin extends TsDsl { - protected value?: string | MaybeTsDsl; +export type ValueExpr = string | MaybeTsDsl; +export interface ValueMethods extends Node { + $value(): ts.Expression | undefined; /** Sets the initializer expression (e.g. `= expr`). */ - assign(this: T, expr: string | MaybeTsDsl): T { - this.value = expr; - return this; - } + assign(expr: ValueExpr): this; +} - protected $value(): ts.Expression | undefined { - return this.$node(this.value); - } +export function ValueMixin>( + Base: TBase, +) { + abstract class Value extends Base { + protected value?: ValueExpr; - $render(): ts.Node { - throw new Error('noop'); + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this.value)) this.value.analyze(ctx); + } + + protected assign(expr: ValueExpr): this { + this.value = expr; + return this; + } + + protected $value(): ts.Expression | undefined { + return this.$node(this.value); + } } + + return Value as unknown as MixinCtor; } diff --git a/packages/openapi-ts/src/ts-dsl/stmt/if.ts b/packages/openapi-ts/src/ts-dsl/stmt/if.ts index 7f87660884..857da8bd8a 100644 --- a/packages/openapi-ts/src/ts-dsl/stmt/if.ts +++ b/packages/openapi-ts/src/ts-dsl/stmt/if.ts @@ -1,12 +1,13 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { DoMixin } from '../mixins/do'; -export class IfTsDsl extends TsDsl { +const Mixed = DoMixin(TsDsl); + +export class IfTsDsl extends Mixed { protected _condition?: string | MaybeTsDsl; protected _else?: ReadonlyArray>; @@ -15,6 +16,16 @@ export class IfTsDsl extends TsDsl { if (condition) this.condition(condition); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._condition)) this._condition.analyze(ctx); + if (this._else) { + for (const stmt of this._else) { + if (isTsDsl(stmt)) stmt.analyze(ctx); + } + } + } + condition(condition: string | MaybeTsDsl): this { this._condition = condition; return this; @@ -25,7 +36,7 @@ export class IfTsDsl extends TsDsl { return this; } - $render(): ts.IfStatement { + protected override _render() { if (!this._condition) throw new Error('Missing condition in if'); const thenStmts = this.$do(); @@ -55,6 +66,3 @@ export class IfTsDsl extends TsDsl { return ts.factory.createIfStatement(condition, thenNode, elseNode); } } - -export interface IfTsDsl extends DoMixin {} -mixin(IfTsDsl, DoMixin); diff --git a/packages/openapi-ts/src/ts-dsl/stmt/return.ts b/packages/openapi-ts/src/ts-dsl/stmt/return.ts index 483f96abda..1d4e960499 100644 --- a/packages/openapi-ts/src/ts-dsl/stmt/return.ts +++ b/packages/openapi-ts/src/ts-dsl/stmt/return.ts @@ -1,25 +1,36 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; -import { ExprMixin, registerLazyAccessReturnFactory } from '../mixins/expr'; +import { isTsDsl, TsDsl } from '../base'; +import { setReturnFactory } from '../mixins/expr'; -export class ReturnTsDsl extends TsDsl { - protected _returnExpr?: string | MaybeTsDsl; +export type ReturnExpr = Symbol | string | MaybeTsDsl; +export type ReturnCtor = (expr?: ReturnExpr) => ReturnTsDsl; - constructor(expr?: string | MaybeTsDsl) { +const Mixed = TsDsl; + +export class ReturnTsDsl extends Mixed { + protected _returnExpr?: ReturnExpr; + + constructor(expr?: ReturnExpr) { super(); this._returnExpr = expr; } - $render(): ts.ReturnStatement { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._returnExpr)) { + ctx.addDependency(this._returnExpr); + } else if (isTsDsl(this._returnExpr)) { + this._returnExpr.analyze(ctx); + } + } + + protected override _render() { return ts.factory.createReturnStatement(this.$node(this._returnExpr)); } } -export interface ReturnTsDsl extends ExprMixin {} -mixin(ReturnTsDsl, ExprMixin); - -registerLazyAccessReturnFactory((...args) => new ReturnTsDsl(...args)); +setReturnFactory((...args) => new ReturnTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/stmt/stmt.ts b/packages/openapi-ts/src/ts-dsl/stmt/stmt.ts index 425b5eafa3..c61995f59c 100644 --- a/packages/openapi-ts/src/ts-dsl/stmt/stmt.ts +++ b/packages/openapi-ts/src/ts-dsl/stmt/stmt.ts @@ -1,8 +1,11 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; -export class StmtTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class StmtTsDsl extends Mixed { protected _inner: ts.Expression | ts.Statement | TsDsl; constructor(inner: ts.Expression | ts.Statement | TsDsl) { @@ -10,7 +13,12 @@ export class StmtTsDsl extends TsDsl { this._inner = inner; } - $render(): ts.Statement { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._inner)) this._inner.analyze(ctx); + } + + protected override _render() { const node = this.$node(this._inner); return ts.isStatement(node) ? node diff --git a/packages/openapi-ts/src/ts-dsl/stmt/throw.ts b/packages/openapi-ts/src/ts-dsl/stmt/throw.ts index 16ef8cb650..51df159c9e 100644 --- a/packages/openapi-ts/src/ts-dsl/stmt/throw.ts +++ b/packages/openapi-ts/src/ts-dsl/stmt/throw.ts @@ -1,10 +1,13 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; +import { isTsDsl, TsDsl } from '../base'; import { LiteralTsDsl } from '../expr/literal'; -export class ThrowTsDsl extends TsDsl { +const Mixed = TsDsl; + +export class ThrowTsDsl extends Mixed { protected error: string | MaybeTsDsl; protected msg?: string | MaybeTsDsl; protected useNew: boolean; @@ -15,15 +18,21 @@ export class ThrowTsDsl extends TsDsl { this.useNew = useNew; } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this.error)) this.error.analyze(ctx); + if (isTsDsl(this.msg)) this.msg.analyze(ctx); + } + message(value: string | MaybeTsDsl): this { this.msg = value; return this; } - $render(): ts.ThrowStatement { + protected override _render() { const errorNode = this.$node(this.error); const messageNode = this.$node(this.msg ? [this.msg] : []).map((expr) => - typeof expr === 'string' ? new LiteralTsDsl(expr).$render() : expr, + typeof expr === 'string' ? this.$node(new LiteralTsDsl(expr)) : expr, ); if (this.useNew) { return ts.factory.createThrowStatement( diff --git a/packages/openapi-ts/src/ts-dsl/stmt/var.ts b/packages/openapi-ts/src/ts-dsl/stmt/var.ts index 14e33d14b8..f49767e174 100644 --- a/packages/openapi-ts/src/ts-dsl/stmt/var.ts +++ b/packages/openapi-ts/src/ts-dsl/stmt/var.ts @@ -1,28 +1,43 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TsDsl, TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DocMixin } from '../mixins/doc'; import { HintMixin } from '../mixins/hint'; -import { - createModifierAccessor, - DefaultMixin, - ExportMixin, -} from '../mixins/modifiers'; +import { DefaultMixin, ExportMixin } from '../mixins/modifiers'; import { PatternMixin } from '../mixins/pattern'; import { ValueMixin } from '../mixins/value'; import { TypeExprTsDsl } from '../type/expr'; -export class VarTsDsl extends TsDsl { +export type VarName = Symbol | string; + +const Mixed = DefaultMixin( + DocMixin( + ExportMixin( + HintMixin(PatternMixin(ValueMixin(TsDsl))), + ), + ), +); + +export class VarTsDsl extends Mixed { protected kind: ts.NodeFlags = ts.NodeFlags.None; - protected modifiers = createModifierAccessor(this); - protected name?: string; + protected name?: VarName; protected _type?: TypeTsDsl; - constructor(name?: string) { + constructor(name?: VarName) { super(); this.name = name; + if (isSymbol(name)) { + name.setKind('var'); + name.setNode(this); + } + } + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.name)) ctx.addDependency(this.name); + this._type?.analyze(ctx); } const(): this { @@ -46,15 +61,16 @@ export class VarTsDsl extends TsDsl { return this; } - $render(): ts.VariableStatement { - const name = this.$pattern() ?? this.name; + protected override _render() { + const name = this.$pattern() ?? this.$node(this.name); if (!name) throw new Error('Var must have either a name or a destructuring pattern'); return ts.factory.createVariableStatement( - this.modifiers.list(), + this.modifiers, ts.factory.createVariableDeclarationList( [ ts.factory.createVariableDeclaration( + // @ts-expect-error need to improve types name, undefined, this.$type(this._type), @@ -66,20 +82,3 @@ export class VarTsDsl extends TsDsl { ); } } - -export interface VarTsDsl - extends DefaultMixin, - DocMixin, - ExportMixin, - HintMixin, - PatternMixin, - ValueMixin {} -mixin( - VarTsDsl, - DefaultMixin, - DocMixin, - ExportMixin, - HintMixin, - PatternMixin, - ValueMixin, -); diff --git a/packages/openapi-ts/src/ts-dsl/token.ts b/packages/openapi-ts/src/ts-dsl/token.ts index 996fab607a..f4fef733d7 100644 --- a/packages/openapi-ts/src/ts-dsl/token.ts +++ b/packages/openapi-ts/src/ts-dsl/token.ts @@ -55,8 +55,7 @@ export class TokenTsDsl extends TsDsl< ); } - /** Renders the final token */ - $render(): ts.Token { + protected override _render(): ts.Token { if (!this._kind) { throw new Error(`Token missing \`.kind(kind)\``); } diff --git a/packages/openapi-ts/src/ts-dsl/type/alias.ts b/packages/openapi-ts/src/ts-dsl/type/alias.ts index 4fa0885efc..cad3226672 100644 --- a/packages/openapi-ts/src/ts-dsl/type/alias.ts +++ b/packages/openapi-ts/src/ts-dsl/type/alias.ts @@ -1,45 +1,55 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TsDsl } from '../base'; import { DocMixin } from '../mixins/doc'; -import { createModifierAccessor, ExportMixin } from '../mixins/modifiers'; +import { ExportMixin } from '../mixins/modifiers'; import { TypeParamsMixin } from '../mixins/type-params'; -export class TypeAliasTsDsl extends TsDsl { - protected value?: MaybeTsDsl; - protected modifiers = createModifierAccessor(this); - protected name: string; +type Name = Symbol | string; +type Value = MaybeTsDsl; - constructor(name: string, fn?: (t: TypeAliasTsDsl) => void) { +const Mixed = DocMixin( + ExportMixin(TypeParamsMixin(TsDsl)), +); + +export class TypeAliasTsDsl extends Mixed { + protected name: Name; + protected value?: Value; + + constructor(name: Name, fn?: (t: TypeAliasTsDsl) => void) { super(); this.name = name; + if (isSymbol(name)) { + name.setKind('type'); + name.setNode(this); + } fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.name)) ctx.addDependency(this.name); + if (isTsDsl(this.value)) this.value.analyze(ctx); + } + /** Sets the type expression on the right-hand side of `= ...`. */ - type(node: MaybeTsDsl): this { + type(node: Value): this { this.value = node; return this; } - /** Renders a `TypeAliasDeclaration` node. */ - $render(): ts.TypeAliasDeclaration { + protected override _render() { if (!this.value) throw new Error(`Type alias '${this.name}' is missing a type definition`); return ts.factory.createTypeAliasDeclaration( - this.modifiers.list(), - this.name, + this.modifiers, + // @ts-expect-error need to improve types + this.$node(this.name), this.$generics(), this.$type(this.value), ); } } - -export interface TypeAliasTsDsl - extends DocMixin, - ExportMixin, - TypeParamsMixin {} -mixin(TypeAliasTsDsl, DocMixin, ExportMixin, TypeParamsMixin); diff --git a/packages/openapi-ts/src/ts-dsl/type/and.ts b/packages/openapi-ts/src/ts-dsl/type/and.ts index fbbb0085e2..59fe23fad4 100644 --- a/packages/openapi-ts/src/ts-dsl/type/and.ts +++ b/packages/openapi-ts/src/ts-dsl/type/and.ts @@ -1,21 +1,38 @@ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; -export class TypeAndTsDsl extends TypeTsDsl { - protected _types: Array = []; +type Type = Symbol | string | ts.TypeNode | TypeTsDsl; - constructor(...nodes: Array) { +const Mixed = TypeTsDsl; + +export class TypeAndTsDsl extends Mixed { + protected _types: Array = []; + + constructor(...nodes: Array) { super(); this.types(...nodes); } - types(...nodes: Array): this { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const t of this._types) { + if (isSymbol(t)) { + ctx.addDependency(t); + } else if (isTsDsl(t)) { + t.analyze(ctx); + } + } + } + + types(...nodes: Array): this { this._types.push(...nodes); return this; } - $render(): ts.IntersectionTypeNode { + protected override _render() { const flat: Array = []; for (const n of this._types) { diff --git a/packages/openapi-ts/src/ts-dsl/type/attr.ts b/packages/openapi-ts/src/ts-dsl/type/attr.ts index d04a64e0de..671fe388ca 100644 --- a/packages/openapi-ts/src/ts-dsl/type/attr.ts +++ b/packages/openapi-ts/src/ts-dsl/type/attr.ts @@ -1,40 +1,54 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { TypeExprMixin } from '../mixins/type-expr'; -export class TypeAttrTsDsl extends TypeTsDsl { - protected _base?: string | MaybeTsDsl; - protected right: string | ts.Identifier; - - constructor( - base: string | MaybeTsDsl, - right: string | ts.Identifier, - ); - constructor(right: string | ts.Identifier); - constructor( - baseOrRight: string | MaybeTsDsl, - maybeRight?: string | ts.Identifier, - ) { +type Base = Symbol | string | MaybeTsDsl; +type Right = Symbol | string | ts.Identifier; + +const Mixed = TypeExprMixin(TypeTsDsl); + +export class TypeAttrTsDsl extends Mixed { + protected _base?: Base; + protected _right!: Right; + + constructor(base: Base, right: string | ts.Identifier); + constructor(right: Right); + constructor(base: Base, right?: Right) { super(); - if (maybeRight) { - this.base(baseOrRight); - this.right = maybeRight; + if (right) { + this.base(base); + this.right(right); } else { - this.base(undefined); - this.right = baseOrRight as string | ts.Identifier; + this.base(); + this.right(base as Right); + } + } + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._base)) { + ctx.addDependency(this._base); + } else if (isTsDsl(this._base)) { + this._base.analyze(ctx); } + if (isSymbol(this._right)) ctx.addDependency(this._right); } - base(base?: string | MaybeTsDsl): this { + base(base?: Base): this { this._base = base; return this; } - $render(): ts.QualifiedName { + right(right: Right): this { + this._right = right; + return this; + } + + protected override _render() { if (!this._base) { throw new Error('TypeAttrTsDsl: missing base for qualified name'); } @@ -42,10 +56,10 @@ export class TypeAttrTsDsl extends TypeTsDsl { if (!ts.isEntityName(left)) { throw new Error('TypeAttrTsDsl: base must be an EntityName'); } - const right = this.$maybeId(this.right); - return ts.factory.createQualifiedName(left, right); + return ts.factory.createQualifiedName( + left, + // @ts-expect-error need to improve types + this.$node(this._right), + ); } } - -export interface TypeAttrTsDsl extends TypeExprMixin {} -mixin(TypeAttrTsDsl, TypeExprMixin); diff --git a/packages/openapi-ts/src/ts-dsl/type/expr.ts b/packages/openapi-ts/src/ts-dsl/type/expr.ts index 6b62042665..7aa03d9a91 100644 --- a/packages/openapi-ts/src/ts-dsl/type/expr.ts +++ b/packages/openapi-ts/src/ts-dsl/type/expr.ts @@ -1,8 +1,8 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { TypeArgsMixin } from '../mixins/type-args'; import { registerLazyAccessTypeExprFactory, @@ -10,38 +10,50 @@ import { } from '../mixins/type-expr'; import { TypeAttrTsDsl } from './attr'; -export class TypeExprTsDsl extends TypeTsDsl { - protected _exprInput?: string | ts.Identifier | TypeAttrTsDsl; +export type TypeExprName = Symbol | string; +export type TypeExprExpr = TypeExprName | TypeAttrTsDsl; + +const Mixed = TypeArgsMixin(TypeExprMixin(TypeTsDsl)); + +export class TypeExprTsDsl extends Mixed { + protected _exprInput?: TypeExprExpr; constructor(); constructor(fn: (t: TypeExprTsDsl) => void); - constructor(name: string); - constructor(name: string, fn?: (t: TypeExprTsDsl) => void); + constructor(name: TypeExprName); + constructor(name: TypeExprName, fn?: (t: TypeExprTsDsl) => void); constructor( - nameOrFn?: string | ((t: TypeExprTsDsl) => void), + name?: TypeExprName | ((t: TypeExprTsDsl) => void), fn?: (t: TypeExprTsDsl) => void, ) { super(); - if (typeof nameOrFn === 'string') { - this._exprInput = nameOrFn; - fn?.(this); + if (typeof name === 'function') { + name(this); } else { - nameOrFn?.(this); + this._exprInput = name; + fn?.(this); + } + } + + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._exprInput)) { + ctx.addDependency(this._exprInput); + } else if (isTsDsl(this._exprInput)) { + this._exprInput.analyze(ctx); } } /** Accesses a nested type (e.g. `Foo.Bar`). */ attr(right: string | ts.Identifier | TypeAttrTsDsl): this { - this._exprInput = - right instanceof TypeAttrTsDsl - ? right.base(this._exprInput) - : new TypeAttrTsDsl(this._exprInput!, right); + this._exprInput = isTsDsl(right) + ? right.base(this._exprInput) + : new TypeAttrTsDsl(this._exprInput!, right); return this; } - $render(): ts.TypeReferenceNode { - if (!this._exprInput) - throw new Error('TypeExpr must have either an expression or an object'); + protected override _render() { + if (!this._exprInput) throw new Error('TypeExpr must have an expression'); return ts.factory.createTypeReferenceNode( // @ts-expect-error --- need to fix types this.$type(this._exprInput), @@ -50,9 +62,6 @@ export class TypeExprTsDsl extends TypeTsDsl { } } -export interface TypeExprTsDsl extends TypeArgsMixin, TypeExprMixin {} -mixin(TypeExprTsDsl, TypeArgsMixin, TypeExprMixin); - registerLazyAccessTypeExprFactory( (...args) => new TypeExprTsDsl(...(args as ConstructorParameters)), diff --git a/packages/openapi-ts/src/ts-dsl/type/fromValue.ts b/packages/openapi-ts/src/ts-dsl/type/fromValue.ts index 3d8a0a5b80..d0b46cafe7 100644 --- a/packages/openapi-ts/src/ts-dsl/type/fromValue.ts +++ b/packages/openapi-ts/src/ts-dsl/type/fromValue.ts @@ -1,13 +1,14 @@ import type ts from 'typescript'; -import { TsDsl } from '../base'; +import type { TsDsl } from '../base'; +import { isTsDsl } from '../base'; import { TypeLiteralTsDsl } from './literal'; import { TypeObjectTsDsl } from './object'; import { TypeTupleTsDsl } from './tuple'; export const fromValue = (input: unknown): TsDsl => { - if (input instanceof TsDsl) { - return input; + if (isTsDsl(input)) { + return input as TsDsl; } if (input === null) { diff --git a/packages/openapi-ts/src/ts-dsl/type/func.ts b/packages/openapi-ts/src/ts-dsl/type/func.ts index 958f64e376..5daf112621 100644 --- a/packages/openapi-ts/src/ts-dsl/type/func.ts +++ b/packages/openapi-ts/src/ts-dsl/type/func.ts @@ -1,23 +1,31 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; import { DocMixin } from '../mixins/doc'; import { ParamMixin } from '../mixins/param'; import { TypeParamsMixin } from '../mixins/type-params'; import { TypeExprTsDsl } from './expr'; -export class TypeFuncTsDsl extends TypeTsDsl { +const Mixed = DocMixin( + ParamMixin(TypeParamsMixin(TypeTsDsl)), +); + +export class TypeFuncTsDsl extends Mixed { protected _returns?: TypeTsDsl; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + this._returns?.analyze(ctx); + } + /** Sets the return type. */ returns(type: string | TypeTsDsl): this { this._returns = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); return this; } - $render(): ts.FunctionTypeNode { + protected override _render() { if (this._returns === undefined) { throw new Error('Missing return type in function type DSL'); } @@ -28,6 +36,3 @@ export class TypeFuncTsDsl extends TypeTsDsl { ); } } - -export interface TypeFuncTsDsl extends DocMixin, ParamMixin, TypeParamsMixin {} -mixin(TypeFuncTsDsl, DocMixin, ParamMixin, TypeParamsMixin); diff --git a/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts b/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts index 0ba632ab51..05ed93f140 100644 --- a/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts +++ b/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts @@ -1,47 +1,54 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { DocMixin } from '../mixins/doc'; -import { createModifierAccessor, ReadonlyMixin } from '../mixins/modifiers'; +import { ReadonlyMixin } from '../mixins/modifiers'; -type Type = string | MaybeTsDsl; +export type TypeIdxSigName = string; +export type TypeIdxSigType = string | MaybeTsDsl; -export class TypeIdxSigTsDsl extends TypeTsDsl { - protected modifiers = createModifierAccessor(this); - protected _key?: Type; - protected _name: string; - protected _type?: Type; +const Mixed = DocMixin(ReadonlyMixin(TypeTsDsl)); - constructor(name: string, fn?: (i: TypeIdxSigTsDsl) => void) { +export class TypeIdxSigTsDsl extends Mixed { + protected _key?: TypeIdxSigType; + protected _name: TypeIdxSigName; + protected _type?: TypeIdxSigType; + + constructor(name: TypeIdxSigName, fn?: (i: TypeIdxSigTsDsl) => void) { super(); this._name = name; fn?.(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._key)) this._key.analyze(ctx); + if (isTsDsl(this._type)) this._type.analyze(ctx); + } + /** Returns true when all required builder calls are present. */ get isValid(): boolean { return this.missingRequiredCalls().length === 0; } /** Sets the key type: `[name: T]` */ - key(type: Type): this { + key(type: TypeIdxSigType): this { this._key = type; return this; } /** Sets the property type. */ - type(type: Type): this { + type(type: TypeIdxSigType): this { this._type = type; return this; } - $render(): ts.IndexSignatureDeclaration { + protected override _render() { this.$validate(); return ts.factory.createIndexSignature( - this.modifiers.list(), + this.modifiers, [ ts.factory.createParameterDeclaration( undefined, @@ -56,9 +63,9 @@ export class TypeIdxSigTsDsl extends TypeTsDsl { } $validate(): asserts this is this & { - _key: Type; - _name: string; - _type: Type; + _key: TypeIdxSigType; + _name: TypeIdxSigName; + _type: TypeIdxSigType; } { const missing = this.missingRequiredCalls(); if (missing.length === 0) return; @@ -75,6 +82,3 @@ export class TypeIdxSigTsDsl extends TypeTsDsl { return missing; } } - -export interface TypeIdxSigTsDsl extends DocMixin, ReadonlyMixin {} -mixin(TypeIdxSigTsDsl, DocMixin, ReadonlyMixin); diff --git a/packages/openapi-ts/src/ts-dsl/type/idx.ts b/packages/openapi-ts/src/ts-dsl/type/idx.ts index a43300bdbe..7720bd1756 100644 --- a/packages/openapi-ts/src/ts-dsl/type/idx.ts +++ b/packages/openapi-ts/src/ts-dsl/type/idx.ts @@ -1,38 +1,45 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { registerLazyAccessTypeIdxFactory, TypeExprMixin, } from '../mixins/type-expr'; -export class TypeIdxTsDsl extends TypeTsDsl { - protected _base: string | MaybeTsDsl; - protected _index: string | MaybeTsDsl | number; +type Base = string | MaybeTsDsl; +type Index = string | number | MaybeTsDsl; - constructor( - base: string | MaybeTsDsl, - index: string | MaybeTsDsl | number, - ) { +const Mixed = TypeExprMixin(TypeTsDsl); + +export class TypeIdxTsDsl extends Mixed { + protected _base!: Base; + protected _index!: Index; + + constructor(base: Base, index: Index) { super(); - this._base = base; - this._index = index; + this.base(base); + this.index(index); } - base(base: string | MaybeTsDsl): this { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._base)) this._base.analyze(ctx); + if (isTsDsl(this._index)) this._index.analyze(ctx); + } + + base(base: Base): this { this._base = base; return this; } - index(index: string | MaybeTsDsl | number): this { + index(index: Index): this { this._index = index; return this; } - $render(): ts.IndexedAccessTypeNode { + protected override _render() { return ts.factory.createIndexedAccessTypeNode( this.$type(this._base), this.$type(this._index), @@ -40,7 +47,4 @@ export class TypeIdxTsDsl extends TypeTsDsl { } } -export interface TypeIdxTsDsl extends TypeExprMixin {} -mixin(TypeIdxTsDsl, TypeExprMixin); - registerLazyAccessTypeIdxFactory((...args) => new TypeIdxTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/type/literal.ts b/packages/openapi-ts/src/ts-dsl/type/literal.ts index 7e1f3caf5c..d85b7458e2 100644 --- a/packages/openapi-ts/src/ts-dsl/type/literal.ts +++ b/packages/openapi-ts/src/ts-dsl/type/literal.ts @@ -1,9 +1,12 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TypeTsDsl } from '../base'; import { LiteralTsDsl } from '../expr/literal'; -export class TypeLiteralTsDsl extends TypeTsDsl { +const Mixed = TypeTsDsl; + +export class TypeLiteralTsDsl extends Mixed { protected value: string | number | boolean | null; constructor(value: string | number | boolean | null) { @@ -11,7 +14,11 @@ export class TypeLiteralTsDsl extends TypeTsDsl { this.value = value; } - $render(): ts.LiteralTypeNode { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + } + + protected override _render() { return ts.factory.createLiteralTypeNode( this.$node(new LiteralTsDsl(this.value)), ); diff --git a/packages/openapi-ts/src/ts-dsl/type/mapped.ts b/packages/openapi-ts/src/ts-dsl/type/mapped.ts index 9462357997..3f448df812 100644 --- a/packages/openapi-ts/src/ts-dsl/type/mapped.ts +++ b/packages/openapi-ts/src/ts-dsl/type/mapped.ts @@ -1,10 +1,13 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; import { TokenTsDsl } from '../token'; -export class TypeMappedTsDsl extends TypeTsDsl { +const Mixed = TypeTsDsl; + +export class TypeMappedTsDsl extends Mixed { protected questionToken?: TokenTsDsl< | ts.SyntaxKind.QuestionToken | ts.SyntaxKind.PlusToken @@ -24,6 +27,14 @@ export class TypeMappedTsDsl extends TypeTsDsl { this.name(name); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + this.questionToken?.analyze(ctx); + this.readonlyToken?.analyze(ctx); + if (isTsDsl(this._key)) this._key.analyze(ctx); + if (isTsDsl(this._type)) this._type.analyze(ctx); + } + /** Returns true when all required builder calls are present. */ get isValid(): boolean { return this.missingRequiredCalls().length === 0; @@ -71,7 +82,7 @@ export class TypeMappedTsDsl extends TypeTsDsl { return this; } - $render(): ts.MappedTypeNode { + protected override _render() { this.$validate(); return ts.factory.createMappedTypeNode( this.$node(this.readonlyToken), diff --git a/packages/openapi-ts/src/ts-dsl/type/object.ts b/packages/openapi-ts/src/ts-dsl/type/object.ts index 26fbf0ee6a..5190bef6a2 100644 --- a/packages/openapi-ts/src/ts-dsl/type/object.ts +++ b/packages/openapi-ts/src/ts-dsl/type/object.ts @@ -1,12 +1,22 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import { TypeTsDsl } from '../base'; import { TypeIdxSigTsDsl } from './idx-sig'; import { TypePropTsDsl } from './prop'; -export class TypeObjectTsDsl extends TypeTsDsl { +const Mixed = TypeTsDsl; + +export class TypeObjectTsDsl extends Mixed { protected props: Array = []; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const prop of this.props) { + prop.analyze(ctx); + } + } + /** Returns true if object has at least one property or spread. */ hasProps(): boolean { return this.props.length > 0; @@ -31,7 +41,7 @@ export class TypeObjectTsDsl extends TypeTsDsl { return this; } - $render(): ts.TypeNode { + protected override _render() { return ts.factory.createTypeLiteralNode(this.$node(this.props)); } } diff --git a/packages/openapi-ts/src/ts-dsl/type/operator.ts b/packages/openapi-ts/src/ts-dsl/type/operator.ts index 674b5bc200..b7bc6da057 100644 --- a/packages/openapi-ts/src/ts-dsl/type/operator.ts +++ b/packages/openapi-ts/src/ts-dsl/type/operator.ts @@ -1,7 +1,8 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; import { registerLazyAccessTypeOperatorFactory } from '../mixins/type-expr'; type Op = @@ -10,6 +11,8 @@ type Op = | ts.SyntaxKind.UniqueKeyword; type Type = string | MaybeTsDsl; +const Mixed = TypeTsDsl; + /** * Builds a TypeScript `TypeOperatorNode`, such as: * @@ -22,10 +25,15 @@ type Type = string | MaybeTsDsl; * * The node will throw during render if required fields are missing. */ -export class TypeOperatorTsDsl extends TypeTsDsl { +export class TypeOperatorTsDsl extends Mixed { protected _op?: Op; protected _type?: Type; + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._type)) this._type.analyze(ctx); + } + /** Shorthand: builds `keyof T`. */ keyof(type: Type): this { this.operator(ts.SyntaxKind.KeyOfKeyword); @@ -59,7 +67,7 @@ export class TypeOperatorTsDsl extends TypeTsDsl { return this; } - $render(): ts.TypeOperatorNode { + protected override _render() { this.$validate(); return ts.factory.createTypeOperatorNode(this._op, this.$type(this._type)); } diff --git a/packages/openapi-ts/src/ts-dsl/type/or.ts b/packages/openapi-ts/src/ts-dsl/type/or.ts index 874a82fabf..a9afad2fe4 100644 --- a/packages/openapi-ts/src/ts-dsl/type/or.ts +++ b/packages/openapi-ts/src/ts-dsl/type/or.ts @@ -1,29 +1,46 @@ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; -export class TypeOrTsDsl extends TypeTsDsl { - protected _types: Array = []; +type Type = Symbol | string | ts.TypeNode | TypeTsDsl; - constructor(...nodes: Array) { +const Mixed = TypeTsDsl; + +export class TypeOrTsDsl extends Mixed { + protected _types: Array = []; + + constructor(...nodes: Array) { super(); this.types(...nodes); } - types(...nodes: Array): this { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const t of this._types) { + if (isSymbol(t)) { + ctx.addDependency(t); + } else if (isTsDsl(t)) { + t.analyze(ctx); + } + } + } + + types(...nodes: Array): this { this._types.push(...nodes); return this; } - $render(): ts.UnionTypeNode { + protected override _render() { const flat: Array = []; - for (const n of this._types) { - const t = this.$type(n); - if (ts.isUnionTypeNode(t)) { - flat.push(...t.types); + for (const node of this._types) { + const type = this.$type(node); + if (ts.isUnionTypeNode(type)) { + flat.push(...type.types); } else { - flat.push(t); + flat.push(type); } } diff --git a/packages/openapi-ts/src/ts-dsl/type/param.ts b/packages/openapi-ts/src/ts-dsl/type/param.ts index 50b43a28e1..55ea62b0bb 100644 --- a/packages/openapi-ts/src/ts-dsl/type/param.ts +++ b/packages/openapi-ts/src/ts-dsl/type/param.ts @@ -1,37 +1,57 @@ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; -export class TypeParamTsDsl extends TypeTsDsl { - protected name?: string | ts.Identifier; - protected constraint?: string | MaybeTsDsl | boolean; - protected defaultValue?: string | MaybeTsDsl | boolean; +export type TypeParamName = Symbol | string; +export type TypeParamExpr = Symbol | string | boolean | MaybeTsDsl; - constructor( - name?: string | ts.Identifier, - fn?: (name: TypeParamTsDsl) => void, - ) { +const Mixed = TypeTsDsl; + +export class TypeParamTsDsl extends Mixed { + protected name?: TypeParamName; + protected constraint?: TypeParamExpr; + protected defaultValue?: TypeParamExpr; + + constructor(name?: TypeParamName, fn?: (name: TypeParamTsDsl) => void) { super(); this.name = name; fn?.(this); } - default(value: string | MaybeTsDsl | boolean): this { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this.name)) ctx.addDependency(this.name); + if (isSymbol(this.constraint)) { + ctx.addDependency(this.constraint); + } else if (isTsDsl(this.constraint)) { + this.constraint.analyze(ctx); + } + if (isSymbol(this.defaultValue)) { + ctx.addDependency(this.defaultValue); + } else if (isTsDsl(this.defaultValue)) { + this.defaultValue.analyze(ctx); + } + } + + default(value: TypeParamExpr): this { this.defaultValue = value; return this; } - extends(constraint: string | MaybeTsDsl | boolean): this { + extends(constraint: TypeParamExpr): this { this.constraint = constraint; return this; } - $render(): ts.TypeParameterDeclaration { + protected override _render() { if (!this.name) throw new Error('Missing type name'); return ts.factory.createTypeParameterDeclaration( undefined, - this.$maybeId(this.name), + // @ts-expect-error need to improve types + this.$node(this.name), this.$type(this.constraint), this.$type(this.defaultValue), ); diff --git a/packages/openapi-ts/src/ts-dsl/type/prop.ts b/packages/openapi-ts/src/ts-dsl/type/prop.ts index 393232508e..e8519ea0b7 100644 --- a/packages/openapi-ts/src/ts-dsl/type/prop.ts +++ b/packages/openapi-ts/src/ts-dsl/type/prop.ts @@ -1,45 +1,54 @@ -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext, Symbol } from '@hey-api/codegen-core'; +import { isSymbol } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { DocMixin } from '../mixins/doc'; -import { createModifierAccessor, ReadonlyMixin } from '../mixins/modifiers'; +import { ReadonlyMixin } from '../mixins/modifiers'; import { OptionalMixin } from '../mixins/optional'; import { TokenTsDsl } from '../token'; import { safePropName } from '../utils/prop'; -export class TypePropTsDsl extends TypeTsDsl { - protected modifiers = createModifierAccessor(this); - protected name: string; - protected _type?: string | MaybeTsDsl; +export type TypePropName = string; +export type TypePropType = Symbol | string | MaybeTsDsl; - constructor(name: string, fn: (p: TypePropTsDsl) => void) { +const Mixed = DocMixin(OptionalMixin(ReadonlyMixin(TypeTsDsl))); + +export class TypePropTsDsl extends Mixed { + protected name: TypePropName; + protected _type?: TypePropType; + + constructor(name: TypePropName, fn: (p: TypePropTsDsl) => void) { super(); this.name = name; fn(this); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isSymbol(this._type)) { + ctx.addDependency(this._type); + } else if (isTsDsl(this._type)) { + this._type.analyze(ctx); + } + } + /** Sets the property type. */ - type(type: string | MaybeTsDsl): this { + type(type: TypePropType): this { this._type = type; return this; } - /** Builds and returns the property signature. */ - $render(): ts.TypeElement { + protected override _render() { if (!this._type) { throw new Error(`Type not specified for property '${this.name}'`); } return ts.factory.createPropertySignature( - this.modifiers.list(), - safePropName(this.name), + this.modifiers, + this.$node(safePropName(this.name)), this._optional ? this.$node(new TokenTsDsl().optional()) : undefined, this.$type(this._type), ); } } - -export interface TypePropTsDsl extends DocMixin, OptionalMixin, ReadonlyMixin {} -mixin(TypePropTsDsl, DocMixin, OptionalMixin, ReadonlyMixin); diff --git a/packages/openapi-ts/src/ts-dsl/type/query.ts b/packages/openapi-ts/src/ts-dsl/type/query.ts index 987bd31dec..1c2e1f8012 100644 --- a/packages/openapi-ts/src/ts-dsl/type/query.ts +++ b/packages/openapi-ts/src/ts-dsl/type/query.ts @@ -1,15 +1,16 @@ -/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; -import { mixin } from '../mixins/apply'; +import { isTsDsl, TypeTsDsl } from '../base'; import { registerLazyAccessTypeQueryFactory, TypeExprMixin, } from '../mixins/type-expr'; -export class TypeQueryTsDsl extends TypeTsDsl { +const Mixed = TypeExprMixin(TypeTsDsl); + +export class TypeQueryTsDsl extends Mixed { protected _expr: string | MaybeTsDsl; constructor(expr: string | MaybeTsDsl) { @@ -17,13 +18,15 @@ export class TypeQueryTsDsl extends TypeTsDsl { this._expr = expr; } - $render(): ts.TypeQueryNode { + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + if (isTsDsl(this._expr)) this._expr.analyze(ctx); + } + + protected override _render() { const expr = this.$node(this._expr); return ts.factory.createTypeQueryNode(expr as unknown as ts.EntityName); } } -export interface TypeQueryTsDsl extends TypeExprMixin {} -mixin(TypeQueryTsDsl, TypeExprMixin); - registerLazyAccessTypeQueryFactory((...args) => new TypeQueryTsDsl(...args)); diff --git a/packages/openapi-ts/src/ts-dsl/type/template.ts b/packages/openapi-ts/src/ts-dsl/type/template.ts index 5617286caa..2f171e5d04 100644 --- a/packages/openapi-ts/src/ts-dsl/type/template.ts +++ b/packages/openapi-ts/src/ts-dsl/type/template.ts @@ -1,9 +1,12 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; import type { MaybeTsDsl } from '../base'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; -export class TypeTemplateTsDsl extends TypeTsDsl { +const Mixed = TypeTsDsl; + +export class TypeTemplateTsDsl extends Mixed { protected parts: Array> = []; constructor(value?: string | MaybeTsDsl) { @@ -11,14 +14,20 @@ export class TypeTemplateTsDsl extends TypeTsDsl { if (value !== undefined) this.add(value); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const part of this.parts) { + if (isTsDsl(part)) part.analyze(ctx); + } + } + /** Adds a raw string segment or embedded type expression. */ add(part: string | MaybeTsDsl): this { this.parts.push(part); return this; } - /** Renders a TemplateLiteralTypeNode. */ - $render(): ts.TemplateLiteralTypeNode { + protected override _render() { const parts = this.$node(this.parts); const normalized: Array = []; diff --git a/packages/openapi-ts/src/ts-dsl/type/tuple.ts b/packages/openapi-ts/src/ts-dsl/type/tuple.ts index 260c437fa2..fb8b09d340 100644 --- a/packages/openapi-ts/src/ts-dsl/type/tuple.ts +++ b/packages/openapi-ts/src/ts-dsl/type/tuple.ts @@ -1,8 +1,11 @@ +import type { AnalysisContext } from '@hey-api/codegen-core'; import ts from 'typescript'; -import { TypeTsDsl } from '../base'; +import { isTsDsl, TypeTsDsl } from '../base'; -export class TypeTupleTsDsl extends TypeTsDsl { +const Mixed = TypeTsDsl; + +export class TypeTupleTsDsl extends Mixed { protected _elements: Array = []; constructor(...nodes: Array) { @@ -10,12 +13,19 @@ export class TypeTupleTsDsl extends TypeTsDsl { this.elements(...nodes); } + override analyze(ctx: AnalysisContext): void { + super.analyze(ctx); + for (const t of this._elements) { + if (isTsDsl(t)) t.analyze(ctx); + } + } + elements(...types: Array): this { this._elements.push(...types); return this; } - $render(): ts.TupleTypeNode { + protected override _render() { return ts.factory.createTupleTypeNode( this._elements.map((t) => this.$type(t)), ); diff --git a/packages/openapi-ts/src/ts-dsl/utils/prop.ts b/packages/openapi-ts/src/ts-dsl/utils/prop.ts index fbc342e9d5..309f0a179b 100644 --- a/packages/openapi-ts/src/ts-dsl/utils/prop.ts +++ b/packages/openapi-ts/src/ts-dsl/utils/prop.ts @@ -1,30 +1,31 @@ -import ts from 'typescript'; +import type ts from 'typescript'; import { numberRegExp, validTypescriptIdentifierRegExp } from '~/utils/regexp'; +import type { TsDsl } from '../base'; import { IdTsDsl } from '../expr/id'; import { LiteralTsDsl } from '../expr/literal'; -export const safeMemberName = (name: string): ts.PropertyName => { +export const safeMemberName = (name: string): TsDsl => { validTypescriptIdentifierRegExp.lastIndex = 0; if (validTypescriptIdentifierRegExp.test(name)) { - return new IdTsDsl(name).$render(); + return new IdTsDsl(name); } - return new LiteralTsDsl(name).$render() as ts.StringLiteral; + return new LiteralTsDsl(name) as TsDsl; }; -export const safePropName = (name: string): ts.PropertyName => { +export const safePropName = (name: string): TsDsl => { numberRegExp.lastIndex = 0; if (numberRegExp.test(name)) { return name.startsWith('-') - ? (new LiteralTsDsl(name).$render() as ts.StringLiteral) - : ts.factory.createNumericLiteral(name); + ? (new LiteralTsDsl(name) as TsDsl) + : (new LiteralTsDsl(Number(name)) as TsDsl); } validTypescriptIdentifierRegExp.lastIndex = 0; if (validTypescriptIdentifierRegExp.test(name)) { - return new IdTsDsl(name).$render(); + return new IdTsDsl(name); } - return new LiteralTsDsl(name).$render() as ts.StringLiteral; + return new LiteralTsDsl(name) as TsDsl; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efc0189b58..1e254d6085 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1228,6 +1228,13 @@ importers: version: 3.0.8(typescript@5.8.3) packages/codegen-core: + dependencies: + ansi-colors: + specifier: 4.1.3 + version: 4.1.3 + color-support: + specifier: 1.1.3 + version: 1.1.3 devDependencies: '@config/vite-base': specifier: workspace:* diff --git a/specs/3.1.x/openai.yaml b/specs/3.1.x/openai.yaml index 788c4eb554..e0d0260f9d 100644 --- a/specs/3.1.x/openai.yaml +++ b/specs/3.1.x/openai.yaml @@ -4113,7 +4113,7 @@ paths: } description: > **Starting a new project?** We recommend trying - [Responses](https://platform.openai.com/docs/api-reference/responses) + [Responses](https://platform.openai.com/docs/api-reference/responses) to take advantage of the latest OpenAI platform features. Compare @@ -4136,9 +4136,9 @@ paths: response, particularly for newer reasoning models. Parameters that are only - supported for reasoning models are noted below. For the current state of + supported for reasoning models are noted below. For the current state of - unsupported parameters in reasoning models, + unsupported parameters in reasoning models, [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). /chat/completions/{completion_id}: @@ -14566,7 +14566,7 @@ paths: ], } description: | - Deactivate certificates at the project level. You can atomically and + Deactivate certificates at the project level. You can atomically and idempotently deactivate up to 10 certificates at a time. /organization/projects/{project_id}/rate_limits: get: @@ -16592,7 +16592,7 @@ paths: "speed": 1.1, "tracing": "auto", "client_secret": { - "value": "ek_abc123", + "value": "ek_abc123", "expires_at": 1234567890 } } @@ -16739,7 +16739,7 @@ paths: } description: | Create an ephemeral API token for use in client-side applications with the - Realtime API specifically for realtime transcriptions. + Realtime API specifically for realtime transcriptions. Can be configured with the same session parameters as the `transcription_session.update` client event. It responds with a session object, plus a `client_secret` key which contains @@ -18885,7 +18885,7 @@ paths: puts(response) description: | Cancels a model response with the given ID. Only responses created with - the `background` parameter set to `true` can be cancelled. + the `background` parameter set to `true` can be cancelled. [Learn more](https://platform.openai.com/docs/guides/background). /responses/{response_id}/input_items: get: @@ -23170,9 +23170,9 @@ paths: File object. - For certain `purpose` values, the correct `mime_type` must be specified. + For certain `purpose` values, the correct `mime_type` must be specified. - Please refer to documentation for the + Please refer to documentation for the [supported MIME types for your use case](https://platform.openai.com/docs/assistants/tools/file-search#supported-files). @@ -23433,7 +23433,7 @@ paths: puts(upload) description: > - Completes the [Upload](https://platform.openai.com/docs/api-reference/uploads/object). + Completes the [Upload](https://platform.openai.com/docs/api-reference/uploads/object). Within the returned Upload object, there is a nested @@ -23575,7 +23575,7 @@ paths: description: > Adds a [Part](https://platform.openai.com/docs/api-reference/uploads/part-object) to an [Upload](https://platform.openai.com/docs/api-reference/uploads/object) object. A Part represents a - chunk of bytes from the file you are trying to upload. + chunk of bytes from the file you are trying to upload. Each Part can be at most 64 MB, and you can add Parts until you hit the Upload maximum of 8 GB. @@ -25849,7 +25849,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. batch_completed: post: @@ -25862,7 +25862,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. batch_expired: post: @@ -25875,7 +25875,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. batch_failed: post: @@ -25888,7 +25888,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. eval_run_canceled: post: @@ -25901,7 +25901,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. eval_run_failed: post: @@ -25914,7 +25914,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. eval_run_succeeded: post: @@ -25927,7 +25927,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. fine_tuning_job_cancelled: post: @@ -25940,7 +25940,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. fine_tuning_job_failed: post: @@ -25953,7 +25953,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. fine_tuning_job_succeeded: post: @@ -25966,7 +25966,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. response_cancelled: post: @@ -25979,7 +25979,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. response_completed: post: @@ -25992,7 +25992,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. response_failed: post: @@ -26005,7 +26005,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. response_incomplete: post: @@ -26018,7 +26018,7 @@ webhooks: responses: '200': description: | - Return a 200 status code to acknowledge receipt of the event. Non-200 + Return a 200 status code to acknowledge receipt of the event. Non-200 status codes will be retried. components: schemas: @@ -27592,7 +27592,7 @@ components: nullable: true description: > If a content parts array was provided, this is an array of `text` and `image_url` - parts. + parts. Otherwise, null. items: @@ -27822,7 +27822,7 @@ components: type: object nullable: true description: | - Data about a previous audio response from the model. + Data about a previous audio response from the model. [Learn more](https://platform.openai.com/docs/guides/audio). required: - id @@ -27986,12 +27986,12 @@ components: filename: type: string description: | - The name of the file, used when passing the file to the model as a + The name of the file, used when passing the file to the model as a string. file_data: type: string description: | - The base64 encoded file data, used when passing the file to the model + The base64 encoded file data, used when passing the file to the model as a string. file_id: type: string @@ -28528,7 +28528,7 @@ components: - click default: click description: | - Specifies the event type. For a click action, this property is + Specifies the event type. For a click action, this property is always set to `click`. x-stainless-const: true button: @@ -28740,7 +28740,7 @@ components: propertyName: type nullable: true description: | - The outputs generated by the code interpreter, such as logs or images. + The outputs generated by the code interpreter, such as logs or images. Can be null if no outputs are available. required: - type @@ -28915,7 +28915,7 @@ components: - computer_screenshot default: computer_screenshot description: | - Specifies the event type. For a computer screenshot, this property is + Specifies the event type. For a computer screenshot, this property is always set to `computer_screenshot`. x-stainless-const: true image_url: @@ -28930,7 +28930,7 @@ components: type: object title: Computer tool call description: | - A tool call to a computer use tool. See the + A tool call to a computer use tool. See the [computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information. properties: type: @@ -28995,7 +28995,7 @@ components: acknowledged_safety_checks: type: array description: | - The safety checks reported by the API that have been acknowledged by the + The safety checks reported by the API that have been acknowledged by the developer. items: $ref: '#/components/schemas/ComputerToolCallSafetyCheck' @@ -29916,7 +29916,7 @@ components: type: object description: | Represents a streamed chunk of a chat completion response returned - by the model, based on the provided input. + by the model, based on the provided input. [Learn more](https://platform.openai.com/docs/guides/streaming-responses). properties: id: @@ -31354,10 +31354,10 @@ components: description: | The image(s) to edit. Must be a supported image file or an array of images. - For `gpt-image-1`, each image should be a `png`, `webp`, or `jpg` file less + For `gpt-image-1`, each image should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to 16 images. - For `dall-e-2`, you can only provide one image, and it should be a square + For `dall-e-2`, you can only provide one image, and it should be a square `png` file less than 4MB. x-oaiMeta: exampleFilePath: otter.png @@ -31386,12 +31386,12 @@ components: example: transparent nullable: true description: | - Allows to set transparency for the background of the generated image(s). - This parameter is only supported for `gpt-image-1`. Must be one of - `transparent`, `opaque` or `auto` (default value). When `auto` is used, the + Allows to set transparency for the background of the generated image(s). + This parameter is only supported for `gpt-image-1`. Must be one of + `transparent`, `opaque` or `auto` (default value). When `auto` is used, the model will automatically determine the best background for the image. - If `transparent`, the output format needs to support transparency, so it + If `transparent`, the output format needs to support transparency, so it should be set to either `png` (default value) or `webp`. model: anyOf: @@ -31461,8 +31461,8 @@ components: example: 100 nullable: true description: | - The compression level (0-100%) for the generated images. This parameter - is only supported for `gpt-image-1` with the `webp` or `jpeg` output + The compression level (0-100%) for the generated images. This parameter + is only supported for `gpt-image-1` with the `webp` or `jpeg` output formats, and defaults to 100. user: type: string @@ -31478,7 +31478,7 @@ components: example: false nullable: true description: > - Edit the image in streaming mode. Defaults to `false`. See the + Edit the image in streaming mode. Defaults to `false`. See the [Image generation guide](https://platform.openai.com/docs/guides/image-generation) for more information. @@ -31547,7 +31547,7 @@ components: example: medium nullable: true description: | - The quality of the image that will be generated. + The quality of the image that will be generated. - `auto` (default value) will automatically select the best quality for the given model. - `high`, `medium` and `low` are supported for `gpt-image-1`. @@ -31591,7 +31591,7 @@ components: example: false nullable: true description: > - Generate the image in streaming mode. Defaults to `false`. See the + Generate the image in streaming mode. Defaults to `false`. See the [Image generation guide](https://platform.openai.com/docs/guides/image-generation) for more information. @@ -31638,12 +31638,12 @@ components: example: transparent nullable: true description: | - Allows to set transparency for the background of the generated image(s). - This parameter is only supported for `gpt-image-1`. Must be one of - `transparent`, `opaque` or `auto` (default value). When `auto` is used, the + Allows to set transparency for the background of the generated image(s). + This parameter is only supported for `gpt-image-1`. Must be one of + `transparent`, `opaque` or `auto` (default value). When `auto` is used, the model will automatically determine the best background for the image. - If `transparent`, the output format needs to support transparency, so it + If `transparent`, the output format needs to support transparency, so it should be set to either `png` (default value) or `webp`. style: type: string @@ -32664,7 +32664,7 @@ components: CreateThreadRequest: type: object description: | - Options to create a new thread. If no thread is provided when running a + Options to create a new thread. If no thread is provided when running a request, an empty thread will be created. additionalProperties: false properties: @@ -32853,7 +32853,7 @@ components: If set to true, the model response data will be streamed to the client as it is generated using [server-sent - events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). See the [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) @@ -32883,10 +32883,10 @@ components: - segment include: description: | - Additional information to include in the transcription response. - `logprobs` will return the log probabilities of the tokens in the - response to understand the model's confidence in the transcription. - `logprobs` only works with response_format set to `json` and only with + Additional information to include in the transcription response. + `logprobs` will return the log probabilities of the tokens in the + response to understand the model's confidence in the transcription. + `logprobs` only works with response_format set to `json` and only with the models `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`. type: array items: @@ -33573,7 +33573,7 @@ components: - double_click default: double_click description: | - Specifies the event type. For a double click action, this property is + Specifies the event type. For a double click action, this property is always set to `double_click`. x-stainless-const: true x: @@ -33600,7 +33600,7 @@ components: - drag default: drag description: | - Specifies the event type. For a drag action, this property is + Specifies the event type. For a drag action, this property is always set to `drag`. x-stainless-const: true path: @@ -35189,7 +35189,7 @@ components: type: object title: File search tool call description: | - The results of a file search tool call. See the + The results of a file search tool call. See the [file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information. properties: id: @@ -35206,7 +35206,7 @@ components: status: type: string description: | - The status of the file search tool call. One of `in_progress`, + The status of the file search tool call. One of `in_progress`, `searching`, `incomplete` or `failed`, enum: - in_progress @@ -36146,7 +36146,7 @@ components: description: >- The parameters the functions accepts, described as a JSON Schema object. See the [guide](https://platform.openai.com/docs/guides/function-calling) for examples, and the [JSON Schema - reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. + reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. Omitting `parameters` defines a function with an empty parameter list. @@ -36155,7 +36155,7 @@ components: type: object title: Function tool call description: > - A tool call to run a function. See the + A tool call to run a function. See the [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. @@ -36578,8 +36578,8 @@ components: - rouge_5 - rouge_l description: | - The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, - `gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, + The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, + `gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, or `rouge_l`. required: - type @@ -36991,7 +36991,7 @@ components: - high - auto description: | - The quality of the generated image. One of `low`, `medium`, `high`, + The quality of the generated image. One of `low`, `medium`, `high`, or `auto`. Default: `auto`. default: auto size: @@ -37002,7 +37002,7 @@ components: - 1536x1024 - auto description: | - The size of the generated image. One of `1024x1024`, `1024x1536`, + The size of the generated image. One of `1024x1024`, `1024x1536`, `1536x1024`, or `auto`. Default: `auto`. default: auto output_format: @@ -37012,7 +37012,7 @@ components: - webp - jpeg description: | - The output format of the generated image. One of `png`, `webp`, or + The output format of the generated image. One of `png`, `webp`, or `jpeg`. Default: `png`. default: png output_compression: @@ -37037,7 +37037,7 @@ components: - opaque - auto description: | - Background type for the generated image. One of `transparent`, + Background type for the generated image. One of `transparent`, `opaque`, or `auto`. Default: `auto`. default: auto input_fidelity: @@ -37045,7 +37045,7 @@ components: input_image_mask: type: object description: | - Optional mask for inpainting. Contains `image_url` + Optional mask for inpainting. Contains `image_url` (string, optional) and `file_id` (string, optional). properties: image_url: @@ -37284,7 +37284,7 @@ components: - type: object title: Item description: | - An item representing part of the context for the response to be + An item representing part of the context for the response to be generated by the model. Can contain text, images, and audio inputs, as well as previous assistant responses and tool call outputs. $ref: '#/components/schemas/Item' @@ -37330,7 +37330,7 @@ components: type: array title: Input item content list description: | - A list of one or many input items to the model, containing different content + A list of one or many input items to the model, containing different content types. items: $ref: '#/components/schemas/InputContent' @@ -37563,7 +37563,7 @@ components: - keypress default: keypress description: | - Specifies the event type. For a keypress action, this property is + Specifies the event type. For a keypress action, this property is always set to `keypress`. x-stainless-const: true keys: @@ -38307,7 +38307,7 @@ components: type: object title: MCP tool description: | - Give the model access to additional tools via remote Model Context Protocol + Give the model access to additional tools via remote Model Context Protocol (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). properties: type: @@ -38390,8 +38390,8 @@ components: - type: string title: MCP tool approval setting description: | - Specify a single approval policy for all tools. One of `always` or - `never`. When set to `always`, all tools will require approval. When + Specify a single approval policy for all tools. One of `always` or + `never`. When set to `always`, all tools will require approval. When set to `never`, all tools will not require approval. enum: - always @@ -39116,7 +39116,7 @@ components: description: | Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + format, and querying for objects via API or the dashboard. Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters. @@ -39230,7 +39230,7 @@ components: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations. - A stable identifier for your end-users. + A stable identifier for your end-users. Used to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn @@ -39240,7 +39240,7 @@ components: example: safety-identifier-1234 description: > A stable identifier used to help detect users of your application that may be violating OpenAI's - usage policies. + usage policies. The IDs should be a string that uniquely identifies each user. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn @@ -39427,7 +39427,7 @@ components: - move default: move description: | - Specifies the event type. For a move action, this property is + Specifies the event type. For a move action, this property is always set to `move`. x-stainless-const: true x: @@ -39646,7 +39646,7 @@ components: streaming responses that return partial images. Value must be between 0 and 3. When set to 0, the response will be a single image sent in one streaming event. - Note that the final image may be sent before the full number of partial images + Note that the final image may be sent before the full number of partial images are generated if the full image is generated more quickly. PredictionContent: type: object @@ -40230,7 +40230,7 @@ components: type: object nullable: true description: | - Reference to a prompt template and its variables. + Reference to a prompt template and its variables. [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). required: - id @@ -40265,12 +40265,12 @@ components: RealtimeClientEventConversationItemCreate: type: object description: | - Add a new Item to the Conversation's context, including messages, function - calls, and function call responses. This event can be used both to populate a - "history" of the conversation and to add new items mid-stream, but has the + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the current limitation that it cannot populate assistant audio messages. - If successful, the server will respond with a `conversation.item.created` + If successful, the server will respond with a `conversation.item.created` event, otherwise an `error` event will be sent. properties: event_id: @@ -40283,7 +40283,7 @@ components: previous_item_id: type: string description: | - The ID of the preceding item after which the new item will be inserted. + The ID of the preceding item after which the new item will be inserted. If not set, the new item will be appended to the end of the conversation. If set to `root`, the new item will be added to the beginning of the conversation. If set to an existing ID, it allows an item to be inserted mid-conversation. If the @@ -40316,9 +40316,9 @@ components: RealtimeClientEventConversationItemDelete: type: object description: | - Send this event when you want to remove any item from the conversation - history. The server will respond with a `conversation.item.deleted` event, - unless the item does not exist in the conversation history, in which case the + Send this event when you want to remove any item from the conversation + history. The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the server will respond with an error. properties: event_id: @@ -40350,9 +40350,9 @@ components: conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. - The server will respond with a `conversation.item.retrieved` event, + The server will respond with a `conversation.item.retrieved` event, - unless the item does not exist in the conversation history, in which case the + unless the item does not exist in the conversation history, in which case the server will respond with an error. properties: @@ -40381,16 +40381,16 @@ components: RealtimeClientEventConversationItemTruncate: type: object description: | - Send this event to truncate a previous assistant message’s audio. The server - will produce audio faster than realtime, so this event is useful when the user - interrupts to truncate audio that has already been sent to the client but not - yet played. This will synchronize the server's understanding of the audio with + Send this event to truncate a previous assistant message’s audio. The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with the client's playback. - Truncating audio will delete the server-side text transcript to ensure there + Truncating audio will delete the server-side text transcript to ensure there is not text in the context that hasn't been heard by the user. - If successful, the server will respond with a `conversation.item.truncated` + If successful, the server will respond with a `conversation.item.truncated` event. properties: event_id: @@ -40403,7 +40403,7 @@ components: item_id: type: string description: | - The ID of the assistant message item to truncate. Only assistant message + The ID of the assistant message item to truncate. Only assistant message items can be truncated. content_index: type: integer @@ -40411,8 +40411,8 @@ components: audio_end_ms: type: integer description: | - Inclusive duration up to which audio is truncated, in milliseconds. If - the audio_end_ms is greater than the actual audio duration, the server + Inclusive duration up to which audio is truncated, in milliseconds. If + the audio_end_ms is greater than the actual audio duration, the server will respond with an error. required: - type @@ -40433,15 +40433,15 @@ components: RealtimeClientEventInputAudioBufferAppend: type: object description: | - Send this event to append audio bytes to the input audio buffer. The audio - buffer is temporary storage you can write to and later commit. In Server VAD - mode, the audio buffer is used to detect speech and the server will decide + Send this event to append audio bytes to the input audio buffer. The audio + buffer is temporary storage you can write to and later commit. In Server VAD + mode, the audio buffer is used to detect speech and the server will decide when to commit. When Server VAD is disabled, you must commit the audio buffer manually. - The client may choose how much audio to place in each event up to a maximum - of 15 MiB, for example streaming smaller chunks from the client may allow the - VAD to be more responsive. Unlike made other client events, the server will + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike made other client events, the server will not send a confirmation response to this event. properties: event_id: @@ -40454,7 +40454,7 @@ components: audio: type: string description: | - Base64-encoded audio bytes. This must be in the format specified by the + Base64-encoded audio bytes. This must be in the format specified by the `input_audio_format` field in the session configuration. required: - type @@ -40471,7 +40471,7 @@ components: RealtimeClientEventInputAudioBufferClear: type: object description: | - Send this event to clear the audio bytes in the buffer. The server will + Send this event to clear the audio bytes in the buffer. The server will respond with an `input_audio_buffer.cleared` event. properties: event_id: @@ -40494,15 +40494,15 @@ components: RealtimeClientEventInputAudioBufferCommit: type: object description: | - Send this event to commit the user input audio buffer, which will create a - new user message item in the conversation. This event will produce an error - if the input audio buffer is empty. When in Server VAD mode, the client does - not need to send this event, the server will commit the audio buffer + Send this event to commit the user input audio buffer, which will create a + new user message item in the conversation. This event will produce an error + if the input audio buffer is empty. When in Server VAD mode, the client does + not need to send this event, the server will commit the audio buffer automatically. - Committing the input audio buffer will trigger input audio transcription - (if enabled in session configuration), but it will not create a response - from the model. The server will respond with an `input_audio_buffer.committed` + Committing the input audio buffer will trigger input audio transcription + (if enabled in session configuration), but it will not create a response + from the model. The server will respond with an `input_audio_buffer.committed` event. properties: event_id: @@ -40527,9 +40527,9 @@ components: description: > **WebRTC Only:** Emit to cut off the current audio response. This will trigger the server to - stop generating audio and emit a `output_audio_buffer.cleared` event. This + stop generating audio and emit a `output_audio_buffer.cleared` event. This - event should be preceded by a `response.cancel` client event to stop the + event should be preceded by a `response.cancel` client event to stop the generation of the current response. @@ -40556,8 +40556,8 @@ components: RealtimeClientEventResponseCancel: type: object description: | - Send this event to cancel an in-progress response. The server will respond - with a `response.done` event with a status of `response.status=cancelled`. If + Send this event to cancel an in-progress response. The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If there is no response to cancel, the server will respond with an error. properties: event_id: @@ -40570,7 +40570,7 @@ components: response_id: type: string description: | - A specific response ID to cancel - if not provided, will cancel an + A specific response ID to cancel - if not provided, will cancel an in-progress response in the default conversation. required: - type @@ -40585,20 +40585,20 @@ components: RealtimeClientEventResponseCreate: type: object description: | - This event instructs the server to create a Response, which means triggering - model inference. When in Server VAD mode, the server will create Responses + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses automatically. - A Response will include at least one Item, and may have two, in which case - the second will be a function call. These Items will be appended to the + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the conversation history. - The server will respond with a `response.created` event, events for Items - and content created, and finally a `response.done` event to indicate the + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the Response is complete. - The `response.create` event includes inference configuration like - `instructions`, and `temperature`. These fields will override the Session's + The `response.create` event includes inference configuration like + `instructions`, and `temperature`. These fields will override the Session's configuration for this Response only. properties: event_id: @@ -40766,8 +40766,8 @@ components: id: type: string description: | - The unique ID of the item, this can be generated by the client to help - manage server-side context, but is not required because the server will + The unique ID of the item, this can be generated by the client to help + manage server-side context, but is not required because the server will generate one if not provided. type: type: string @@ -40791,8 +40791,8 @@ components: - incomplete - in_progress description: | - The status of the item (`completed`, `incomplete`, `in_progress`). These have no effect - on the conversation, but are accepted for consistency with the + The status of the item (`completed`, `incomplete`, `in_progress`). These have no effect + on the conversation, but are accepted for consistency with the `conversation.item.created` event. role: type: string @@ -40801,14 +40801,14 @@ components: - assistant - system description: | - The role of the message sender (`user`, `assistant`, `system`), only + The role of the message sender (`user`, `assistant`, `system`), only applicable for `message` items. content: type: array description: | - The content of the message, applicable for `message` items. + The content of the message, applicable for `message` items. - Message items of role `system` support only `input_text` content - - Message items of role `user` support `input_text` and `input_audio` + - Message items of role `user` support `input_text` and `input_audio` content - Message items of role `assistant` support `text` content. items: @@ -40816,9 +40816,9 @@ components: call_id: type: string description: | - The ID of the function call (for `function_call` and - `function_call_output` items). If passed on a `function_call_output` - item, the server will check that a `function_call` item with the same + The ID of the function call (for `function_call` and + `function_call_output` items). If passed on a `function_call_output` + item, the server will check that a `function_call` item with the same ID exists in the conversation history. name: type: string @@ -40868,8 +40868,8 @@ components: - incomplete - in_progress description: | - The status of the item (`completed`, `incomplete`, `in_progress`). These have no effect - on the conversation, but are accepted for consistency with the + The status of the item (`completed`, `incomplete`, `in_progress`). These have no effect + on the conversation, but are accepted for consistency with the `conversation.item.created` event. role: type: string @@ -40878,14 +40878,14 @@ components: - assistant - system description: | - The role of the message sender (`user`, `assistant`, `system`), only + The role of the message sender (`user`, `assistant`, `system`), only applicable for `message` items. content: type: array description: | - The content of the message, applicable for `message` items. + The content of the message, applicable for `message` items. - Message items of role `system` support only `input_text` content - - Message items of role `user` support `input_text` and `input_audio` + - Message items of role `user` support `input_text` and `input_audio` content - Message items of role `assistant` support `text` content. items: @@ -40921,9 +40921,9 @@ components: call_id: type: string description: | - The ID of the function call (for `function_call` and - `function_call_output` items). If passed on a `function_call_output` - item, the server will check that a `function_call` item with the same + The ID of the function call (for `function_call` and + `function_call_output` items). If passed on a `function_call_output` + item, the server will check that a `function_call` item with the same ID exists in the conversation history. name: type: string @@ -40957,7 +40957,7 @@ components: - incomplete - in_progress description: | - The final status of the response (`completed`, `cancelled`, `failed`, or + The final status of the response (`completed`, `cancelled`, `failed`, or `incomplete`, `in_progress`). status_details: type: object @@ -40971,8 +40971,8 @@ components: - incomplete - failed description: | - The type of error that caused the response to fail, corresponding - with the `status` field (`completed`, `cancelled`, `incomplete`, + The type of error that caused the response to fail, corresponding + with the `status` field (`completed`, `cancelled`, `incomplete`, `failed`). reason: type: string @@ -40982,15 +40982,15 @@ components: - max_output_tokens - content_filter description: | - The reason the Response did not complete. For a `cancelled` Response, - one of `turn_detected` (the server VAD detected a new start of speech) - or `client_cancelled` (the client sent a cancel event). For an - `incomplete` Response, one of `max_output_tokens` or `content_filter` + The reason the Response did not complete. For a `cancelled` Response, + one of `turn_detected` (the server VAD detected a new start of speech) + or `client_cancelled` (the client sent a cancel event). For an + `incomplete` Response, one of `max_output_tokens` or `content_filter` (the server-side safety filter activated and cut off the response). error: type: object description: | - A description of the error that caused the response to fail, + A description of the error that caused the response to fail, populated when the `status` is `failed`. properties: type: @@ -41009,25 +41009,25 @@ components: usage: type: object description: | - Usage statistics for the Response, this will correspond to billing. A - Realtime API session will maintain a conversation context and append new - Items to the Conversation, thus output from previous turns (text and + Usage statistics for the Response, this will correspond to billing. A + Realtime API session will maintain a conversation context and append new + Items to the Conversation, thus output from previous turns (text and audio tokens) will become the input for later turns. properties: total_tokens: type: integer description: | - The total number of tokens in the Response including input and output + The total number of tokens in the Response including input and output text and audio tokens. input_tokens: type: integer description: | - The number of input tokens used in the Response, including text and + The number of input tokens used in the Response, including text and audio tokens. output_tokens: type: integer description: | - The number of output tokens sent in the Response, including text and + The number of output tokens sent in the Response, including text and audio tokens. input_token_details: type: object @@ -41118,23 +41118,23 @@ components: instructions: type: string description: | - The default system instructions (i.e. system message) prepended to model - calls. This field allows the client to guide the model on desired - responses. The model can be instructed on response content and format, - (e.g. "be extremely succinct", "act friendly", "here are examples of good - responses") and on audio behavior (e.g. "talk quickly", "inject emotion - into your voice", "laugh frequently"). The instructions are not guaranteed - to be followed by the model, but they provide guidance to the model on the + The default system instructions (i.e. system message) prepended to model + calls. This field allows the client to guide the model on desired + responses. The model can be instructed on response content and format, + (e.g. "be extremely succinct", "act friendly", "here are examples of good + responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed + to be followed by the model, but they provide guidance to the model on the desired behavior. - Note that the server sets default instructions which will be used if this - field is not set and are visible in the `session.created` event at the + Note that the server sets default instructions which will be used if this + field is not set and are visible in the `session.created` event at the start of the session. voice: $ref: '#/components/schemas/VoiceIdsShared' description: | - The voice the model uses to respond. Voice cannot be changed during the - session once the model has responded with audio at least once. Current + The voice the model uses to respond. Voice cannot be changed during the + session once the model has responded with audio at least once. Current voice options are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`. output_audio_format: @@ -41163,8 +41163,8 @@ components: description: type: string description: | - The description of the function, including guidance on when and how - to call it, and guidance about what to tell the user when calling + The description of the function, including guidance on when and how + to call it, and guidance about what to tell the user when calling (if anything). parameters: type: object @@ -41172,7 +41172,7 @@ components: tool_choice: type: string description: | - How the model chooses tools. Options are `auto`, `none`, `required`, or + How the model chooses tools. Options are `auto`, `none`, `required`, or specify a function, like `{"type": "function", "function": {"name": "my_function"}}`. temperature: type: number @@ -41195,7 +41195,7 @@ components: Controls which conversation the response is added to. Currently supports `auto` and `none`, with `auto` as the default value. The `auto` value means that the contents of the response will be added to the default - conversation. Set this to `none` to create an out-of-band response which + conversation. Set this to `none` to create an out-of-band response which will not add items to default conversation. anyOf: - type: string @@ -41297,13 +41297,13 @@ components: type: object description: | Returned when a conversation item is created. There are several scenarios that produce this event: - - The server is generating a Response, which if successful will produce - either one or two Items, which will be of type `message` + - The server is generating a Response, which if successful will produce + either one or two Items, which will be of type `message` (role `assistant`) or type `function_call`. - - The input audio buffer has been committed, either by the client or the - server (in `server_vad` mode). The server will take the content of the + - The input audio buffer has been committed, either by the client or the + server (in `server_vad` mode). The server will take the content of the input audio buffer and add it to a new user message Item. - - The client has sent a `conversation.item.create` event to add a new Item + - The client has sent a `conversation.item.create` event to add a new Item to the Conversation. properties: event_id: @@ -41317,8 +41317,8 @@ components: type: string nullable: true description: | - The ID of the preceding item in the Conversation context, allows the - client to understand the order of the conversation. Can be `null` if the + The ID of the preceding item in the Conversation context, allows the + client to understand the order of the conversation. Can be `null` if the item has no predecessor. item: $ref: '#/components/schemas/RealtimeConversationItem' @@ -41346,8 +41346,8 @@ components: RealtimeServerEventConversationItemDeleted: type: object description: | - Returned when an item in the conversation is deleted by the client with a - `conversation.item.delete` event. This event is used to synchronize the + Returned when an item in the conversation is deleted by the client with a + `conversation.item.delete` event. This event is used to synchronize the server's understanding of the conversation history with the client's view. properties: event_id: @@ -41494,8 +41494,8 @@ components: RealtimeServerEventConversationItemInputAudioTranscriptionFailed: type: object description: | - Returned when input audio transcription is configured, and a transcription - request for a user message failed. These events are separate from other + Returned when input audio transcription is configured, and a transcription + request for a user message failed. These events are separate from other `error` events so that the client can identify the related Item. properties: event_id: @@ -41597,11 +41597,11 @@ components: RealtimeServerEventConversationItemTruncated: type: object description: | - Returned when an earlier assistant audio message item is truncated by the - client with a `conversation.item.truncate` event. This event is used to + Returned when an earlier assistant audio message item is truncated by the + client with a `conversation.item.truncate` event. This event is used to synchronize the server's understanding of the audio with the client's playback. - This action will truncate the audio and remove the server-side text transcript + This action will truncate the audio and remove the server-side text transcript to ensure there is no text in the context that hasn't been heard by the user. properties: event_id: @@ -41641,8 +41641,8 @@ components: RealtimeServerEventError: type: object description: | - Returned when an error occurs, which could be a client problem or a server - problem. Most errors are recoverable and the session will stay open, we + Returned when an error occurs, which could be a client problem or a server + problem. Most errors are recoverable and the session will stay open, we recommend to implementors to monitor and log error messages by default. properties: event_id: @@ -41701,7 +41701,7 @@ components: RealtimeServerEventInputAudioBufferCleared: type: object description: | - Returned when the input audio buffer is cleared by the client with a + Returned when the input audio buffer is cleared by the client with a `input_audio_buffer.clear` event. properties: event_id: @@ -41725,9 +41725,9 @@ components: RealtimeServerEventInputAudioBufferCommitted: type: object description: | - Returned when an input audio buffer is committed, either by the client or + Returned when an input audio buffer is committed, either by the client or automatically in server VAD mode. The `item_id` property is the ID of the user - message item that will be created, thus a `conversation.item.created` event + message item that will be created, thus a `conversation.item.created` event will also be sent to the client. properties: event_id: @@ -41763,15 +41763,15 @@ components: RealtimeServerEventInputAudioBufferSpeechStarted: type: object description: | - Sent by the server when in `server_vad` mode to indicate that speech has been - detected in the audio buffer. This can happen any time audio is added to the - buffer (unless speech is already detected). The client may want to use this - event to interrupt audio playback or provide visual feedback to the user. + Sent by the server when in `server_vad` mode to indicate that speech has been + detected in the audio buffer. This can happen any time audio is added to the + buffer (unless speech is already detected). The client may want to use this + event to interrupt audio playback or provide visual feedback to the user. - The client should expect to receive a `input_audio_buffer.speech_stopped` event - when speech stops. The `item_id` property is the ID of the user message item - that will be created when speech stops and will also be included in the - `input_audio_buffer.speech_stopped` event (unless the client manually commits + The client should expect to receive a `input_audio_buffer.speech_stopped` event + when speech stops. The `item_id` property is the ID of the user message item + that will be created when speech stops and will also be included in the + `input_audio_buffer.speech_stopped` event (unless the client manually commits the audio buffer during VAD activation). properties: event_id: @@ -41784,9 +41784,9 @@ components: audio_start_ms: type: integer description: | - Milliseconds from the start of all audio written to the buffer during the - session when speech was first detected. This will correspond to the - beginning of audio sent to the model, and thus includes the + Milliseconds from the start of all audio written to the buffer during the + session when speech was first detected. This will correspond to the + beginning of audio sent to the model, and thus includes the `prefix_padding_ms` configured in the Session. item_id: type: string @@ -41810,8 +41810,8 @@ components: RealtimeServerEventInputAudioBufferSpeechStopped: type: object description: | - Returned in `server_vad` mode when the server detects the end of speech in - the audio buffer. The server will also send an `conversation.item.created` + Returned in `server_vad` mode when the server detects the end of speech in + the audio buffer. The server will also send an `conversation.item.created` event with the user message item that is created from the audio buffer. properties: event_id: @@ -41824,8 +41824,8 @@ components: audio_end_ms: type: integer description: | - Milliseconds since the session started when speech stopped. This will - correspond to the end of audio sent to the model, and thus includes the + Milliseconds since the session started when speech stopped. This will + correspond to the end of audio sent to the model, and thus includes the `min_silence_duration_ms` configured in the Session. item_id: type: string @@ -41955,9 +41955,9 @@ components: RealtimeServerEventRateLimitsUpdated: type: object description: | - Emitted at the beginning of a Response to indicate the updated rate limits. - When a Response is created some tokens will be "reserved" for the output - tokens, the rate limits shown here reflect that reservation, which is then + Emitted at the beginning of a Response to indicate the updated rate limits. + When a Response is created some tokens will be "reserved" for the output + tokens, the rate limits shown here reflect that reservation, which is then adjusted accordingly once the Response is completed. properties: event_id: @@ -42378,8 +42378,8 @@ components: RealtimeServerEventResponseDone: type: object description: | - Returned when a Response is done streaming. Always emitted, no matter the - final state. The Response object included in the `response.done` event will + Returned when a Response is done streaming. Always emitted, no matter the + final state. The Response object included in the `response.done` event will include all output Items in the Response but will omit the raw audio data. properties: event_id: @@ -42587,7 +42587,7 @@ components: RealtimeServerEventResponseOutputItemDone: type: object description: | - Returned when an Item is done streaming. Also emitted when a Response is + Returned when an Item is done streaming. Also emitted when a Response is interrupted, incomplete, or cancelled. properties: event_id: @@ -42733,8 +42733,8 @@ components: RealtimeServerEventSessionCreated: type: object description: | - Returned when a Session is created. Emitted automatically when a new - connection is established as the first server event. This event will contain + Returned when a Session is created. Emitted automatically when a new + connection is established as the first server event. This event will contain the default Session configuration. properties: event_id: @@ -42784,7 +42784,7 @@ components: RealtimeServerEventSessionUpdated: type: object description: | - Returned when a session is updated with a `session.update` event, unless + Returned when a session is updated with a `session.update` event, unless there is an error. properties: event_id: @@ -42831,7 +42831,7 @@ components: RealtimeServerEventTranscriptionSessionUpdated: type: object description: | - Returned when a transcription session is updated with a `transcription_session.update` event, unless + Returned when a transcription session is updated with a `transcription_session.update` event, unless there is an error. properties: event_id: @@ -43880,7 +43880,7 @@ components: A new Realtime transcription session configuration. When a session is created on the server via REST API, the session object - also contains an ephemeral key. Default TTL for keys is 10 minutes. This + also contains an ephemeral key. Default TTL for keys is 10 minutes. This property is not present when a session is updated via the WebSocket API. properties: client_secret: @@ -43948,8 +43948,8 @@ components: turn_detection: type: object description: | - Configuration for turn detection. Can be set to `null` to turn off. Server - VAD means that the model will detect the start and end of speech based on + Configuration for turn detection. Can be set to `null` to turn off. Server + VAD means that the model will detect the start and end of speech based on audio volume and respond at the end of user speech. properties: type: @@ -43959,19 +43959,19 @@ components: threshold: type: number description: | - Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A - higher threshold will require louder audio to activate the model, and + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A + higher threshold will require louder audio to activate the model, and thus might perform better in noisy environments. prefix_padding_ms: type: integer description: | - Amount of audio to include before the VAD detected speech (in + Amount of audio to include before the VAD detected speech (in milliseconds). Defaults to 300ms. silence_duration_ms: type: integer description: | - Duration of silence to detect speech stop (in milliseconds). Defaults - to 500ms. With shorter values the model will respond more quickly, + Duration of silence to detect speech stop (in milliseconds). Defaults + to 500ms. With shorter values the model will respond more quickly, but may jump in on short pauses from the user. required: - client_secret @@ -44044,7 +44044,7 @@ components: default: medium nullable: true description: | - Constrains effort on reasoning for + Constrains effort on reasoning for [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently supported values are `minimal`, `low`, `medium`, and `high`. Reducing reasoning effort can result in faster responses and fewer tokens used @@ -44054,7 +44054,7 @@ components: description: | A description of the chain of thought used by a reasoning model while generating a response. Be sure to include these items in your `input` to the Responses API - for subsequent turns of a conversation if you are manually + for subsequent turns of a conversation if you are manually [managing context](https://platform.openai.com/docs/guides/conversation-state). title: Reasoning properties: @@ -44151,7 +44151,7 @@ components: status: type: string description: | - The status of the response generation. One of `completed`, `failed`, + The status of the response generation. One of `completed`, `failed`, `in_progress`, `cancelled`, `queued`, or `incomplete`. enum: - completed @@ -44185,7 +44185,7 @@ components: - The length and order of items in the `output` array is dependent on the model's response. - - Rather than accessing the first item in the `output` array and + - Rather than accessing the first item in the `output` array and assuming it's an `assistant` message with the content generated by the model, you might consider using the `output_text` property where supported in SDKs. @@ -44202,12 +44202,12 @@ components: anyOf: - type: string description: | - A text input to the model, equivalent to a text input with the + A text input to the model, equivalent to a text input with the `developer` role. - type: array title: Input item list description: | - A list of one or many input items to the model, containing + A list of one or many input items to the model, containing different content types. items: $ref: '#/components/schemas/InputItem' @@ -44215,8 +44215,8 @@ components: type: string nullable: true description: | - SDK-only convenience property that contains the aggregated text output - from all `output_text` items in the `output` array, if any are present. + SDK-only convenience property that contains the aggregated text output + from all `output_text` items in the `output` array, if any are present. Supported in the Python and JavaScript SDKs. x-oaiSupportedSDKs: - python @@ -45577,7 +45577,7 @@ components: "object": "response", "created_at": 1740855869, "status": "incomplete", - "error": null, + "error": null, "incomplete_details": { "reason": "max_tokens" }, @@ -45658,8 +45658,8 @@ components: ResponseLogProb: type: object description: | - A logprob is the logarithmic probability that the model assigns to producing - a particular token at a given position in the sequence. Less-negative (higher) + A logprob is the logarithmic probability that the model assigns to producing + a particular token at a given position in the sequence. Less-negative (higher) logprob values indicate greater model confidence in that token choice. properties: token: @@ -45994,8 +45994,8 @@ components: `["text"]` - The `gpt-4o-audio-preview` model can also be used to - [generate audio](https://platform.openai.com/docs/guides/audio). To request that this model generate + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that this model generate both text and audio responses, you can use: `["text", "audio"]` @@ -46946,7 +46946,7 @@ components: cached_tokens: type: integer description: | - The number of tokens that were retrieved from the cache. + The number of tokens that were retrieved from the cache. [More on prompt caching](https://platform.openai.com/docs/guides/prompt-caching). required: - cached_tokens @@ -47120,19 +47120,19 @@ components: item: type: object description: > - The dataset item provided to the grader. This will be used to populate + The dataset item provided to the grader. This will be used to populate the `item` namespace. See [the guide](https://platform.openai.com/docs/guides/graders) for more details. model_sample: type: string description: > - The model sample to be evaluated. This value will be used to populate + The model sample to be evaluated. This value will be used to populate the `sample` namespace. See [the guide](https://platform.openai.com/docs/guides/graders) for more details. - The `output_json` variable will be populated if the model sample is a + The `output_json` variable will be populated if the model sample is a valid JSON string. @@ -48425,7 +48425,7 @@ components: - screenshot default: screenshot description: | - Specifies the event type. For a screenshot action, this property is + Specifies the event type. For a screenshot action, this property is always set to `screenshot`. x-stainless-const: true required: @@ -48442,7 +48442,7 @@ components: - scroll default: scroll description: | - Specifies the event type. For a scroll action, this property is + Specifies the event type. For a scroll action, this property is always set to `scroll`. x-stainless-const: true x: @@ -48657,8 +48657,8 @@ components: description: | An object specifying the format that the model must output. - Configuring `{ "type": "json_schema" }` enables Structured Outputs, - which ensures the model will match your supplied JSON schema. Learn more in the + Configuring `{ "type": "json_schema" }` enables Structured Outputs, + which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). The default format is `{ "type": "text" }` with no additional options. @@ -49281,7 +49281,7 @@ components: - type default: type description: | - Specifies the event type. For a type action, this property is + Specifies the event type. For a type action, this property is always set to `type`. x-stainless-const: true text: @@ -49978,21 +49978,21 @@ components: type: integer default: 300 description: | - Amount of audio to include before the VAD detected speech (in + Amount of audio to include before the VAD detected speech (in milliseconds). silence_duration_ms: type: integer default: 200 description: | Duration of silence to detect speech stop (in milliseconds). - With shorter values the model will respond more quickly, + With shorter values the model will respond more quickly, but may jump in on short pauses from the user. threshold: type: number default: 0.5 description: | - Sensitivity threshold (0.0 to 1.0) for voice activity detection. A - higher threshold will require louder audio to activate the model, and + Sensitivity threshold (0.0 to 1.0) for voice activity detection. A + higher threshold will require louder audio to activate the model, and thus might perform better in noisy environments. ValidateGraderRequest: type: object @@ -50044,10 +50044,10 @@ components: VectorStoreFileAttributes: type: object description: | - Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. Keys are strings - with a maximum length of 64 characters. Values are strings with a maximum + Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. Keys are strings + with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters, booleans, or numbers. maxProperties: 16 propertyNames: @@ -50532,7 +50532,7 @@ components: - wait default: wait description: | - Specifies the event type. For a wait action, this property is + Specifies the event type. For a wait action, this property is always set to `wait`. x-stainless-const: true required: @@ -50607,7 +50607,7 @@ components: WebSearchContextSize: type: string description: | - High level guidance for the amount of context window space to use for the + High level guidance for the amount of context window space to use for the search. One of `low`, `medium`, or `high`. `medium` is the default. enum: - low @@ -50622,7 +50622,7 @@ components: country: type: string description: | - The two-letter + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of the user, e.g. `US`. region: @@ -50636,13 +50636,13 @@ components: timezone: type: string description: | - The [IANA timezone](https://timeapi.io/documentation/iana-timezones) + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the user, e.g. `America/Los_Angeles`. WebSearchToolCall: type: object title: Web search tool call description: | - The results of a web search tool call. See the + The results of a web search tool call. See the [web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information. properties: id: @@ -52116,7 +52116,7 @@ components: transcript: type: string description: | - The transcript of the audio, used for `input_audio` and `audio` + The transcript of the audio, used for `input_audio` and `audio` content types. RealtimeConnectParams: type: object @@ -52893,7 +52893,7 @@ x-oaiMeta: title: Webhook Events description: | Webhooks are HTTP requests sent by OpenAI to a URL you specify when certain - events happen during the course of API usage. + events happen during the course of API usage. [Learn more about webhooks](https://platform.openai.com/docs/guides/webhooks). navigationGroup: webhooks