@kubb/plugin-ts 5.0.0-alpha.4 → 5.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,6 +65,8 @@ type Options = {
65
65
  /**
66
66
  * Set a suffix for the generated enums.
67
67
  * @default 'enum'
68
+ * @deprecated Set `enumSuffix` on the adapter (`adapterOas({ enumSuffix })`) instead.
69
+ * In v5, the adapter owns this decision at parse time; the plugin option is ignored.
68
70
  */
69
71
  enumSuffix?: string;
70
72
  /**
@@ -72,6 +74,8 @@ type Options = {
72
74
  * - 'string' represents dates as string values.
73
75
  * - 'date' represents dates as JavaScript Date objects.
74
76
  * @default 'string'
77
+ * @deprecated Set `dateType` on the adapter (`adapterOas({ dateType })`) instead.
78
+ * In v5, the adapter owns this decision at parse time; the plugin option is ignored.
75
79
  */
76
80
  dateType?: 'string' | 'date';
77
81
  /**
@@ -80,6 +84,8 @@ type Options = {
80
84
  * - 'bigint' uses the TypeScript `bigint` type (accurate for values exceeding Number.MAX_SAFE_INTEGER).
81
85
  * @note in v5 of Kubb 'bigint' will become the default to better align with OpenAPI's int64 specification.
82
86
  * @default 'number'
87
+ * @deprecated Set `integerType` on the adapter (`adapterOas({ integerType })`) instead.
88
+ * In v5, the adapter owns this decision at parse time; the plugin option is ignored.
83
89
  */
84
90
  integerType?: 'number' | 'bigint';
85
91
  /**
@@ -88,6 +94,8 @@ type Options = {
88
94
  * - 'unknown' requires type narrowing before use.
89
95
  * - 'void' represents no value.
90
96
  * @default 'any'
97
+ * @deprecated Set `unknownType` on the adapter (`adapterOas({ unknownType })`) instead.
98
+ * In v5, the adapter owns this decision at parse time; the plugin option is ignored.
91
99
  */
92
100
  unknownType?: 'any' | 'unknown' | 'void';
93
101
  /**
@@ -96,6 +104,8 @@ type Options = {
96
104
  * - 'unknown' requires type narrowing before use.
97
105
  * - 'void' represents no value.
98
106
  * @default `unknownType`
107
+ * @deprecated Set `emptySchemaType` on the adapter (`adapterOas({ emptySchemaType })`) instead.
108
+ * In v5, the adapter owns this decision at parse time; the plugin option is ignored.
99
109
  */
100
110
  emptySchemaType?: 'any' | 'unknown' | 'void';
101
111
  /**
@@ -167,4 +177,4 @@ type ResolvedOptions = {
167
177
  type PluginTs = PluginFactoryOptions<'plugin-ts', Options, ResolvedOptions, never, ResolvePathOptions>;
168
178
  //#endregion
169
179
  export { PluginTs as n, Options as t };
170
- //# sourceMappingURL=types-aotMcdUB.d.ts.map
180
+ //# sourceMappingURL=types-eWhVEVgF.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-ts",
3
- "version": "5.0.0-alpha.4",
3
+ "version": "5.0.0-alpha.5",
4
4
  "description": "TypeScript code generation plugin for Kubb, transforming OpenAPI schemas into TypeScript interfaces, types, and utility functions.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -67,17 +67,17 @@
67
67
  }
68
68
  ],
69
69
  "dependencies": {
70
- "@kubb/fabric-core": "0.13.3",
71
- "@kubb/react-fabric": "0.13.3",
70
+ "@kubb/fabric-core": "0.14.0",
71
+ "@kubb/react-fabric": "0.14.0",
72
72
  "remeda": "^2.33.6",
73
73
  "typescript": "5.9.3",
74
- "@kubb/ast": "5.0.0-alpha.4",
75
- "@kubb/core": "5.0.0-alpha.4",
76
- "@kubb/oas": "5.0.0-alpha.4",
77
- "@kubb/plugin-oas": "5.0.0-alpha.4"
74
+ "@kubb/ast": "5.0.0-alpha.5",
75
+ "@kubb/core": "5.0.0-alpha.5",
76
+ "@kubb/plugin-oas": "5.0.0-alpha.5",
77
+ "@kubb/oas": "5.0.0-alpha.5"
78
78
  },
79
79
  "peerDependencies": {
80
- "@kubb/react-fabric": "0.13.3"
80
+ "@kubb/react-fabric": "0.14.0"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22"
@@ -18,11 +18,24 @@ type Props = {
18
18
  enumType: PluginTs['resolvedOptions']['enumType']
19
19
  enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
20
20
  syntaxType: PluginTs['resolvedOptions']['syntaxType']
21
+ mapper?: PluginTs['resolvedOptions']['mapper']
21
22
  description?: string
22
23
  keysToOmit?: string[]
23
24
  }
24
25
 
25
- export function Type({ name, typedName, node, keysToOmit, optionalType, arrayType, syntaxType, enumType, enumKeyCasing, ...rest }: Props): FabricReactNode {
26
+ export function Type({
27
+ name,
28
+ typedName,
29
+ node,
30
+ keysToOmit,
31
+ optionalType,
32
+ arrayType,
33
+ syntaxType,
34
+ enumType,
35
+ enumKeyCasing,
36
+ mapper,
37
+ ...rest
38
+ }: Props): FabricReactNode {
26
39
  const typeNodes: ts.Node[] = []
27
40
 
28
41
  const description = rest.description || node?.description
@@ -32,7 +45,7 @@ export function Type({ name, typedName, node, keysToOmit, optionalType, arrayTyp
32
45
  },
33
46
  })
34
47
 
35
- const printer = printerTs({ optionalType, arrayType, enumType })
48
+ const printer = printerTs({ optionalType, arrayType, enumType, mapper })
36
49
  let type = printer.print(node)
37
50
 
38
51
  if (!type) {
package/src/factory.ts CHANGED
@@ -662,43 +662,39 @@ export const keywordTypeNodes = {
662
662
  * Converts a path like '/pet/{petId}/uploadImage' to a template literal type
663
663
  * like `/pet/${string}/uploadImage`
664
664
  */
665
+ /**
666
+ * Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
667
+ * (e.g. `/pets/:petId`) to a TypeScript template literal type
668
+ * like `` `/pets/${string}` ``.
669
+ */
665
670
  export function createUrlTemplateType(path: string): ts.TypeNode {
666
- // If no parameters, return literal string type
667
- if (!path.includes('{')) {
668
- return factory.createLiteralTypeNode(factory.createStringLiteral(path))
669
- }
671
+ // normalized Express `:param` OAS `{param}` so a single regex handles both.
672
+ const normalized = path.replace(/:([^/]+)/g, '{$1}')
670
673
 
671
- // Split path by parameter placeholders, e.g. '/pet/{petId}/upload' -> ['/pet/', 'petId', '/upload']
672
- const segments = path.split(/(\{[^}]+\})/)
674
+ if (!normalized.includes('{')) {
675
+ return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
676
+ }
673
677
 
674
- // Separate static parts from parameter placeholders
678
+ const segments = normalized.split(/(\{[^}]+\})/)
675
679
  const parts: string[] = []
676
680
  const parameterIndices: number[] = []
677
681
 
678
682
  segments.forEach((segment) => {
679
683
  if (segment.startsWith('{') && segment.endsWith('}')) {
680
- // This is a parameter placeholder
681
684
  parameterIndices.push(parts.length)
682
- parts.push(segment) // Will be replaced with ${string}
685
+ parts.push(segment)
683
686
  } else if (segment) {
684
- // This is a static part
685
687
  parts.push(segment)
686
688
  }
687
689
  })
688
690
 
689
- // Build template literal type
690
- // Template literal structure: head + templateSpans[]
691
- // For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
692
-
693
691
  const head = ts.factory.createTemplateHead(parts[0] || '')
694
692
  const templateSpans: ts.TemplateLiteralTypeSpan[] = []
695
693
 
696
694
  parameterIndices.forEach((paramIndex, i) => {
697
695
  const isLast = i === parameterIndices.length - 1
698
696
  const nextPart = parts[paramIndex + 1] || ''
699
-
700
697
  const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
701
-
702
698
  templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
703
699
  })
704
700
 
@@ -1,45 +1,46 @@
1
+ import { applyParamsCasing } from '@kubb/ast'
1
2
  import type { SchemaNode } from '@kubb/ast/types'
2
3
  import { defineGenerator } from '@kubb/core'
3
4
  import { useKubb } from '@kubb/core/hooks'
4
5
  import { File } from '@kubb/react-fabric'
5
6
  import { Type } from '../../components/v2/Type.tsx'
6
7
  import type { PluginTs } from '../../types'
8
+ import { buildDataSchemaNode, buildResponsesSchemaNode, buildResponseUnionSchemaNode } from './utils.ts'
7
9
 
8
10
  export const typeGenerator = defineGenerator<PluginTs>({
9
11
  name: 'typescript',
10
12
  type: 'react',
11
13
  Operation({ node, adapter, options }) {
12
- const { enumType, enumKeyCasing, optionalType, arrayType, syntaxType } = options
14
+ const { enumType, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, mapper } = options
15
+ const { mode, getFile, resolveName } = useKubb<PluginTs>()
13
16
 
14
- const { plugin, mode, getFile, resolveName } = useKubb<PluginTs>()
17
+ const file = getFile({ name: node.operationId, extname: '.ts', mode })
18
+ const params = applyParamsCasing(node.parameters, paramsCasing)
15
19
 
16
- const file = getFile({
17
- name: node.operationId,
18
- pluginName: plugin.name,
19
- extname: '.ts',
20
- mode,
21
- })
20
+ function renderSchemaType({
21
+ node: schemaNode,
22
+ name,
23
+ typedName,
24
+ description,
25
+ }: {
26
+ node: SchemaNode | null
27
+ name: string
28
+ typedName: string
29
+ description?: string
30
+ }) {
31
+ if (!schemaNode) {
32
+ return null
33
+ }
22
34
 
23
- function renderSchemaType({ node: schemaNode, name, typedName, description }: { node: SchemaNode; name: string; typedName: string; description?: string }) {
24
35
  const imports = adapter.getImports(schemaNode, (schemaName) => ({
25
- name: resolveName({
26
- name: schemaName,
27
- pluginName: plugin.name,
28
- type: 'type',
29
- }),
30
- path: getFile({
31
- name: schemaName,
32
- pluginName: plugin.name,
33
- extname: '.ts',
34
- mode,
35
- }).path,
36
+ name: resolveName({ name: schemaName, type: 'type' }),
37
+ path: getFile({ name: schemaName, extname: '.ts', mode }).path,
36
38
  }))
37
39
 
38
40
  return (
39
41
  <>
40
42
  {mode === 'split' &&
41
43
  imports.map((imp) => <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} isTypeOnly />)}
42
-
43
44
  <Type
44
45
  name={name}
45
46
  typedName={typedName}
@@ -50,127 +51,93 @@ export const typeGenerator = defineGenerator<PluginTs>({
50
51
  optionalType={optionalType}
51
52
  arrayType={arrayType}
52
53
  syntaxType={syntaxType}
54
+ mapper={mapper}
53
55
  />
54
56
  </>
55
57
  )
56
58
  }
57
59
 
58
- // Parameter types each parameter rendered as its own type
59
- const paramTypes = node.parameters.map((param) => {
60
- const name = resolveName({
61
- name: `${node.operationId} ${param.name}`,
62
- pluginName: plugin.name,
63
- type: 'function',
64
- })
65
- const typedName = resolveName({
66
- name: `${node.operationId} ${param.name}`,
67
- pluginName: plugin.name,
68
- type: 'type',
69
- })
70
-
71
- return renderSchemaType({ node: param.schema, name, typedName })
72
- })
60
+ const paramTypes = params.map((param) =>
61
+ renderSchemaType({
62
+ node: param.schema,
63
+ name: resolveName({ name: `${node.operationId} ${param.name}`, type: 'function' }),
64
+ typedName: resolveName({ name: `${node.operationId} ${param.name}`, type: 'type' }),
65
+ }),
66
+ )
73
67
 
74
- // Response types
75
68
  const responseTypes = node.responses
76
69
  .filter((res) => res.schema)
77
- .map((res) => {
78
- const schemaNode = res.schema!
79
- const responseName = `${node.operationId} ${res.statusCode}`
80
- const resolvedName = resolveName({
81
- name: responseName,
82
- pluginName: plugin.name,
83
- type: 'function',
84
- })
85
- const typedName = resolveName({
86
- name: responseName,
87
- pluginName: plugin.name,
88
- type: 'type',
89
- })
90
-
91
- return renderSchemaType({ node: schemaNode, name: resolvedName, typedName, description: res.description })
92
- })
70
+ .map((res) =>
71
+ renderSchemaType({
72
+ node: res.schema!,
73
+ name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
74
+ typedName: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'type' }),
75
+ description: res.description,
76
+ }),
77
+ )
93
78
 
94
- // Request body type
95
79
  const requestType = node.requestBody
96
- ? (() => {
97
- const requestName = `${node.operationId} MutationRequest`
98
- const resolvedName = resolveName({
99
- name: requestName,
100
- pluginName: plugin.name,
101
- type: 'function',
102
- })
103
- const typedName = resolveName({
104
- name: requestName,
105
- pluginName: plugin.name,
106
- type: 'type',
107
- })
108
-
109
- return renderSchemaType({ node: node.requestBody, name: resolvedName, typedName, description: node.requestBody.description })
110
- })()
80
+ ? renderSchemaType({
81
+ node: node.requestBody,
82
+ name: resolveName({ name: `${node.operationId} MutationRequest`, type: 'function' }),
83
+ typedName: resolveName({ name: `${node.operationId} MutationRequest`, type: 'type' }),
84
+ description: node.requestBody.description,
85
+ })
111
86
  : null
112
87
 
88
+ const dataType = renderSchemaType({
89
+ node: buildDataSchemaNode({ node: { ...node, parameters: params }, resolveName }),
90
+ name: resolveName({ name: `${node.operationId} Data`, type: 'function' }),
91
+ typedName: resolveName({ name: `${node.operationId} Data`, type: 'type' }),
92
+ })
93
+
94
+ const responsesType = renderSchemaType({
95
+ node: buildResponsesSchemaNode({ node, resolveName }),
96
+ name: resolveName({ name: `${node.operationId} Responses`, type: 'function' }),
97
+ typedName: resolveName({ name: `${node.operationId} Responses`, type: 'type' }),
98
+ })
99
+
100
+ const responseType = renderSchemaType({
101
+ node: buildResponseUnionSchemaNode({ node, resolveName }),
102
+ name: resolveName({ name: `${node.operationId} Response`, type: 'function' }),
103
+ typedName: resolveName({ name: `${node.operationId} Response`, type: 'type' }),
104
+ })
105
+
113
106
  return (
114
107
  <File baseName={file.baseName} path={file.path} meta={file.meta}>
115
108
  {paramTypes}
116
109
  {responseTypes}
117
110
  {requestType}
111
+ {dataType}
112
+ {responsesType}
113
+ {responseType}
118
114
  </File>
119
115
  )
120
116
  },
121
117
  Schema({ node, adapter, options }) {
122
- const { enumType, enumKeyCasing, syntaxType, optionalType, arrayType } = options
123
- const { plugin, mode, resolveName, getFile } = useKubb<PluginTs>()
118
+ const { enumType, enumKeyCasing, syntaxType, optionalType, arrayType, mapper } = options
119
+ const { mode, resolveName, getFile } = useKubb<PluginTs>()
124
120
 
125
121
  if (!node.name) {
126
122
  return
127
123
  }
128
124
 
129
125
  const imports = adapter.getImports(node, (schemaName) => ({
130
- name: resolveName({
131
- name: schemaName,
132
- pluginName: plugin.name,
133
- type: 'type',
134
- }),
135
- path: getFile({
136
- name: schemaName,
137
- pluginName: plugin.name,
138
- extname: '.ts',
139
- mode,
140
- // options: {
141
- // group
142
- // },
143
- }).path,
126
+ name: resolveName({ name: schemaName, type: 'type' }),
127
+ path: getFile({ name: schemaName, extname: '.ts', mode }).path,
144
128
  }))
145
129
 
146
130
  const isEnumSchema = node.type === 'enum'
147
131
 
148
- let typedName = resolveName({
149
- name: node.name,
150
- pluginName: plugin.name,
151
- type: 'type',
152
- })
153
-
132
+ let typedName = resolveName({ name: node.name, type: 'type' })
154
133
  if (['asConst', 'asPascalConst'].includes(enumType) && isEnumSchema) {
155
- typedName = typedName += 'Key'
134
+ typedName += 'Key'
156
135
  }
157
136
 
158
137
  const type = {
159
- name: resolveName({
160
- name: node.name,
161
- pluginName: plugin.name,
162
- type: 'function',
163
- }),
138
+ name: resolveName({ name: node.name, type: 'function' }),
164
139
  typedName,
165
- file: getFile({
166
- name: node.name,
167
- pluginName: plugin.name,
168
- extname: '.ts',
169
- mode,
170
- // options: {
171
- // group
172
- // },
173
- }),
140
+ file: getFile({ name: node.name, extname: '.ts', mode }),
174
141
  } as const
175
142
 
176
143
  return (
@@ -179,7 +146,6 @@ export const typeGenerator = defineGenerator<PluginTs>({
179
146
  imports.map((imp) => (
180
147
  <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={type.file.path} path={imp.path} name={imp.name} isTypeOnly />
181
148
  ))}
182
-
183
149
  <Type
184
150
  name={type.name}
185
151
  typedName={type.typedName}
@@ -189,6 +155,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
189
155
  optionalType={optionalType}
190
156
  arrayType={arrayType}
191
157
  syntaxType={syntaxType}
158
+ mapper={mapper}
192
159
  />
193
160
  </File>
194
161
  )
@@ -0,0 +1,145 @@
1
+ import { createProperty, createSchema } from '@kubb/ast'
2
+ import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
3
+
4
+ type ResolveName = (opts: { name: string; type: 'type' | 'function' }) => string
5
+
6
+ type BuildParamsSchemaOptions = {
7
+ params: Array<ParameterNode>
8
+ operationId: string
9
+ resolveName: ResolveName
10
+ }
11
+
12
+ /**
13
+ * Builds an `ObjectSchemaNode` for a group of parameters (path/query/header).
14
+ * Each property is a `ref` schema pointing to the individually-resolved parameter type.
15
+ */
16
+ export function buildParamsSchema({ params, operationId, resolveName }: BuildParamsSchemaOptions): SchemaNode {
17
+ return createSchema({
18
+ type: 'object',
19
+ properties: params.map((param) =>
20
+ createProperty({
21
+ name: param.name,
22
+ schema: createSchema({
23
+ type: 'ref',
24
+ name: resolveName({ name: `${operationId} ${param.name}`, type: 'function' }),
25
+ optional: !param.required,
26
+ }),
27
+ }),
28
+ ),
29
+ })
30
+ }
31
+
32
+ type BuildOperationSchemaOptions = {
33
+ node: OperationNode
34
+ resolveName: ResolveName
35
+ }
36
+
37
+ /**
38
+ * Builds an `ObjectSchemaNode` representing the `<OperationId>Data` type:
39
+ * - `data` → request body ref (optional) or `never`
40
+ * - `pathParams` → inline object of path param refs, or `never`
41
+ * - `queryParams` → inline object of query param refs (optional), or `never`
42
+ * - `headerParams` → inline object of header param refs (optional), or `never`
43
+ * - `url` → Express-style template literal (plugin-ts extension, handled by printer)
44
+ */
45
+ export function buildDataSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode {
46
+ const pathParams = node.parameters.filter((p) => p.in === 'path')
47
+ const queryParams = node.parameters.filter((p) => p.in === 'query')
48
+ const headerParams = node.parameters.filter((p) => p.in === 'header')
49
+
50
+ return createSchema({
51
+ type: 'object',
52
+ properties: [
53
+ createProperty({
54
+ name: 'data',
55
+ schema: node.requestBody
56
+ ? createSchema({
57
+ type: 'ref',
58
+ name: resolveName({ name: `${node.operationId} MutationRequest`, type: 'function' }),
59
+ optional: true,
60
+ })
61
+ : createSchema({ type: 'never', optional: true }),
62
+ }),
63
+ createProperty({
64
+ name: 'pathParams',
65
+ schema:
66
+ pathParams.length > 0
67
+ ? buildParamsSchema({ params: pathParams, operationId: node.operationId, resolveName })
68
+ : createSchema({ type: 'never', optional: true }),
69
+ }),
70
+ createProperty({
71
+ name: 'queryParams',
72
+ schema:
73
+ queryParams.length > 0
74
+ ? createSchema({ ...buildParamsSchema({ params: queryParams, operationId: node.operationId, resolveName }), optional: true })
75
+ : createSchema({ type: 'never', optional: true }),
76
+ }),
77
+ createProperty({
78
+ name: 'headerParams',
79
+ schema:
80
+ headerParams.length > 0
81
+ ? createSchema({ ...buildParamsSchema({ params: headerParams, operationId: node.operationId, resolveName }), optional: true })
82
+ : createSchema({ type: 'never', optional: true }),
83
+ }),
84
+ createProperty({
85
+ name: 'url',
86
+ schema: createSchema({ type: 'url', path: node.path }),
87
+ }),
88
+ ],
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Builds an `ObjectSchemaNode` representing `<OperationId>Responses` — keyed by HTTP status code.
94
+ *
95
+ * Example output:
96
+ * ```ts
97
+ * export type PlaceOrderPatchResponses = { 200: PlaceOrderPatch200; 405: PlaceOrderPatch405 }
98
+ * ```
99
+ */
100
+ export function buildResponsesSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode | null {
101
+ const responsesWithSchema = node.responses.filter((res) => res.schema)
102
+
103
+ if (responsesWithSchema.length === 0) {
104
+ return null
105
+ }
106
+
107
+ return createSchema({
108
+ type: 'object',
109
+ properties: responsesWithSchema.map((res) =>
110
+ createProperty({
111
+ name: String(res.statusCode),
112
+ schema: createSchema({
113
+ type: 'ref',
114
+ name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
115
+ }),
116
+ }),
117
+ ),
118
+ })
119
+ }
120
+
121
+ /**
122
+ * Builds a `UnionSchemaNode` representing `<OperationId>Response` — all response types in union format.
123
+ *
124
+ * Example output:
125
+ * ```ts
126
+ * export type PlaceOrderPatchResponse = PlaceOrderPatch200 | PlaceOrderPatch405
127
+ * ```
128
+ */
129
+ export function buildResponseUnionSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode | null {
130
+ const responsesWithSchema = node.responses.filter((res) => res.schema)
131
+
132
+ if (responsesWithSchema.length === 0) {
133
+ return null
134
+ }
135
+
136
+ return createSchema({
137
+ type: 'union',
138
+ members: responsesWithSchema.map((res) =>
139
+ createSchema({
140
+ type: 'ref',
141
+ name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
142
+ }),
143
+ ),
144
+ })
145
+ }
package/src/plugin.ts CHANGED
@@ -167,6 +167,17 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
167
167
  { depth: 'shallow' },
168
168
  )
169
169
 
170
+ const barrelFiles = await getBarrelFiles(this.fabric.files, {
171
+ type: output.barrelType ?? 'named',
172
+ root,
173
+ output,
174
+ meta: {
175
+ pluginName: this.plugin.name,
176
+ },
177
+ })
178
+
179
+ await this.upsertFile(...barrelFiles)
180
+
170
181
  return
171
182
  }
172
183
 
package/src/printer.ts CHANGED
@@ -19,6 +19,10 @@ type TsOptions = {
19
19
  * @default `'inlineLiteral'`
20
20
  */
21
21
  enumType: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
22
+ /**
23
+ * Custom property signatures that override specific object properties by name.
24
+ */
25
+ mapper?: Record<string, ts.PropertySignature>
22
26
  }
23
27
 
24
28
  type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode>
@@ -143,13 +147,19 @@ export const printerTs = definePrinter<TsPrinter>((options) => ({
143
147
  any: () => factory.keywordTypeNodes.any,
144
148
  unknown: () => factory.keywordTypeNodes.unknown,
145
149
  void: () => factory.keywordTypeNodes.void,
150
+ never: () => factory.keywordTypeNodes.never,
146
151
  boolean: () => factory.keywordTypeNodes.boolean,
147
152
  null: () => factory.keywordTypeNodes.null,
148
153
  blob: () => factory.createTypeReferenceNode('Blob', []),
149
154
  string: () => factory.keywordTypeNodes.string,
150
155
  uuid: () => factory.keywordTypeNodes.string,
151
156
  email: () => factory.keywordTypeNodes.string,
152
- url: () => factory.keywordTypeNodes.string,
157
+ url: (node) => {
158
+ if (node.path) {
159
+ return factory.createUrlTemplateType(node.path)
160
+ }
161
+ return factory.keywordTypeNodes.string
162
+ },
153
163
  datetime: () => factory.keywordTypeNodes.string,
154
164
  number: () => factory.keywordTypeNodes.number,
155
165
  integer: () => factory.keywordTypeNodes.number,
@@ -219,6 +229,10 @@ export const printerTs = definePrinter<TsPrinter>((options) => ({
219
229
  const { print } = this
220
230
 
221
231
  const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
232
+ if (this.options.mapper && Object.hasOwn(this.options.mapper, prop.name)) {
233
+ return this.options.mapper[prop.name]!
234
+ }
235
+
222
236
  const baseType = (print(prop.schema) ?? factory.keywordTypeNodes.unknown) as ts.TypeNode
223
237
  const type = buildPropertyType(prop.schema, baseType, this.options.optionalType)
224
238