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

Merge pull request #2942 from hey-api/chore/dsl-func

fix(@pinia/colada): correctly access instantiated SDKs

authored by

Lubos and committed by
GitHub
52fbf531 520251d7

+550 -217
+5
.changeset/lemon-plants-shop.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + @pinia/colada: correctly access instantiated SDKs
+2 -2
dev/openapi-ts.config.ts
··· 42 42 // 'dutchie.json', 43 43 // 'invalid', 44 44 // 'full.yaml', 45 - 'openai.yaml', 46 - // 'opencode.yaml', 45 + // 'openai.yaml', 46 + 'opencode.yaml', 47 47 // 'sdk-instance.yaml', 48 48 // 'string-with-format.yaml', 49 49 // 'transformers.json',
+5 -1
packages/openapi-ts/src/generate/renderer.ts
··· 13 13 import ts from 'typescript'; 14 14 15 15 import { ensureValidIdentifier } from '~/openApi/shared/utils/identifier'; 16 + import { TsDsl } from '~/ts-dsl'; 16 17 import { tsc } from '~/tsc'; 17 18 import { tsNodeToString } from '~/tsc/utils'; 18 19 ··· 186 187 187 188 for (const symbolId of file.symbols.body) { 188 189 const value = project.symbols.getValue(symbolId); 189 - if (typeof value === 'string') { 190 + if (value instanceof TsDsl) { 191 + const node = value.$render(); 192 + lines.push(tsNodeToString({ node, unescape: true })); 193 + } else if (typeof value === 'string') { 190 194 lines.push(value); 191 195 } else if (value instanceof Array) { 192 196 for (const node of value) {
+6 -7
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts
··· 387 387 plugin.config.instance 388 388 ? $.new(refChildClass.placeholder).args( 389 389 $.object((o) => 390 - o.prop('client', () => $('this').attr('client')), 390 + o.prop('client', $('this').attr('client')), 391 391 ), 392 392 ) 393 393 : $(refChildClass.placeholder), ··· 402 402 ? $.return( 403 403 $.new(refChildClass.placeholder).args( 404 404 $.object((o) => 405 - o.prop('client', () => $('this').attr('client')), 405 + o.prop('client', $('this').attr('client')), 406 406 ), 407 407 ), 408 408 ) ··· 426 426 const node = createClientClass({ 427 427 plugin, 428 428 symbol: symbolHeyApiClient, 429 - }).$render(); 429 + }); 430 430 plugin.setSymbolValue(symbolHeyApiClient, node); 431 431 } 432 432 ··· 486 486 plugin, 487 487 sdkName: symbol.placeholder, 488 488 symbol: symbolHeyApiRegistry, 489 - }).$render(); 489 + }); 490 490 plugin.setSymbolValue(symbolHeyApiRegistry, node); 491 491 const registryNode = $.field(registryName, (f) => 492 492 f ··· 509 509 category: 'external', 510 510 resource: '@angular/core.Injectable', 511 511 }).placeholder, 512 - (o) => o.prop('providedIn', () => $.literal('root')), 512 + (o) => o.prop('providedIn', $.literal('root')), 513 513 ), 514 514 ) 515 - .do(...currentClass.nodes) 516 - .$render(); 515 + .do(...currentClass.nodes); 517 516 plugin.setSymbolValue(symbol, node); 518 517 }; 519 518
+31 -26
packages/openapi-ts/src/plugins/@pinia/colada/v0/plugin.ts
··· 1 + import { registryName } from '~/plugins/@hey-api/sdk/shared/class'; 1 2 import { operationClasses } from '~/plugins/@hey-api/sdk/shared/operation'; 2 3 import { stringCase } from '~/utils/stringCase'; 3 4 ··· 64 65 }) 65 66 : undefined; 66 67 const entry = classes ? classes.values().next().value : undefined; 67 - const queryFn = 68 - // TODO: this should use class graph to determine correct path string 69 - // as it's really easy to break once we change the class casing 70 - entry 71 - ? [ 72 - plugin.referenceSymbol({ 73 - category: 'utility', 74 - resource: 'class', 75 - resourceId: entry.path[0], 76 - tool: 'sdk', 77 - }).placeholder, 78 - ...entry.path.slice(1).map((className: string) => 79 - stringCase({ 80 - case: 'camelCase', 81 - value: className, 82 - }), 83 - ), 84 - entry.methodName, 85 - ] 86 - .filter(Boolean) 87 - .join('.') 88 - : plugin.referenceSymbol({ 89 - category: 'sdk', 90 - resource: 'operation', 91 - resourceId: operation.id, 92 - }).placeholder; 68 + // TODO: this should use class graph to determine correct path string 69 + // as it's really easy to break once we change the class casing 70 + let queryFn: string; 71 + if (entry) { 72 + const symbolClass = plugin.referenceSymbol({ 73 + category: 'utility', 74 + resource: 'class', 75 + resourceId: entry.path[0], 76 + tool: 'sdk', 77 + }); 78 + queryFn = [ 79 + symbolClass.placeholder, 80 + ...(sdkPlugin.config.instance ? [registryName, 'get()'] : []), 81 + ...entry.path.slice(1).map((className) => 82 + stringCase({ 83 + case: 'camelCase', 84 + value: className, 85 + }), 86 + ), 87 + entry.methodName, 88 + ] 89 + .filter(Boolean) 90 + .join('.'); 91 + } else { 92 + queryFn = plugin.referenceSymbol({ 93 + category: 'sdk', 94 + resource: 'operation', 95 + resourceId: operation.id, 96 + }).placeholder; 97 + } 93 98 94 99 if (plugin.hooks.operation.isQuery(operation)) { 95 100 if (plugin.config.queryOptions.enabled) {
+31 -26
packages/openapi-ts/src/plugins/swr/v2/plugin.ts
··· 1 + import { registryName } from '~/plugins/@hey-api/sdk/shared/class'; 1 2 import { operationClasses } from '~/plugins/@hey-api/sdk/shared/operation'; 2 3 import { stringCase } from '~/utils/stringCase'; 3 4 ··· 29 30 }) 30 31 : undefined; 31 32 const entry = classes ? classes.values().next().value : undefined; 32 - const queryFn = 33 - // TODO: this should use class graph to determine correct path string 34 - // as it's really easy to break once we change the class casing 35 - entry 36 - ? [ 37 - plugin.referenceSymbol({ 38 - category: 'utility', 39 - resource: 'class', 40 - resourceId: entry.path[0], 41 - tool: 'sdk', 42 - }).placeholder, 43 - ...entry.path.slice(1).map((className) => 44 - stringCase({ 45 - case: 'camelCase', 46 - value: className, 47 - }), 48 - ), 49 - entry.methodName, 50 - ] 51 - .filter(Boolean) 52 - .join('.') 53 - : plugin.referenceSymbol({ 54 - category: 'sdk', 55 - resource: 'operation', 56 - resourceId: operation.id, 57 - }).placeholder; 33 + // TODO: this should use class graph to determine correct path string 34 + // as it's really easy to break once we change the class casing 35 + let queryFn: string; 36 + if (entry) { 37 + const symbolClass = plugin.referenceSymbol({ 38 + category: 'utility', 39 + resource: 'class', 40 + resourceId: entry.path[0], 41 + tool: 'sdk', 42 + }); 43 + queryFn = [ 44 + symbolClass.placeholder, 45 + ...(sdkPlugin.config.instance ? [registryName, 'get()'] : []), 46 + ...entry.path.slice(1).map((className) => 47 + stringCase({ 48 + case: 'camelCase', 49 + value: className, 50 + }), 51 + ), 52 + entry.methodName, 53 + ] 54 + .filter(Boolean) 55 + .join('.'); 56 + } else { 57 + queryFn = plugin.referenceSymbol({ 58 + category: 'sdk', 59 + resource: 'operation', 60 + resourceId: operation.id, 61 + }).placeholder; 62 + } 58 63 59 64 if (plugin.hooks.operation.isQuery(operation)) { 60 65 // if (plugin.config.queryOptions.enabled) {
+28 -76
packages/openapi-ts/src/plugins/swr/v2/useSwr.ts
··· 6 6 createOperationComment, 7 7 hasOperationSse, 8 8 } from '~/plugins/shared/utils/operation'; 9 - import { tsc } from '~/tsc'; 9 + import type { TsDsl } from '~/ts-dsl'; 10 + import { $ } from '~/ts-dsl'; 10 11 11 12 import type { SwrPlugin } from '../types'; 12 13 ··· 35 36 }), 36 37 }); 37 38 38 - const awaitSdkExpression = tsc.awaitExpression({ 39 - expression: tsc.callExpression({ 40 - functionName: queryFn, 41 - parameters: [ 42 - tsc.objectExpression({ 43 - multiLine: true, 44 - obj: [ 45 - // { 46 - // spread: optionsParamName, 47 - // }, 48 - // { 49 - // spread: 'queryKey[0]', 50 - // }, 51 - // { 52 - // key: 'signal', 53 - // shorthand: true, 54 - // value: tsc.identifier({ 55 - // text: 'signal', 56 - // }), 57 - // }, 58 - { 59 - key: 'throwOnError', 60 - value: true, 61 - }, 62 - ], 63 - }), 64 - ], 65 - }), 66 - }); 67 - const statements: Array<ts.Statement> = []; 39 + const awaitSdkFn = $(queryFn) 40 + .call($.object((o) => o.prop('throwOnError', $.literal(true)))) 41 + .await(); 42 + 43 + const statements: Array<ts.Statement | TsDsl<any>> = []; 68 44 if (plugin.getPluginOrThrow('@hey-api/sdk').config.responseStyle === 'data') { 69 - statements.push( 70 - tsc.returnVariable({ 71 - expression: awaitSdkExpression, 72 - }), 73 - ); 45 + statements.push($.return(awaitSdkFn)); 74 46 } else { 75 47 statements.push( 76 - tsc.constVariable({ 77 - destructure: true, 78 - expression: awaitSdkExpression, 79 - name: 'data', 80 - }), 81 - tsc.returnVariable({ 82 - expression: 'data', 83 - }), 48 + $.const().object('data').assign(awaitSdkFn), 49 + $.return('data'), 84 50 ); 85 51 } 86 52 87 - const statement = tsc.constVariable({ 88 - comment: plugin.config.comments 89 - ? createOperationComment({ operation }) 90 - : undefined, 91 - exportConst: symbolUseQueryFn.exported, 92 - expression: tsc.arrowFunction({ 93 - parameters: [ 94 - // { 95 - // isRequired: isRequiredOptions, 96 - // name: optionsParamName, 97 - // type: typeData, 98 - // }, 99 - ], 100 - statements: [ 101 - tsc.returnStatement({ 102 - expression: tsc.callExpression({ 103 - functionName: symbolUseSwr.placeholder, 104 - parameters: [ 105 - tsc.stringLiteral({ 106 - text: operation.path, 107 - }), 108 - tsc.arrowFunction({ 109 - async: true, 110 - statements, 111 - }), 112 - ], 113 - }), 114 - }), 115 - ], 116 - }), 117 - name: symbolUseQueryFn.placeholder, 118 - }); 53 + const statement = $.const(symbolUseQueryFn.placeholder) 54 + .export(symbolUseQueryFn.exported) 55 + .$if( 56 + plugin.config.comments && createOperationComment({ operation }), 57 + (c, v) => c.describe(v as Array<string>), 58 + ) 59 + .assign( 60 + $.func().do( 61 + $.return( 62 + $(symbolUseSwr.placeholder).call( 63 + $.literal(operation.path), 64 + $.func() 65 + .async() 66 + .do(...statements), 67 + ), 68 + ), 69 + ), 70 + ); 119 71 plugin.setSymbolValue(symbolUseQueryFn, statement); 120 72 };
+18
packages/openapi-ts/src/ts-dsl/await.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import type { ExprInput, MaybeTsDsl } from './base'; 4 + import { TsDsl } from './base'; 5 + 6 + export class AwaitTsDsl extends TsDsl<ts.AwaitExpression> { 7 + private exprNode: MaybeTsDsl<ExprInput>; 8 + 9 + constructor(expr: MaybeTsDsl<ExprInput>) { 10 + super(); 11 + this.exprNode = expr; 12 + } 13 + 14 + $render(): ts.AwaitExpression { 15 + const expr = this.$node(this.exprNode); 16 + return ts.factory.createAwaitExpression(expr); 17 + } 18 + }
+61 -10
packages/openapi-ts/src/ts-dsl/base.ts
··· 23 23 value: V, 24 24 ifTrue: (self: T, v: Exclude<V, false | null | undefined>) => R | void, 25 25 ifFalse?: (self: T, v: Extract<V, false | null | undefined>) => R | void, 26 + ): R | T; 27 + $if<T extends TsDsl, V, R extends TsDsl = T>( 28 + this: T, 29 + value: V, 30 + ifTrue: (v: Exclude<V, false | null | undefined>) => R | void, 31 + ifFalse?: (v: Extract<V, false | null | undefined>) => R | void, 32 + ): R | T; 33 + $if<T extends TsDsl, V, R extends TsDsl = T>( 34 + this: T, 35 + value: V, 36 + ifTrue: () => R | void, 37 + ifFalse?: () => R | void, 38 + ): R | T; 39 + $if<T extends TsDsl, V, R extends TsDsl = T>( 40 + this: T, 41 + value: V, 42 + ifTrue: any, 43 + ifFalse?: any, 26 44 ): R | T { 27 45 if (value) { 28 - const result = ifTrue( 29 - this, 30 - value as Exclude<V, false | null | undefined>, 31 - ); 32 - return result ?? this; 46 + // Try calling with (self, value), then (value), then () 47 + let result: R | void | undefined; 48 + try { 49 + result = ifTrue?.(this, value as Exclude<V, false | null | undefined>); 50 + } catch { 51 + // ignore and try other signatures 52 + } 53 + if (result === undefined) { 54 + try { 55 + result = ifTrue?.(value as Exclude<V, false | null | undefined>); 56 + } catch { 57 + // ignore and try zero-arg 58 + } 59 + } 60 + if (result === undefined) { 61 + try { 62 + result = ifTrue?.(); 63 + } catch { 64 + // swallow 65 + } 66 + } 67 + return (result ?? this) as R | T; 33 68 } 34 69 if (ifFalse) { 35 - const result = ifFalse( 36 - this, 37 - value as Extract<V, false | null | undefined>, 38 - ); 39 - return result ?? this; 70 + let result: R | void | undefined; 71 + try { 72 + result = ifFalse?.(this, value as Extract<V, false | null | undefined>); 73 + } catch { 74 + // ignore 75 + } 76 + if (result === undefined) { 77 + try { 78 + result = ifFalse?.(value as Extract<V, false | null | undefined>); 79 + } catch { 80 + // ignore 81 + } 82 + } 83 + if (result === undefined) { 84 + try { 85 + result = ifFalse?.(); 86 + } catch { 87 + // ignore 88 + } 89 + } 90 + return (result ?? this) as R | T; 40 91 } 41 92 return this; 42 93 }
+6
packages/openapi-ts/src/ts-dsl/call.ts
··· 1 1 import ts from 'typescript'; 2 2 3 + import { AwaitTsDsl } from './await'; 3 4 import type { ExprInput, MaybeTsDsl } from './base'; 4 5 import { TsDsl } from './base'; 5 6 ··· 20 21 args(...args: ReadonlyArray<MaybeTsDsl<ExprInput>>): this { 21 22 this.callArgs = args; 22 23 return this; 24 + } 25 + 26 + /** Await the result of the call expression. */ 27 + await(): AwaitTsDsl { 28 + return new AwaitTsDsl(this); 23 29 } 24 30 25 31 $render(): ts.CallExpression {
-52
packages/openapi-ts/src/ts-dsl/const.ts
··· 1 - /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ 2 - import ts from 'typescript'; 3 - 4 - import { TsDsl } from './base'; 5 - import { mixin } from './mixins/apply'; 6 - import { DescribeMixin } from './mixins/describe'; 7 - import { 8 - createModifierAccessor, 9 - DefaultMixin, 10 - ExportMixin, 11 - } from './mixins/modifiers'; 12 - import { ValueMixin } from './mixins/value'; 13 - 14 - export class ConstTsDsl extends TsDsl<ts.VariableStatement> { 15 - private modifiers = createModifierAccessor(this); 16 - private name: string; 17 - 18 - constructor(name: string) { 19 - super(); 20 - this.name = name; 21 - } 22 - 23 - $render(): ts.VariableStatement { 24 - return ts.factory.createVariableStatement( 25 - this.modifiers.list(), 26 - ts.factory.createVariableDeclarationList( 27 - [ 28 - ts.factory.createVariableDeclaration( 29 - ts.factory.createIdentifier(this.name), 30 - undefined, 31 - undefined, 32 - this.$node(this.initializer), 33 - ), 34 - ], 35 - ts.NodeFlags.Const, 36 - ), 37 - ); 38 - } 39 - } 40 - 41 - export interface ConstTsDsl 42 - extends DefaultMixin, 43 - DescribeMixin, 44 - ExportMixin, 45 - ValueMixin {} 46 - mixin( 47 - ConstTsDsl, 48 - DefaultMixin, 49 - [DescribeMixin, { overrideRender: true }], 50 - ExportMixin, 51 - ValueMixin, 52 - );
+1 -1
packages/openapi-ts/src/ts-dsl/describe.ts
··· 17 17 this.add(...lines); 18 18 } 19 19 } 20 - if (fn) fn(this); 20 + fn?.(this); 21 21 } 22 22 23 23 add(...lines: ReadonlyArray<string>): this {
+1 -1
packages/openapi-ts/src/ts-dsl/field.ts
··· 24 24 constructor(name: string, fn?: (f: FieldTsDsl) => void) { 25 25 super(); 26 26 this.name = name; 27 - if (fn) fn(this); 27 + fn?.(this); 28 28 } 29 29 30 30 /** Sets the property's type. */
+162
packages/openapi-ts/src/ts-dsl/func.ts
··· 1 + /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ 2 + import ts from 'typescript'; 3 + 4 + import type { MaybeTsDsl } from './base'; 5 + import { TsDsl } from './base'; 6 + import { mixin } from './mixins/apply'; 7 + import { DecoratorMixin } from './mixins/decorator'; 8 + import { DescribeMixin } from './mixins/describe'; 9 + import { GenericsMixin } from './mixins/generics'; 10 + import { 11 + AbstractMixin, 12 + AsyncMixin, 13 + createModifierAccessor, 14 + PrivateMixin, 15 + ProtectedMixin, 16 + PublicMixin, 17 + StaticMixin, 18 + } from './mixins/modifiers'; 19 + import { OptionalMixin } from './mixins/optional'; 20 + import { ParamMixin } from './mixins/param'; 21 + import { createTypeAccessor } from './mixins/type'; 22 + 23 + type FuncMode = 'arrow' | 'decl' | 'expr'; 24 + 25 + class ImplFuncTsDsl<M extends FuncMode = 'arrow'> extends TsDsl< 26 + M extends 'decl' 27 + ? ts.FunctionDeclaration 28 + : M extends 'expr' 29 + ? ts.FunctionExpression 30 + : ts.ArrowFunction 31 + > { 32 + private body: Array<MaybeTsDsl<ts.Statement | ts.Expression>> = []; 33 + private mode: FuncMode; 34 + private modifiers = createModifierAccessor(this); 35 + private name?: string; 36 + private _returns = createTypeAccessor(this); 37 + 38 + constructor(); 39 + constructor(fn: (f: ImplFuncTsDsl<'arrow'>) => void); 40 + constructor(name: string); 41 + constructor(name: string, fn: (f: ImplFuncTsDsl<'decl'>) => void); 42 + constructor( 43 + nameOrFn?: string | ((f: ImplFuncTsDsl<'arrow'>) => void), 44 + fn?: (f: ImplFuncTsDsl<'decl'>) => void, 45 + ) { 46 + super(); 47 + if (typeof nameOrFn === 'string') { 48 + this.name = nameOrFn; 49 + this.mode = 'decl'; 50 + fn?.(this as unknown as FuncTsDsl<'decl'>); 51 + } else { 52 + this.mode = 'arrow'; 53 + nameOrFn?.(this as unknown as FuncTsDsl<'arrow'>); 54 + } 55 + } 56 + 57 + arrow(): FuncTsDsl<'arrow'> { 58 + this.mode = 'arrow'; 59 + return this as unknown as FuncTsDsl<'arrow'>; 60 + } 61 + 62 + decl(): FuncTsDsl<'decl'> { 63 + this.mode = 'decl'; 64 + return this as unknown as FuncTsDsl<'decl'>; 65 + } 66 + 67 + expr(): FuncTsDsl<'expr'> { 68 + this.mode = 'expr'; 69 + return this as unknown as FuncTsDsl<'expr'>; 70 + } 71 + 72 + do(...items: ReadonlyArray<MaybeTsDsl<ts.Statement | ts.Expression>>): this { 73 + this.body.push(...items); 74 + return this; 75 + } 76 + 77 + /** Sets the return type. */ 78 + returns = this._returns.method; 79 + 80 + $render(): M extends 'decl' 81 + ? ts.FunctionDeclaration 82 + : M extends 'expr' 83 + ? ts.FunctionExpression 84 + : ts.ArrowFunction { 85 + const builtParams = this.$node(this._params ?? []); 86 + const builtBody = this.$stmt(this.body); 87 + 88 + if (this.mode === 'decl') { 89 + if (!this.name) throw new Error('Function declaration requires a name'); 90 + return ts.factory.createFunctionDeclaration( 91 + [...(this.decorators ?? []), ...this.modifiers.list()], 92 + undefined, 93 + this.name, 94 + this.$generics(), 95 + builtParams, 96 + this._returns.$render(), 97 + ts.factory.createBlock(builtBody, true), 98 + ) as any; 99 + } 100 + 101 + if (this.mode === 'expr') { 102 + return ts.factory.createFunctionExpression( 103 + [...this.modifiers.list()], 104 + undefined, 105 + this.name ? ts.factory.createIdentifier(this.name) : undefined, 106 + this.$generics(), 107 + builtParams, 108 + this._returns.$render(), 109 + ts.factory.createBlock(builtBody, true), 110 + ) as any; 111 + } 112 + 113 + const exprBody = 114 + builtBody.length === 1 && ts.isReturnStatement(builtBody[0]!) 115 + ? (builtBody[0].expression ?? ts.factory.createBlock(builtBody, true)) 116 + : ts.factory.createBlock(builtBody, true); 117 + 118 + return ts.factory.createArrowFunction( 119 + [...this.modifiers.list()], 120 + this.$generics(), 121 + builtParams, 122 + this._returns.$render(), 123 + undefined, 124 + exprBody, 125 + ) as any; 126 + } 127 + } 128 + 129 + interface ImplFuncTsDsl 130 + extends AbstractMixin, 131 + AsyncMixin, 132 + DecoratorMixin, 133 + DescribeMixin, 134 + GenericsMixin, 135 + OptionalMixin, 136 + ParamMixin, 137 + PrivateMixin, 138 + ProtectedMixin, 139 + PublicMixin, 140 + StaticMixin {} 141 + mixin( 142 + ImplFuncTsDsl, 143 + AbstractMixin, 144 + AsyncMixin, 145 + DecoratorMixin, 146 + [DescribeMixin, { overrideRender: true }], 147 + GenericsMixin, 148 + OptionalMixin, 149 + ParamMixin, 150 + PrivateMixin, 151 + ProtectedMixin, 152 + PublicMixin, 153 + StaticMixin, 154 + ); 155 + 156 + export const FuncTsDsl = ImplFuncTsDsl as { 157 + new (): FuncTsDsl<'arrow'>; 158 + new (fn: (f: FuncTsDsl<'arrow'>) => void): FuncTsDsl<'arrow'>; 159 + new (name: string): FuncTsDsl<'decl'>; 160 + new (name: string, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>; 161 + } & typeof ImplFuncTsDsl; 162 + export type FuncTsDsl<M extends FuncMode = 'arrow'> = ImplFuncTsDsl<M>;
+1 -1
packages/openapi-ts/src/ts-dsl/getter.ts
··· 25 25 constructor(name: string, fn?: (g: GetterTsDsl) => void) { 26 26 super(); 27 27 this.name = name; 28 - if (fn) fn(this); 28 + fn?.(this); 29 29 } 30 30 31 31 /** Adds one or more expressions to the getter body. */
+23 -4
packages/openapi-ts/src/ts-dsl/index.ts
··· 1 1 import { AttrTsDsl } from './attr'; 2 + import { AwaitTsDsl } from './await'; 2 3 import { BinaryTsDsl } from './binary'; 3 4 import { CallTsDsl } from './call'; 4 5 import { ClassTsDsl } from './class'; 5 - import { ConstTsDsl } from './const'; 6 6 import { DescribeTsDsl } from './describe'; 7 7 import { ExprTsDsl } from './expr'; 8 8 import { FieldTsDsl } from './field'; 9 + import { FuncTsDsl } from './func'; 9 10 import { GetterTsDsl } from './getter'; 10 11 import { IfTsDsl } from './if'; 11 12 import { InitTsDsl } from './init'; ··· 21 22 import { TemplateTsDsl } from './template'; 22 23 import { ThrowTsDsl } from './throw'; 23 24 import { TypeTsDsl } from './type'; 25 + import { VarTsDsl } from './var'; 24 26 25 27 const base = { 26 28 attr: (...args: ConstructorParameters<typeof AttrTsDsl>) => 27 29 new AttrTsDsl(...args), 30 + await: (...args: ConstructorParameters<typeof AwaitTsDsl>) => 31 + new AwaitTsDsl(...args), 28 32 binary: (...args: ConstructorParameters<typeof BinaryTsDsl>) => 29 33 new BinaryTsDsl(...args), 30 34 call: (...args: ConstructorParameters<typeof CallTsDsl>) => 31 35 new CallTsDsl(...args), 32 36 class: (...args: ConstructorParameters<typeof ClassTsDsl>) => 33 37 new ClassTsDsl(...args), 34 - const: (...args: ConstructorParameters<typeof ConstTsDsl>) => 35 - new ConstTsDsl(...args), 38 + const: (...args: ConstructorParameters<typeof VarTsDsl>) => 39 + new VarTsDsl(...args).const(), 36 40 describe: (...args: ConstructorParameters<typeof DescribeTsDsl>) => 37 41 new DescribeTsDsl(...args), 38 42 expr: (...args: ConstructorParameters<typeof ExprTsDsl>) => 39 43 new ExprTsDsl(...args), 40 44 field: (...args: ConstructorParameters<typeof FieldTsDsl>) => 41 45 new FieldTsDsl(...args), 46 + func: ((nameOrFn?: any, fn?: any) => { 47 + if (nameOrFn === undefined) return new FuncTsDsl(); 48 + if (typeof nameOrFn !== 'string') return new FuncTsDsl(nameOrFn); 49 + if (fn === undefined) return new FuncTsDsl(nameOrFn); 50 + return new FuncTsDsl(nameOrFn, fn); 51 + }) as { 52 + (): FuncTsDsl<'arrow'>; 53 + (fn: (f: FuncTsDsl<'arrow'>) => void): FuncTsDsl<'arrow'>; 54 + (name: string): FuncTsDsl<'decl'>; 55 + (name: string, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>; 56 + }, 42 57 getter: (...args: ConstructorParameters<typeof GetterTsDsl>) => 43 58 new GetterTsDsl(...args), 44 59 if: (...args: ConstructorParameters<typeof IfTsDsl>) => new IfTsDsl(...args), 45 60 init: (...args: ConstructorParameters<typeof InitTsDsl>) => 46 61 new InitTsDsl(...args), 62 + let: (...args: ConstructorParameters<typeof VarTsDsl>) => 63 + new VarTsDsl(...args).let(), 47 64 literal: (...args: ConstructorParameters<typeof LiteralTsDsl>) => 48 65 new LiteralTsDsl(...args), 49 66 method: (...args: ConstructorParameters<typeof MethodTsDsl>) => ··· 67 84 throw: (...args: ConstructorParameters<typeof ThrowTsDsl>) => 68 85 new ThrowTsDsl(...args), 69 86 type: TypeTsDsl, 87 + var: (...args: ConstructorParameters<typeof VarTsDsl>) => 88 + new VarTsDsl(...args), 70 89 }; 71 90 72 91 export const $ = Object.assign( ··· 74 93 base, 75 94 ); 76 95 77 - export type { TsDsl } from './base'; 96 + export { TsDsl } from './base';
+1 -1
packages/openapi-ts/src/ts-dsl/init.ts
··· 20 20 21 21 constructor(fn?: (i: InitTsDsl) => void) { 22 22 super(); 23 - if (fn) fn(this); 23 + fn?.(this); 24 24 } 25 25 26 26 /** Adds one or more statements or expressions to the constructor body. */
+1 -1
packages/openapi-ts/src/ts-dsl/method.ts
··· 29 29 constructor(name: string, fn?: (m: MethodTsDsl) => void) { 30 30 super(); 31 31 this.name = name; 32 - if (fn) fn(this); 32 + fn?.(this); 33 33 } 34 34 35 35 /** Sets the return type. */
+9 -4
packages/openapi-ts/src/ts-dsl/object.ts
··· 10 10 11 11 constructor(fn?: (o: ObjectTsDsl) => void) { 12 12 super(); 13 - if (fn) fn(this); 13 + fn?.(this); 14 14 } 15 15 16 16 /** Sets automatic line output with optional threshold (default: 3). */ ··· 31 31 return this; 32 32 } 33 33 34 - /** Adds a property assignment using a callback builder. */ 34 + /** Adds a property assignment. */ 35 35 prop( 36 36 name: string, 37 - fn: (p: (expr: ExprInput) => ExprTsDsl) => TsDsl<ts.Expression>, 37 + fn: 38 + | TsDsl<ts.Expression> 39 + | ((p: (expr: ExprInput) => ExprTsDsl) => TsDsl<ts.Expression>), 38 40 ): this { 39 - const result = fn((expr: ExprInput) => new ExprTsDsl(expr)); 41 + const result = 42 + typeof fn === 'function' 43 + ? fn((expr: ExprInput) => new ExprTsDsl(expr)) 44 + : fn; 40 45 this.props.push({ expr: result, name }); 41 46 return this; 42 47 }
+1 -1
packages/openapi-ts/src/ts-dsl/param.ts
··· 15 15 constructor(name: string, fn?: (p: ParamTsDsl) => void) { 16 16 super(); 17 17 this.name = name; 18 - if (fn) fn(this); 18 + fn?.(this); 19 19 } 20 20 21 21 /** Sets the parameter's type. */
+1 -1
packages/openapi-ts/src/ts-dsl/setter.ts
··· 25 25 constructor(name: string, fn?: (s: SetterTsDsl) => void) { 26 26 super(); 27 27 this.name = name; 28 - if (fn) fn(this); 28 + fn?.(this); 29 29 } 30 30 31 31 /** Adds one or more expressions/statements to the setter body. */
+2 -2
packages/openapi-ts/src/ts-dsl/type.ts
··· 39 39 fn?: (base: TypeReferenceTsDsl) => void, 40 40 ) { 41 41 super(name); 42 - if (fn) fn(this); 42 + fn?.(this); 43 43 } 44 44 45 45 /** Starts an object type literal (e.g. `{ foo: string }`). */ ··· 70 70 fn?: (base: TypeParamTsDsl) => void, 71 71 ) { 72 72 super(name); 73 - if (fn) fn(this); 73 + fn?.(this); 74 74 } 75 75 76 76 $render(): ts.TypeParameterDeclaration {
+154
packages/openapi-ts/src/ts-dsl/var.ts
··· 1 + /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ 2 + import ts from 'typescript'; 3 + 4 + import { TsDsl } from './base'; 5 + import { mixin } from './mixins/apply'; 6 + import { DescribeMixin } from './mixins/describe'; 7 + import { 8 + createModifierAccessor, 9 + DefaultMixin, 10 + ExportMixin, 11 + } from './mixins/modifiers'; 12 + import { ValueMixin } from './mixins/value'; 13 + 14 + export class VarTsDsl extends TsDsl<ts.VariableStatement> { 15 + private kind: ts.NodeFlags = ts.NodeFlags.None; 16 + private modifiers = createModifierAccessor(this); 17 + private name?: string; 18 + private pattern?: ReadonlyArray<string> | Record<string, string>; 19 + private _rest?: string; 20 + 21 + constructor(name?: string) { 22 + super(); 23 + this.name = name; 24 + } 25 + 26 + const(): this { 27 + this.kind = ts.NodeFlags.Const; 28 + return this; 29 + } 30 + 31 + let(): this { 32 + this.kind = ts.NodeFlags.Let; 33 + return this; 34 + } 35 + 36 + object( 37 + ...props: ReadonlyArray< 38 + string | ReadonlyArray<string> | Record<string, string> 39 + > 40 + ): this { 41 + const entries: Record<string, string> = {}; 42 + for (const p of props) { 43 + if (typeof p === 'string') { 44 + entries[p] = p; // shorthand 45 + } else if (p instanceof Array) { 46 + for (const name of p) entries[name] = name; 47 + } else { 48 + Object.assign(entries, p); 49 + } 50 + } 51 + this.pattern = entries; 52 + return this; 53 + } 54 + 55 + rest(name: string): this { 56 + this._rest = name; 57 + return this; 58 + } 59 + 60 + tuple(...props: ReadonlyArray<string> | [ReadonlyArray<string>]): this { 61 + const names = 62 + props[0] instanceof Array 63 + ? [...props[0]] 64 + : (props as ReadonlyArray<string>); 65 + this.pattern = names; 66 + return this; 67 + } 68 + 69 + var(): this { 70 + this.kind = ts.NodeFlags.None; 71 + return this; 72 + } 73 + 74 + $render(): ts.VariableStatement { 75 + let _pattern: ts.BindingPattern | undefined; 76 + 77 + if (this.pattern) { 78 + if (this.pattern instanceof Array) { 79 + const elements = this.pattern.map((p) => 80 + ts.factory.createBindingElement( 81 + undefined, 82 + undefined, 83 + ts.factory.createIdentifier(p), 84 + ), 85 + ); 86 + 87 + const restEl = this.createRest(); 88 + if (restEl) elements.push(restEl); 89 + 90 + _pattern = ts.factory.createArrayBindingPattern(elements); 91 + } else { 92 + const elements = Object.entries(this.pattern).map(([key, alias]) => 93 + key === alias 94 + ? ts.factory.createBindingElement( 95 + undefined, 96 + undefined, 97 + ts.factory.createIdentifier(key), 98 + ) 99 + : ts.factory.createBindingElement( 100 + undefined, 101 + ts.factory.createIdentifier(key), 102 + ts.factory.createIdentifier(alias), 103 + ), 104 + ); 105 + 106 + const restEl = this.createRest(); 107 + if (restEl) elements.push(restEl); 108 + 109 + _pattern = ts.factory.createObjectBindingPattern(elements); 110 + } 111 + } 112 + const name = _pattern ?? this.name; 113 + if (!name) { 114 + throw new Error('Var must have either a name or a destructuring pattern'); 115 + } 116 + return ts.factory.createVariableStatement( 117 + this.modifiers.list(), 118 + ts.factory.createVariableDeclarationList( 119 + [ 120 + ts.factory.createVariableDeclaration( 121 + name, 122 + undefined, 123 + undefined, 124 + this.$node(this.initializer), 125 + ), 126 + ], 127 + this.kind, 128 + ), 129 + ); 130 + } 131 + 132 + private createRest(): ts.BindingElement | undefined { 133 + return this._rest 134 + ? ts.factory.createBindingElement( 135 + ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), 136 + undefined, 137 + ts.factory.createIdentifier(this._rest), 138 + ) 139 + : undefined; 140 + } 141 + } 142 + 143 + export interface VarTsDsl 144 + extends DefaultMixin, 145 + DescribeMixin, 146 + ExportMixin, 147 + ValueMixin {} 148 + mixin( 149 + VarTsDsl, 150 + DefaultMixin, 151 + [DescribeMixin, { overrideRender: true }], 152 + ExportMixin, 153 + ValueMixin, 154 + );