fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2829 from hey-api/feat/event-hooks

feat(parser): add event hooks

authored by

Lubos and committed by
GitHub
b2ab11a5 f495969a

+505 -323
+5
.changeset/eager-bobcats-doubt.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + feat(parser): add `events` hooks
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-zod/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-zod/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+13 -13
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-all-of/transformers.gen.ts
··· 2 2 3 3 import type { GetFooResponse } from './types.gen'; 4 4 5 - export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const quxSchemaResponseTransformer = (data: any) => { 6 + if (data.baz) { 7 + data.baz = new Date(data.baz); 8 + } 7 9 return data; 8 10 }; 9 11 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = data.foo.map((item: any) => { 12 - return barSchemaResponseTransformer(item); 13 - }); 12 + const bazSchemaResponseTransformer = (data: any) => { 13 + data = quxSchemaResponseTransformer(data); 14 + data.bar = new Date(data.bar); 14 15 return data; 15 16 }; 16 17 ··· 21 22 return data; 22 23 }; 23 24 24 - const bazSchemaResponseTransformer = (data: any) => { 25 - data = quxSchemaResponseTransformer(data); 26 - data.bar = new Date(data.bar); 25 + const fooSchemaResponseTransformer = (data: any) => { 26 + data.foo = data.foo.map((item: any) => { 27 + return barSchemaResponseTransformer(item); 28 + }); 27 29 return data; 28 30 }; 29 31 30 - const quxSchemaResponseTransformer = (data: any) => { 31 - if (data.baz) { 32 - data.baz = new Date(data.baz); 33 - } 32 + export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 33 + data = fooSchemaResponseTransformer(data); 34 34 return data; 35 35 };
+7 -7
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-any-of-null/transformers.gen.ts
··· 2 2 3 3 import type { GetFooResponse } from './types.gen'; 4 4 5 - export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 6 - data = data.map((item: any) => { 7 - return fooSchemaResponseTransformer(item); 8 - }); 9 - return data; 10 - }; 11 - 12 5 const fooSchemaResponseTransformer = (data: any) => { 13 6 if (data.foo) { 14 7 data.foo = new Date(data.foo); ··· 21 14 } 22 15 return data; 23 16 }; 17 + 18 + export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 19 + data = data.map((item: any) => { 20 + return fooSchemaResponseTransformer(item); 21 + }); 22 + return data; 23 + };
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+4 -4
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-zod/transformers.gen.ts
··· 2 2 3 3 import type { PostFooResponse } from './types.gen'; 4 4 5 - export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const fooSchemaResponseTransformer = (data: any) => { 6 + data.foo = BigInt(data.foo.toString()); 7 7 return data; 8 8 }; 9 9 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = BigInt(data.foo.toString()); 10 + export const postFooResponseTransformer = async (data: any): Promise<PostFooResponse> => { 11 + data = fooSchemaResponseTransformer(data); 12 12 return data; 13 13 };
+13 -13
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-all-of/transformers.gen.ts
··· 2 2 3 3 import type { GetFooResponse } from './types.gen'; 4 4 5 - export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 6 - data = fooSchemaResponseTransformer(data); 5 + const quxSchemaResponseTransformer = (data: any) => { 6 + if (data.baz) { 7 + data.baz = new Date(data.baz); 8 + } 7 9 return data; 8 10 }; 9 11 10 - const fooSchemaResponseTransformer = (data: any) => { 11 - data.foo = data.foo.map((item: any) => { 12 - return barSchemaResponseTransformer(item); 13 - }); 12 + const bazSchemaResponseTransformer = (data: any) => { 13 + data = quxSchemaResponseTransformer(data); 14 + data.bar = new Date(data.bar); 14 15 return data; 15 16 }; 16 17 ··· 21 22 return data; 22 23 }; 23 24 24 - const bazSchemaResponseTransformer = (data: any) => { 25 - data = quxSchemaResponseTransformer(data); 26 - data.bar = new Date(data.bar); 25 + const fooSchemaResponseTransformer = (data: any) => { 26 + data.foo = data.foo.map((item: any) => { 27 + return barSchemaResponseTransformer(item); 28 + }); 27 29 return data; 28 30 }; 29 31 30 - const quxSchemaResponseTransformer = (data: any) => { 31 - if (data.baz) { 32 - data.baz = new Date(data.baz); 33 - } 32 + export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 33 + data = fooSchemaResponseTransformer(data); 34 34 return data; 35 35 };
+9 -9
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-any-of-null/transformers.gen.ts
··· 2 2 3 3 import type { GetFooResponse, NestedDateObjectResponse } from './types.gen'; 4 4 5 - export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 6 - data = data.map((item: any) => { 7 - return fooSchemaResponseTransformer(item); 8 - }); 9 - return data; 10 - }; 11 - 12 5 const fooSchemaResponseTransformer = (data: any) => { 13 6 if (data.foo) { 14 7 data.foo = new Date(data.foo); ··· 25 18 return data; 26 19 }; 27 20 28 - export const nestedDateObjectResponseTransformer = async (data: any): Promise<NestedDateObjectResponse> => { 29 - data = nestedDateObjectSchemaResponseTransformer(data); 21 + export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 22 + data = data.map((item: any) => { 23 + return fooSchemaResponseTransformer(item); 24 + }); 30 25 return data; 31 26 }; 32 27 ··· 38 33 } 39 34 return data; 40 35 }; 36 + 37 + export const nestedDateObjectResponseTransformer = async (data: any): Promise<NestedDateObjectResponse> => { 38 + data = nestedDateObjectSchemaResponseTransformer(data); 39 + return data; 40 + };
+32 -4
packages/openapi-ts-tests/main/test/openapi-ts.config.ts
··· 40 40 // 'dutchie.json', 41 41 // 'invalid', 42 42 // 'openai.yaml', 43 - 'full.yaml', 43 + // 'full.yaml', 44 44 // 'opencode.yaml', 45 45 // 'sdk-instance.yaml', 46 46 // 'string-with-format.yaml', 47 - // 'transformers.json', 47 + 'transformers.json', 48 48 // 'type-format.yaml', 49 49 // 'validators.yaml', 50 50 // 'validators-circular-ref.json', ··· 124 124 // }, 125 125 }, 126 126 hooks: { 127 + events: { 128 + // 'plugin:handler:after': ({ plugin }) => { 129 + // console.log(`(${plugin.name}): handler finished`); 130 + // }, 131 + // 'plugin:handler:before': ({ plugin }) => { 132 + // console.log(`(${plugin.name}): handler starting`); 133 + // }, 134 + // 'symbol:register:after': ({ plugin, symbol }) => { 135 + // console.log(`(global, ${plugin.name}) registered:`, symbol.id); 136 + // }, 137 + // 'symbol:register:before': ({ plugin, symbol }) => { 138 + // console.log(`(global, ${plugin.name}):`, symbol.name); 139 + // }, 140 + // 'symbol:setValue:after': ({ plugin, symbol }) => { 141 + // console.log(`(${plugin.name}) set value:`, symbol.id); 142 + // }, 143 + // 'symbol:setValue:before': ({ plugin, symbol }) => { 144 + // console.log(`(${plugin.name}) setting value:`, symbol.id); 145 + // }, 146 + }, 127 147 operations: { 128 148 getKind() { 129 149 // noop ··· 237 257 // transformer: '@hey-api/transformers', 238 258 // transformer: true, 239 259 validator: { 240 - request: 'arktype', 241 - response: 'arktype', 260 + request: 'zod', 261 + response: 'zod', 242 262 }, 243 263 '~hooks': { 244 264 symbols: { ··· 326 346 // name: 'q{{name}}CoolWebhook', 327 347 // }, 328 348 '~hooks': { 349 + events: { 350 + // 'symbol:register:after': ({ plugin, symbol }) => { 351 + // console.log(`(${plugin.name}) registered:`, symbol.id); 352 + // }, 353 + // 'symbol:register:before': ({ plugin, symbol }) => { 354 + // console.log(`(${plugin.name}):`, symbol.name); 355 + // }, 356 + }, 329 357 symbols: { 330 358 // getFilePath: (symbol) => { 331 359 // if (symbol.name) {
+1 -98
packages/openapi-ts/src/ir/types.d.ts
··· 1 - import type { Symbol } from '@hey-api/codegen-core'; 2 - 3 1 import type { JsonSchemaDraft2020_12 } from '~/openApi/3.1.x/types/json-schema-draft-2020-12'; 4 2 import type { 5 3 SecuritySchemeObject, ··· 27 25 schemas?: Record<string, IRSchemaObject>; 28 26 } 29 27 30 - interface IRHooks { 31 - /** 32 - * Hooks specifically for overriding operations behavior. 33 - * 34 - * Use these to classify operations, decide which outputs to generate, 35 - * or apply custom behavior to individual operations. 36 - */ 37 - operations?: { 38 - /** 39 - * Classify the given operation into one or more kinds. 40 - * 41 - * Each kind determines how we treat the operation (e.g., generating queries or mutations). 42 - * 43 - * **Default behavior:** 44 - * - GET → 'query' 45 - * - DELETE, PATCH, POST, PUT → 'mutation' 46 - * 47 - * **Resolution order:** 48 - * 1. If `isQuery` or `isMutation` returns `true` or `false`, that overrides `getKind`. 49 - * 2. If `isQuery` or `isMutation` returns `undefined`, the result of `getKind` is used. 50 - * 51 - * @param operation - The operation object to classify. 52 - * @returns An array containing one or more of 'query' or 'mutation', or undefined to fallback to default behavior. 53 - * @example 54 - * getKind: (operation) => { 55 - * if (operation.method === 'get' && operation.path === '/search') { 56 - * return ['query', 'mutation']; 57 - * } 58 - * return; // fallback to default behavior 59 - * } 60 - */ 61 - getKind?: ( 62 - operation: IROperationObject, 63 - ) => ReadonlyArray<'mutation' | 'query'> | undefined; 64 - /** 65 - * Check if the given operation should be treated as a mutation. 66 - * 67 - * This affects which outputs are generated for the operation. 68 - * 69 - * **Default behavior:** DELETE, PATCH, POST, and PUT operations are treated as mutations. 70 - * 71 - * **Resolution order:** If this returns `true` or `false`, it overrides `getKind`. 72 - * If it returns `undefined`, `getKind` is used instead. 73 - * 74 - * @param operation - The operation object to check. 75 - * @returns true if the operation is a mutation, false otherwise, or undefined to fallback to `getKind`. 76 - * @example 77 - * isMutation: (operation) => { 78 - * if (operation.method === 'post' && operation.path === '/search') { 79 - * return true; 80 - * } 81 - * return; // fallback to default behavior 82 - } 83 - */ 84 - isMutation?: (operation: IROperationObject) => boolean | undefined; 85 - /** 86 - * Check if the given operation should be treated as a query. 87 - * 88 - * This affects which outputs are generated for the operation. 89 - * 90 - * **Default behavior:** GET operations are treated as queries. 91 - * 92 - * **Resolution order:** If this returns `true` or `false`, it overrides `getKind`. 93 - * If it returns `undefined`, `getKind` is used instead. 94 - * 95 - * @param operation - The operation object to check. 96 - * @returns true if the operation is a query, false otherwise, or undefined to fallback to `getKind`. 97 - * @example 98 - * isQuery: (operation) => { 99 - * if (operation.method === 'post' && operation.path === '/search') { 100 - * return true; 101 - * } 102 - * return; // fallback to default behavior 103 - } 104 - */ 105 - isQuery?: (operation: IROperationObject) => boolean | undefined; 106 - }; 107 - /** 108 - * Hooks specifically for overriding symbols behavior. 109 - * 110 - * Use these to customize file placement. 111 - */ 112 - symbols?: { 113 - /** 114 - * Optional output strategy to override default plugin behavior. 115 - * 116 - * Use this to route generated symbols to specific files. 117 - * 118 - * @returns The file path to output the symbol to, or undefined to fallback to default behavior. 119 - */ 120 - getFilePath?: (symbol: Symbol) => string | undefined; 121 - }; 122 - } 123 - 124 - interface IROperationObject { 28 + export interface IROperationObject { 125 29 body?: IRBodyObject; 126 30 deprecated?: boolean; 127 31 description?: string; ··· 318 222 export type BodyObject = IRBodyObject; 319 223 export type ComponentsObject = IRComponentsObject; 320 224 export type Context<Spec extends Record<string, any> = any> = IRContext<Spec>; 321 - export type Hooks = IRHooks; 322 225 export type Model = IRModel; 323 226 export type OperationObject = IROperationObject; 324 227 export type ParameterObject = IRParameterObject;
+189
packages/openapi-ts/src/parser/types/hooks.d.ts
··· 1 + import type { Symbol, SymbolIn } from '@hey-api/codegen-core'; 2 + 3 + import type { IROperationObject } from '~/ir/types'; 4 + import type { PluginInstance } from '~/plugins/shared/utils/instance'; 5 + 6 + export type Hooks = { 7 + /** 8 + * Event hooks. 9 + */ 10 + events?: { 11 + /** 12 + * Triggered after executing a plugin handler. 13 + * 14 + * @param args Arguments object. 15 + * @returns void 16 + */ 17 + 'plugin:handler:after'?: (args: { 18 + /** Plugin that just executed. */ 19 + plugin: PluginInstance; 20 + }) => void; 21 + /** 22 + * Triggered before executing a plugin handler. 23 + * 24 + * @param args Arguments object. 25 + * @returns void 26 + */ 27 + 'plugin:handler:before'?: (args: { 28 + /** Plugin about to execute. */ 29 + plugin: PluginInstance; 30 + }) => void; 31 + /** 32 + * Triggered after registering a symbol. 33 + * 34 + * You can use this to perform actions after a symbol is registered. 35 + * 36 + * @param args Arguments object. 37 + * @returns void 38 + */ 39 + 'symbol:register:after'?: (args: { 40 + /** Plugin that registered the symbol. */ 41 + plugin: PluginInstance; 42 + /** The registered symbol. */ 43 + symbol: Symbol; 44 + }) => void; 45 + /** 46 + * Triggered before registering a symbol. 47 + * 48 + * You can use this to modify the symbol before it's registered. 49 + * 50 + * @param args Arguments object. 51 + * @returns void 52 + */ 53 + 'symbol:register:before'?: (args: { 54 + /** Plugin registering the symbol. */ 55 + plugin: PluginInstance; 56 + /** Symbol to register. */ 57 + symbol: SymbolIn; 58 + }) => void; 59 + /** 60 + * Triggered after setting a symbol value. 61 + * 62 + * You can use this to perform actions after a symbol's value is set. 63 + * 64 + * @param args Arguments object. 65 + * @returns void 66 + */ 67 + 'symbol:setValue:after'?: (args: { 68 + /** Plugin that set the symbol value. */ 69 + plugin: PluginInstance; 70 + /** The symbol. */ 71 + symbol: Symbol; 72 + /** The value that was set. */ 73 + value: unknown; 74 + }) => void; 75 + /** 76 + * Triggered before setting a symbol value. 77 + * 78 + * You can use this to modify the value before it's set. 79 + * 80 + * @param args Arguments object. 81 + * @returns void 82 + */ 83 + 'symbol:setValue:before'?: (args: { 84 + /** Plugin setting the symbol value. */ 85 + plugin: PluginInstance; 86 + /** The symbol. */ 87 + symbol: Symbol; 88 + /** The value to set. */ 89 + value: unknown; 90 + }) => void; 91 + }; 92 + /** 93 + * Hooks specifically for overriding operations behavior. 94 + * 95 + * Use these to classify operations, decide which outputs to generate, 96 + * or apply custom behavior to individual operations. 97 + */ 98 + operations?: { 99 + /** 100 + * Classify the given operation into one or more kinds. 101 + * 102 + * Each kind determines how we treat the operation (e.g., generating queries or mutations). 103 + * 104 + * **Default behavior:** 105 + * - GET → 'query' 106 + * - DELETE, PATCH, POST, PUT → 'mutation' 107 + * 108 + * **Resolution order:** 109 + * 1. If `isQuery` or `isMutation` returns `true` or `false`, that overrides `getKind`. 110 + * 2. If `isQuery` or `isMutation` returns `undefined`, the result of `getKind` is used. 111 + * 112 + * @param operation - The operation object to classify. 113 + * @returns An array containing one or more of 'query' or 'mutation', or undefined to fallback to default behavior. 114 + * @example 115 + * ```ts 116 + * getKind: (operation) => { 117 + * if (operation.method === 'get' && operation.path === '/search') { 118 + * return ['query', 'mutation']; 119 + * } 120 + * return; // fallback to default behavior 121 + * } 122 + * ``` 123 + */ 124 + getKind?: ( 125 + operation: IROperationObject, 126 + ) => ReadonlyArray<'mutation' | 'query'> | undefined; 127 + /** 128 + * Check if the given operation should be treated as a mutation. 129 + * 130 + * This affects which outputs are generated for the operation. 131 + * 132 + * **Default behavior:** DELETE, PATCH, POST, and PUT operations are treated as mutations. 133 + * 134 + * **Resolution order:** If this returns `true` or `false`, it overrides `getKind`. 135 + * If it returns `undefined`, `getKind` is used instead. 136 + * 137 + * @param operation - The operation object to check. 138 + * @returns true if the operation is a mutation, false otherwise, or undefined to fallback to `getKind`. 139 + * @example 140 + * ```ts 141 + * isMutation: (operation) => { 142 + * if (operation.method === 'post' && operation.path === '/search') { 143 + * return true; 144 + * } 145 + * return; // fallback to default behavior 146 + * } 147 + * ``` 148 + */ 149 + isMutation?: (operation: IROperationObject) => boolean | undefined; 150 + /** 151 + * Check if the given operation should be treated as a query. 152 + * 153 + * This affects which outputs are generated for the operation. 154 + * 155 + * **Default behavior:** GET operations are treated as queries. 156 + * 157 + * **Resolution order:** If this returns `true` or `false`, it overrides `getKind`. 158 + * If it returns `undefined`, `getKind` is used instead. 159 + * 160 + * @param operation - The operation object to check. 161 + * @returns true if the operation is a query, false otherwise, or undefined to fallback to `getKind`. 162 + * @example 163 + * ```ts 164 + * isQuery: (operation) => { 165 + * if (operation.method === 'post' && operation.path === '/search') { 166 + * return true; 167 + * } 168 + * return; // fallback to default behavior 169 + * } 170 + * ``` 171 + */ 172 + isQuery?: (operation: IROperationObject) => boolean | undefined; 173 + }; 174 + /** 175 + * Hooks specifically for overriding symbols behavior. 176 + * 177 + * Use these to customize file placement. 178 + */ 179 + symbols?: { 180 + /** 181 + * Optional output strategy to override default plugin behavior. 182 + * 183 + * Use this to route generated symbols to specific files. 184 + * 185 + * @returns The file path to output the symbol to, or undefined to fallback to default behavior. 186 + */ 187 + getFilePath?: (symbol: Symbol) => string | undefined; 188 + }; 189 + };
+16 -2
packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.test.ts
··· 156 156 config: { 157 157 exportFromIndex: false, 158 158 }, 159 - context: {} as any, 159 + context: { 160 + config: { 161 + // @ts-expect-error 162 + parser: { 163 + hooks: {}, 164 + }, 165 + }, 166 + }, 160 167 dependencies: [], 161 168 gen: new Project({ 162 169 renderers: {}, ··· 322 329 config: { 323 330 exportFromIndex: false, 324 331 }, 325 - context: {} as any, 332 + context: { 333 + config: { 334 + // @ts-expect-error 335 + parser: { 336 + hooks: {}, 337 + }, 338 + }, 339 + }, 326 340 dependencies: [], 327 341 gen: new Project({ 328 342 renderers: {},
+32 -4
packages/openapi-ts/src/plugins/@hey-api/sdk/__tests__/plugin.test.ts
··· 192 192 config: { 193 193 exportFromIndex: false, 194 194 }, 195 - context: {} as any, 195 + context: { 196 + config: { 197 + // @ts-expect-error 198 + parser: { 199 + hooks: {}, 200 + }, 201 + }, 202 + }, 196 203 dependencies: [], 197 204 gen: new Project({ 198 205 renderers: {}, ··· 390 397 config: { 391 398 exportFromIndex: false, 392 399 }, 393 - context: {} as any, 400 + context: { 401 + config: { 402 + // @ts-expect-error 403 + parser: { 404 + hooks: {}, 405 + }, 406 + }, 407 + }, 394 408 dependencies: [], 395 409 gen: new Project({ 396 410 renderers: {}, ··· 549 563 config: { 550 564 exportFromIndex: false, 551 565 }, 552 - context: {} as any, 566 + context: { 567 + config: { 568 + // @ts-expect-error 569 + parser: { 570 + hooks: {}, 571 + }, 572 + }, 573 + }, 553 574 dependencies: [], 554 575 gen: new Project({ 555 576 renderers: {}, ··· 710 731 config: { 711 732 exportFromIndex: false, 712 733 }, 713 - context: {} as any, 734 + context: { 735 + config: { 736 + // @ts-expect-error 737 + parser: { 738 + hooks: {}, 739 + }, 740 + }, 741 + }, 714 742 dependencies: [], 715 743 gen: new Project({ 716 744 renderers: {},
+4 -8
packages/openapi-ts/src/plugins/@hey-api/sdk/operation.ts
··· 466 466 const pluginTransformers = plugin.getPluginOrThrow( 467 467 plugin.config.transformer, 468 468 ); 469 - const symbolResponseTransformer = plugin.getSymbol( 470 - pluginTransformers.api.selector('response', operation.id), 471 - ); 472 - if ( 473 - symbolResponseTransformer && 474 - plugin.getSymbolValue(symbolResponseTransformer) 475 - ) { 469 + const selector = pluginTransformers.api.selector('response', operation.id); 470 + if (plugin.isSymbolRegistered(selector)) { 471 + const ref = plugin.referenceSymbol(selector); 476 472 requestOptions.push({ 477 473 key: 'responseTransformer', 478 - value: symbolResponseTransformer.placeholder, 474 + value: ref.placeholder, 479 475 }); 480 476 } 481 477 }
+41 -53
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 61 61 const selector = plugin.api.selector('response-ref', schema.$ref); 62 62 63 63 if (!plugin.getSymbol(selector)) { 64 - const symbol = plugin.registerSymbol({ 65 - name: buildName({ 66 - config: { 67 - case: 'camelCase', 68 - name: '{{name}}SchemaResponseTransformer', 69 - }, 70 - name: refToName(schema.$ref), 71 - }), 72 - selector, 73 - }); 74 - 75 64 // create each schema response transformer only once 76 65 const refSchema = plugin.context.resolveIrRef<IR.SchemaObject>( 77 66 schema.$ref, ··· 81 70 schema: refSchema, 82 71 }); 83 72 if (nodes.length) { 73 + const symbol = plugin.registerSymbol({ 74 + name: buildName({ 75 + config: { 76 + case: 'camelCase', 77 + name: '{{name}}SchemaResponseTransformer', 78 + }, 79 + name: refToName(schema.$ref), 80 + }), 81 + selector, 82 + }); 84 83 const node = tsc.constVariable({ 85 84 expression: tsc.arrowFunction({ 86 85 async: false, ··· 97 96 name: symbol.placeholder, 98 97 }); 99 98 plugin.setSymbolValue(symbol, node); 100 - } else { 101 - // the created schema response transformer was empty, do not generate 102 - // it and prevent any future attempts 103 - plugin.setSymbolValue(symbol, null); 104 99 } 105 100 } 106 101 107 - const symbolResponseTransformerRef = plugin.referenceSymbol(selector); 108 - if (plugin.getSymbolValue(symbolResponseTransformerRef) !== null) { 102 + if (plugin.isSymbolRegistered(selector)) { 103 + const ref = plugin.referenceSymbol(selector); 109 104 const callExpression = tsc.callExpression({ 110 - functionName: symbolResponseTransformerRef.placeholder, 105 + functionName: ref.placeholder, 111 106 parameters: [dataExpression], 112 107 }); 113 108 ··· 341 336 ); 342 337 if (!symbolResponse) return; 343 338 344 - const symbolResponseTransformer = plugin.registerSymbol({ 339 + // TODO: parser - consider handling simple string response which is also a date 340 + const nodes = schemaResponseTransformerNodes({ 341 + plugin, 342 + schema: response, 343 + }); 344 + if (!nodes.length) return; 345 + const symbol = plugin.registerSymbol({ 345 346 exported: true, 346 347 name: buildName({ 347 348 config: { ··· 352 353 }), 353 354 selector: plugin.api.selector('response', operation.id), 354 355 }); 355 - 356 - // TODO: parser - consider handling simple string response which is also a date 357 - const nodes = schemaResponseTransformerNodes({ 358 - plugin, 359 - schema: response, 360 - }); 361 - if (nodes.length) { 362 - const responseTransformerNode = tsc.constVariable({ 363 - exportConst: symbolResponseTransformer.exported, 364 - expression: tsc.arrowFunction({ 365 - async: true, 366 - multiLine: true, 367 - parameters: [ 368 - { 369 - name: dataVariableName, 370 - // TODO: parser - add types, generate types without transforms 371 - type: tsc.keywordTypeNode({ keyword: 'any' }), 372 - }, 356 + const value = tsc.constVariable({ 357 + exportConst: symbol.exported, 358 + expression: tsc.arrowFunction({ 359 + async: true, 360 + multiLine: true, 361 + parameters: [ 362 + { 363 + name: dataVariableName, 364 + // TODO: parser - add types, generate types without transforms 365 + type: tsc.keywordTypeNode({ keyword: 'any' }), 366 + }, 367 + ], 368 + returnType: tsc.typeReferenceNode({ 369 + typeArguments: [ 370 + tsc.typeReferenceNode({ typeName: symbolResponse.placeholder }), 373 371 ], 374 - returnType: tsc.typeReferenceNode({ 375 - typeArguments: [ 376 - tsc.typeReferenceNode({ typeName: symbolResponse.placeholder }), 377 - ], 378 - typeName: 'Promise', 379 - }), 380 - statements: ensureStatements(nodes), 372 + typeName: 'Promise', 381 373 }), 382 - name: symbolResponseTransformer.placeholder, 383 - }); 384 - plugin.setSymbolValue( 385 - symbolResponseTransformer, 386 - responseTransformerNode, 387 - ); 388 - } else { 389 - plugin.setSymbolValue(symbolResponseTransformer, null); 390 - } 374 + statements: ensureStatements(nodes), 375 + }), 376 + name: symbol.placeholder, 377 + }); 378 + plugin.setSymbolValue(symbol, value); 391 379 }, 392 380 { 393 381 order: 'declarations',
+8 -1
packages/openapi-ts/src/plugins/@hey-api/typescript/__tests__/plugin.test.ts
··· 185 185 config: { 186 186 exportFromIndex: false, 187 187 }, 188 - context: {} as any, 188 + context: { 189 + config: { 190 + // @ts-expect-error 191 + parser: { 192 + hooks: {}, 193 + }, 194 + }, 195 + }, 189 196 dependencies: [], 190 197 gen: new Project({ 191 198 renderers: {},
+1 -1
packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts
··· 141 141 }), 142 142 name: symbolMutationOptions.placeholder, 143 143 }); 144 - plugin.setSymbolValue(symbolMutationOptions.id, statement); 144 + plugin.setSymbolValue(symbolMutationOptions, statement); 145 145 };
+105 -81
packages/openapi-ts/src/plugins/shared/utils/instance.ts
··· 16 16 } from '~/ir/graph'; 17 17 import type { IR } from '~/ir/types'; 18 18 import type { OpenApi } from '~/openApi/types'; 19 + import type { Hooks } from '~/parser/types/hooks'; 19 20 import type { PluginConfigMap } from '~/plugins/config'; 20 21 import type { Plugin } from '~/plugins/types'; 21 22 import { jsonPointerToPath } from '~/utils/ref'; ··· 38 39 return symbol.meta.pluginName; 39 40 }; 40 41 41 - const defaultGetKind: Required<Required<IR.Hooks>['operations']>['getKind'] = ( 42 + const defaultGetKind: Required<Required<Hooks>['operations']>['getKind'] = ( 42 43 operation, 43 44 ) => { 44 45 switch (operation.method) { ··· 54 55 } 55 56 }; 56 57 58 + type EventHooks = { 59 + [K in keyof Required<NonNullable<Hooks['events']>>]: Array< 60 + NonNullable<NonNullable<Hooks['events']>[K]> 61 + >; 62 + }; 63 + 57 64 export class PluginInstance<T extends Plugin.Types = Plugin.Types> { 58 65 api: T['api']; 59 66 config: Omit<T['resolvedConfig'], 'name' | 'output'>; 60 67 context: IR.Context; 61 68 dependencies: Required<Plugin.Config<T>>['dependencies'] = []; 69 + private eventHooks: EventHooks; 62 70 gen: IProject; 63 71 private handler: Plugin.Config<T>['handler']; 64 72 name: T['resolvedConfig']['name']; ··· 87 95 this.config = props.config; 88 96 this.context = props.context; 89 97 this.dependencies = props.dependencies; 98 + this.eventHooks = this.buildEventHooks(); 90 99 this.gen = props.gen; 91 100 this.handler = props.handler; 92 101 this.name = props.name; ··· 336 345 } 337 346 } 338 347 339 - private forEachError(error: unknown, event: WalkEvent) { 340 - const originalError = 341 - error instanceof Error ? error : new Error(String(error)); 342 - throw new HeyApiError({ 343 - args: [event], 344 - error: originalError, 345 - event: event.type, 346 - name: 'Error', 347 - pluginName: this.name, 348 - }); 349 - } 350 - 351 348 /** 352 349 * Retrieves a registered plugin instance by its name from the context. This 353 350 * allows plugins to access other plugins that have been registered in the ··· 382 379 return this.gen.symbols.get(symbolIdOrSelector); 383 380 } 384 381 385 - private getSymbolFilePath(symbol: Symbol): string | undefined { 386 - const getFilePathFnPlugin = this.config['~hooks']?.symbols?.getFilePath; 387 - const getFilePathFnPluginResult = getFilePathFnPlugin?.(symbol); 388 - if (getFilePathFnPluginResult !== undefined) { 389 - return getFilePathFnPluginResult; 390 - } 391 - const getFilePathFnParser = 392 - this.context.config.parser.hooks.symbols?.getFilePath; 393 - const getFilePathFnParserResult = getFilePathFnParser?.(symbol); 394 - if (getFilePathFnParserResult !== undefined) { 395 - return getFilePathFnParserResult; 396 - } 397 - return defaultGetFilePath(symbol); 398 - } 399 - 400 - getSymbolValue(idOrSymbol: number | Symbol): unknown { 401 - return this.gen.symbols.getValue(this.symbolToId(idOrSymbol)); 402 - } 403 - 404 - hasSymbolValue(idOrSymbol: number | Symbol): boolean { 405 - return this.gen.symbols.hasValue(this.symbolToId(idOrSymbol)); 406 - } 407 - 408 382 hooks = { 409 383 operation: { 410 384 isMutation: (operation: IR.OperationObject): boolean => 411 385 this.isOperationKind(operation, 'mutation'), 412 386 isQuery: (operation: IR.OperationObject): boolean => 413 387 this.isOperationKind(operation, 'query'), 414 - }, 415 - symbol: { 416 - getFilePath: (symbol: Symbol): string | undefined => 417 - this.getSymbolFilePath(symbol), 418 388 }, 419 389 }; 420 390 421 - private isOperationKind( 422 - operation: IR.OperationObject, 423 - kind: 'mutation' | 'query', 424 - ): boolean { 425 - const methodName = kind === 'query' ? 'isQuery' : 'isMutation'; 426 - const isFnPlugin = this.config['~hooks']?.operations?.[methodName]; 427 - const isFnPluginResult = isFnPlugin?.(operation); 428 - if (isFnPluginResult !== undefined) { 429 - return isFnPluginResult; 430 - } 431 - const getKindFnPlugin = this.config['~hooks']?.operations?.getKind; 432 - const getKindFnPluginResult = getKindFnPlugin?.(operation); 433 - if (getKindFnPluginResult !== undefined) { 434 - return getKindFnPluginResult.includes(kind); 435 - } 436 - const isFnParser = 437 - this.context.config.parser.hooks.operations?.[methodName]; 438 - const isFnParserResult = isFnParser?.(operation); 439 - if (isFnParserResult !== undefined) { 440 - return isFnParserResult; 441 - } 442 - const getKindFnParser = 443 - this.context.config.parser.hooks.operations?.getKind; 444 - const getKindFnParserResult = getKindFnParser?.(operation); 445 - if (getKindFnParserResult !== undefined) { 446 - return getKindFnParserResult.includes(kind); 447 - } 448 - return (defaultGetKind(operation) ?? []).includes(kind); 449 - } 450 - 451 391 isSymbolRegistered(symbolIdOrSelector: number | Selector): boolean { 452 392 return this.gen.symbols.isRegistered(symbolIdOrSelector); 453 393 } ··· 457 397 } 458 398 459 399 registerSymbol(symbol: SymbolIn): Symbol { 460 - return this.gen.symbols.register({ 400 + const symbolIn: SymbolIn = { 461 401 ...symbol, 462 402 exportFrom: 463 403 symbol.exportFrom ?? ··· 466 406 this.config.exportFromIndex 467 407 ? ['index'] 468 408 : undefined), 469 - getFilePath: symbol.getFilePath ?? this.hooks.symbol.getFilePath, 409 + getFilePath: symbol.getFilePath ?? this.getSymbolFilePath.bind(this), 470 410 meta: { 471 411 pluginName: path.isAbsolute(this.name) ? 'custom' : this.name, 472 412 ...symbol.meta, 473 413 }, 474 - }); 414 + }; 415 + for (const hook of this.eventHooks['symbol:register:before']) { 416 + hook({ plugin: this, symbol: symbolIn }); 417 + } 418 + const symbolOut = this.gen.symbols.register(symbolIn); 419 + for (const hook of this.eventHooks['symbol:register:after']) { 420 + hook({ plugin: this, symbol: symbolOut }); 421 + } 422 + return symbolOut; 475 423 } 476 424 477 425 /** 478 426 * Executes plugin's handler function. 479 427 */ 480 - async run() { 428 + async run(): Promise<void> { 429 + for (const hook of this.eventHooks['plugin:handler:before']) { 430 + hook({ plugin: this }); 431 + } 481 432 await this.handler({ plugin: this }); 433 + for (const hook of this.eventHooks['plugin:handler:after']) { 434 + hook({ plugin: this }); 435 + } 482 436 } 483 437 484 - setSymbolValue( 485 - idOrSymbol: number | Symbol, 486 - value: unknown, 487 - ): Map<number, unknown> { 488 - return this.gen.symbols.setValue(this.symbolToId(idOrSymbol), value); 438 + setSymbolValue(symbol: Symbol, value: unknown): void { 439 + for (const hook of this.eventHooks['symbol:setValue:before']) { 440 + hook({ plugin: this, symbol, value }); 441 + } 442 + this.gen.symbols.setValue(symbol.id, value); 443 + for (const hook of this.eventHooks['symbol:setValue:after']) { 444 + hook({ plugin: this, symbol, value }); 445 + } 446 + } 447 + 448 + private buildEventHooks(): EventHooks { 449 + const result: EventHooks = { 450 + 'plugin:handler:after': [], 451 + 'plugin:handler:before': [], 452 + 'symbol:register:after': [], 453 + 'symbol:register:before': [], 454 + 'symbol:setValue:after': [], 455 + 'symbol:setValue:before': [], 456 + }; 457 + const scopes = [ 458 + this.config['~hooks']?.events, 459 + this.context.config.parser.hooks.events, 460 + ]; 461 + for (const scope of scopes) { 462 + if (!scope) continue; 463 + for (const [key, value] of Object.entries(scope)) { 464 + if (value) { 465 + result[key as keyof typeof result].push(value.bind(scope) as any); 466 + } 467 + } 468 + } 469 + return result; 470 + } 471 + 472 + private forEachError(error: unknown, event: WalkEvent) { 473 + const originalError = 474 + error instanceof Error ? error : new Error(String(error)); 475 + throw new HeyApiError({ 476 + args: [event], 477 + error: originalError, 478 + event: event.type, 479 + name: 'Error', 480 + pluginName: this.name, 481 + }); 489 482 } 490 483 491 - private symbolToId(idOrSymbol: number | Symbol): number { 492 - return typeof idOrSymbol === 'number' ? idOrSymbol : idOrSymbol.id; 484 + private getSymbolFilePath(symbol: Symbol): string | undefined { 485 + const hooks = [ 486 + this.config['~hooks']?.symbols, 487 + this.context.config.parser.hooks.symbols, 488 + ]; 489 + for (const hook of hooks) { 490 + const result = hook?.getFilePath?.(symbol); 491 + if (result !== undefined) return result; 492 + } 493 + return defaultGetFilePath(symbol); 494 + } 495 + 496 + private isOperationKind( 497 + operation: IR.OperationObject, 498 + kind: 'mutation' | 'query', 499 + ): boolean { 500 + const method = kind === 'query' ? 'isQuery' : 'isMutation'; 501 + const hooks = [ 502 + this.config['~hooks']?.operations?.[method], 503 + this.config['~hooks']?.operations?.getKind, 504 + this.context.config.parser.hooks.operations?.[method], 505 + this.context.config.parser.hooks.operations?.getKind, 506 + defaultGetKind, 507 + ]; 508 + for (const hook of hooks) { 509 + if (hook) { 510 + const result = hook(operation); 511 + if (result !== undefined) { 512 + return typeof result === 'boolean' ? result : result.includes(kind); 513 + } 514 + } 515 + } 516 + return false; 493 517 } 494 518 }
+2 -2
packages/openapi-ts/src/plugins/types.d.ts
··· 1 1 import type { ValueToObject } from '~/config/utils/config'; 2 2 import type { Package } from '~/config/utils/package'; 3 - import type { IR } from '~/ir/types'; 4 3 import type { OpenApi as LegacyOpenApi } from '~/openApi'; 4 + import type { Hooks } from '~/parser/types/hooks'; 5 5 import type { PluginInstance } from '~/plugins/shared/utils/instance'; 6 6 import type { Client as LegacyClient } from '~/types/client'; 7 7 import type { Files } from '~/types/utils'; ··· 68 68 * Use these to classify resources, control which outputs are generated, 69 69 * or provide custom behavior for specific resources. 70 70 */ 71 - '~hooks'?: IR.Hooks; 71 + '~hooks'?: Hooks; 72 72 }; 73 73 74 74 /**
+3 -3
packages/openapi-ts/src/types/parser.d.ts
··· 1 - import type { IR } from '~/ir/types'; 2 1 import type { 3 2 OpenApiMetaObject, 4 3 OpenApiOperationObject, ··· 7 6 OpenApiResponseObject, 8 7 OpenApiSchemaObject, 9 8 } from '~/openApi/types'; 9 + import type { Hooks } from '~/parser/types/hooks'; 10 10 11 11 import type { StringCase, StringName } from './case'; 12 12 ··· 24 24 * Use these to classify resources, control which outputs are generated, 25 25 * or provide custom behavior for specific resources. 26 26 */ 27 - hooks?: IR.Hooks; 27 + hooks?: Hooks; 28 28 /** 29 29 * Pagination configuration. 30 30 */ ··· 206 206 * Use these to classify resources, control which outputs are generated, 207 207 * or provide custom behavior for specific resources. 208 208 */ 209 - hooks: IR.Hooks; 209 + hooks: Hooks; 210 210 /** 211 211 * Pagination configuration. 212 212 */