@kubb/core 5.0.0-alpha.20 → 5.0.0-alpha.21

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.
package/src/types.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { AsyncEventEmitter, PossiblePromise } from '@internals/utils'
2
2
  import type { Node, RootNode, SchemaNode, Visitor } from '@kubb/ast/types'
3
3
  import type { Fabric as FabricType, KubbFile } from '@kubb/fabric-core/types'
4
+ import type { HttpMethod } from '@kubb/oas'
4
5
  import type { DEFAULT_STUDIO_URL, logLevel } from './constants.ts'
5
6
  import type { Storage } from './createStorage.ts'
7
+ import type { Generator } from './defineGenerator.ts'
6
8
  import type { KubbEvents } from './Kubb.ts'
7
9
  import type { PluginDriver } from './PluginDriver.ts'
8
10
 
@@ -110,7 +112,8 @@ export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptio
110
112
  * The raw source document produced after the first `parse()` call.
111
113
  * `undefined` before parsing; typed by the adapter's `TDocument` generic.
112
114
  */
113
- document?: TOptions['document']
115
+ document: TOptions['document'] | null
116
+ rootNode: RootNode | null
114
117
  /**
115
118
  * Convert the raw source into a universal `RootNode`.
116
119
  */
@@ -229,7 +232,7 @@ export type Config<TInput = Input> = {
229
232
  * Configures how `index.ts` files are created, including disabling barrel file generation. Each plugin has its own `barrelType` option; this setting controls the root barrel file (e.g., `src/gen/index.ts`).
230
233
  * @default 'named'
231
234
  */
232
- barrelType?: Exclude<BarrelType, 'propagate'> | false
235
+ barrelType?: 'all' | 'named' | false
233
236
  /**
234
237
  * Adds a default banner to the start of every generated file indicating it was generated by Kubb.
235
238
  * - 'simple' adds banner with link to Kubb.
@@ -297,25 +300,53 @@ export type ResolveOptionsContext<TOptions> = {
297
300
  /**
298
301
  * Base constraint for all plugin resolver objects.
299
302
  *
300
- * `default` and `resolveOptions` are injected automatically by `defineResolver` — plugin
301
- * authors may override them but never need to implement them from scratch.
302
- * Concrete plugin resolver types extend this with their own helper methods.
303
+ * `default`, `resolveOptions`, `resolvePath`, and `resolveFile` are injected automatically
304
+ * by `defineResolver` — plugin authors may override them but never need to implement them
305
+ * from scratch.
306
+ *
307
+ * @example
308
+ * ```ts
309
+ * type MyResolver = Resolver & {
310
+ * resolveName(node: SchemaNode): string
311
+ * resolveTypedName(node: SchemaNode): string
312
+ * }
313
+ * ```
303
314
  */
304
315
  export type Resolver = {
305
316
  name: string
317
+ pluginName: Plugin['name']
306
318
  default(name: ResolveNameParams['name'], type?: ResolveNameParams['type']): string
307
319
  resolveOptions<TOptions>(node: Node, context: ResolveOptionsContext<TOptions>): TOptions | null
320
+ resolvePath(params: ResolverPathParams, context: ResolverContext): KubbFile.Path
321
+ resolveFile(params: ResolverFileParams, context: ResolverContext): KubbFile.File
322
+ resolveBanner(node: RootNode | null, context: ResolveBannerContext): string | undefined
323
+ resolveFooter(node: RootNode | null, context: ResolveBannerContext): string | undefined
308
324
  }
309
325
 
310
326
  /**
311
- * The user-facing subset of a `Resolver` — everything except the methods injected by
312
- * `defineResolver` (`default` and `resolveOptions`).
327
+ * The user-facing subset of a `Resolver` — everything except the four methods injected by
328
+ * `defineResolver` (`default`, `resolveOptions`, `resolvePath`, and `resolveFile`).
329
+ *
330
+ * All four injected methods can still be overridden by providing them explicitly in the builder.
313
331
  *
314
- * When you pass a `UserResolver` to `defineResolver`, the standard `default` and
315
- * `resolveOptions` implementations are injected automatically so plugin authors never
316
- * need to define them by hand. Both can still be overridden by providing them explicitly.
332
+ * @example
333
+ * ```ts
334
+ * export const resolver = defineResolver<PluginTs>(() => ({
335
+ * name: 'default',
336
+ * resolveName(node) { return this.default(node.name, 'function') },
337
+ * }))
338
+ * ```
339
+ */
340
+ export type UserResolver = Omit<Resolver, 'default' | 'resolveOptions' | 'resolvePath' | 'resolveFile' | 'resolveBanner' | 'resolveFooter'>
341
+
342
+ /**
343
+ * Base type for plugin builder objects.
344
+ * Concrete plugin builder types extend this with their own schema-building helpers.
345
+ * Use `defineBuilder` to define a builder object and export it alongside the plugin.
317
346
  */
318
- export type UserResolver = Omit<Resolver, 'default' | 'resolveOptions'>
347
+ export type Builder = {
348
+ name: string
349
+ }
319
350
 
320
351
  export type PluginFactoryOptions<
321
352
  /**
@@ -343,6 +374,11 @@ export type PluginFactoryOptions<
343
374
  * Use `defineResolver` to define the resolver object and export it alongside the plugin.
344
375
  */
345
376
  TResolver extends Resolver = Resolver,
377
+ /**
378
+ * Builder object that encapsulates the schema-building helpers used by this plugin.
379
+ * Use `defineBuilder` to define the builder object and export it alongside the plugin.
380
+ */
381
+ TBuilder extends Builder = Builder,
346
382
  > = {
347
383
  name: TName
348
384
  options: TOptions
@@ -350,6 +386,7 @@ export type PluginFactoryOptions<
350
386
  context: TContext
351
387
  resolvePathOptions: TResolvePathOptions
352
388
  resolver: TResolver
389
+ builder: TBuilder
353
390
  }
354
391
 
355
392
  export type UserPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
@@ -419,6 +456,7 @@ export type PluginLifecycle<TOptions extends PluginFactoryOptions = PluginFactor
419
456
  * Options can als be included.
420
457
  * @type hookFirst
421
458
  * @example ('./Pet.ts', './src/gen/') => '/src/gen/Pet.ts'
459
+ * @deprecated this will be replaced by resolvers
422
460
  */
423
461
  resolvePath?: (this: PluginContext<TOptions>, baseName: KubbFile.BaseName, mode?: KubbFile.Mode, options?: TOptions['resolvePathOptions']) => KubbFile.Path
424
462
  /**
@@ -426,6 +464,7 @@ export type PluginLifecycle<TOptions extends PluginFactoryOptions = PluginFactor
426
464
  * Useful when converting to PascalCase or camelCase.
427
465
  * @type hookFirst
428
466
  * @example ('pet') => 'Pet'
467
+ * @deprecated this will be replaced by resolvers
429
468
  */
430
469
  resolveName?: (this: PluginContext<TOptions>, name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
431
470
  }
@@ -504,7 +543,7 @@ export type PluginContext<TOptions extends PluginFactoryOptions = PluginFactoryO
504
543
  /**
505
544
  * Specify the export location for the files and define the behavior of the output
506
545
  */
507
- export type Output<TOptions> = {
546
+ export type Output<_TOptions = unknown> = {
508
547
  /**
509
548
  * Path to the output folder or file that will contain the generated code
510
549
  */
@@ -517,11 +556,11 @@ export type Output<TOptions> = {
517
556
  /**
518
557
  * Add a banner text in the beginning of every file
519
558
  */
520
- banner?: string | ((options: TOptions) => string)
559
+ banner?: string | ((node: RootNode) => string)
521
560
  /**
522
561
  * Add a footer text in the beginning of every file
523
562
  */
524
- footer?: string | ((options: TOptions) => string)
563
+ footer?: string | ((node: RootNode) => string)
525
564
  /**
526
565
  * Whether to override existing external files if they already exist.
527
566
  * @default false
@@ -574,8 +613,8 @@ export type { CoreGeneratorV2, Generator, ReactGeneratorV2 } from './defineGener
574
613
  export type { KubbEvents } from './Kubb.ts'
575
614
 
576
615
  /**
577
- * A preset bundles a name, one or more resolvers, and optional AST transformers
578
- * into a single reusable configuration object.
616
+ * A preset bundles a name, one or more resolvers, optional AST transformers,
617
+ * and optional generators into a single reusable configuration object.
579
618
  *
580
619
  * @template TResolver - The concrete resolver type for this preset.
581
620
  */
@@ -592,6 +631,11 @@ export type Preset<TResolver extends Resolver = Resolver> = {
592
631
  * Optional AST visitors / transformers applied after resolving.
593
632
  */
594
633
  transformers?: Array<Visitor>
634
+ /**
635
+ * Optional generators used by this preset. Plugin implementations cast this
636
+ * to their concrete generator type.
637
+ */
638
+ generators?: Array<Generator<any>>
595
639
  }
596
640
 
597
641
  /**
@@ -601,3 +645,147 @@ export type Preset<TResolver extends Resolver = Resolver> = {
601
645
  * @template TName - The union of valid preset name keys.
602
646
  */
603
647
  export type Presets<TResolver extends Resolver = Resolver> = Record<CompatibilityPreset, Preset<TResolver>>
648
+
649
+ type ByTag = {
650
+ type: 'tag'
651
+ pattern: string | RegExp
652
+ }
653
+
654
+ type ByOperationId = {
655
+ type: 'operationId'
656
+ pattern: string | RegExp
657
+ }
658
+
659
+ type ByPath = {
660
+ type: 'path'
661
+ pattern: string | RegExp
662
+ }
663
+
664
+ type ByMethod = {
665
+ type: 'method'
666
+ pattern: HttpMethod | RegExp
667
+ }
668
+ // TODO implement as alternative for ByMethod
669
+ // type ByMethods = {
670
+ // type: 'methods'
671
+ // pattern: Array<HttpMethod>
672
+ // }
673
+
674
+ type BySchemaName = {
675
+ type: 'schemaName'
676
+ pattern: string | RegExp
677
+ }
678
+
679
+ type ByContentType = {
680
+ type: 'contentType'
681
+ pattern: string | RegExp
682
+ }
683
+
684
+ export type Exclude = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
685
+ export type Include = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
686
+
687
+ export type Override<TOptions> = (ByTag | ByOperationId | ByPath | ByMethod | BySchemaName | ByContentType) & {
688
+ //TODO should be options: Omit<Partial<TOptions>, 'override'>
689
+ options: Partial<TOptions>
690
+ }
691
+
692
+ export type ResolvePathOptions = {
693
+ pluginName?: string
694
+ group?: {
695
+ tag?: string
696
+ path?: string
697
+ }
698
+ type?: ResolveNameParams['type']
699
+ }
700
+
701
+ /**
702
+ * File-specific parameters for `Resolver.resolvePath`.
703
+ *
704
+ * Pass alongside a `ResolverContext` to identify which file to resolve.
705
+ * Provide `tag` for tag-based grouping or `path` for path-based grouping.
706
+ *
707
+ * @example
708
+ * ```ts
709
+ * resolver.resolvePath(
710
+ * { baseName: 'petTypes.ts', tag: 'pets' },
711
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
712
+ * )
713
+ * // → '/src/types/petsController/petTypes.ts'
714
+ * ```
715
+ */
716
+ export type ResolverPathParams = {
717
+ baseName: KubbFile.BaseName
718
+ pathMode?: KubbFile.Mode
719
+ /**
720
+ * Tag value used when `group.type === 'tag'`.
721
+ */
722
+ tag?: string
723
+ /**
724
+ * Path value used when `group.type === 'path'`.
725
+ */
726
+ path?: string
727
+ }
728
+
729
+ /**
730
+ * Shared context passed as the second argument to `Resolver.resolvePath` and `Resolver.resolveFile`.
731
+ *
732
+ * Describes where on disk output is rooted, which output config is active, and the optional
733
+ * grouping strategy that controls subdirectory layout.
734
+ *
735
+ * @example
736
+ * ```ts
737
+ * const context: ResolverContext = {
738
+ * root: config.root,
739
+ * output,
740
+ * group,
741
+ * }
742
+ * ```
743
+ */
744
+ export type ResolverContext = {
745
+ root: string
746
+ output: Output
747
+ group?: Group
748
+ /** Plugin name used to populate `meta.pluginName` on the resolved file. */
749
+ pluginName?: string
750
+ }
751
+
752
+ /**
753
+ * File-specific parameters for `Resolver.resolveFile`.
754
+ *
755
+ * Pass alongside a `ResolverContext` to fully describe the file to resolve.
756
+ * `tag` and `path` are used only when a matching `group` is present in the context.
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * resolver.resolveFile(
761
+ * { name: 'listPets', extname: '.ts', tag: 'pets' },
762
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
763
+ * )
764
+ * // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
765
+ * ```
766
+ */
767
+ export type ResolverFileParams = {
768
+ name: string
769
+ extname: KubbFile.Extname
770
+ /** Tag value used when `group.type === 'tag'`. */
771
+ tag?: string
772
+ /** Path value used when `group.type === 'path'`. */
773
+ path?: string
774
+ }
775
+
776
+ /**
777
+ * Context passed to `Resolver.resolveBanner` and `Resolver.resolveFooter`.
778
+ *
779
+ * `output` is optional — not every plugin configures a banner/footer.
780
+ * `config` carries the global Kubb config, used to derive the default Kubb banner.
781
+ *
782
+ * @example
783
+ * ```ts
784
+ * resolver.resolveBanner(rootNode, { output: { banner: '// generated' }, config })
785
+ * // → '// generated'
786
+ * ```
787
+ */
788
+ export type ResolveBannerContext = {
789
+ output?: Pick<Output, 'banner' | 'footer'>
790
+ config: Config
791
+ }
@@ -33,7 +33,7 @@ function getBarrelFilesByRoot(root: string | undefined, files: Array<KubbFile.Re
33
33
  const cachedFiles = new Map<KubbFile.Path, KubbFile.File>()
34
34
 
35
35
  TreeNode.build(files, root)?.forEach((treeNode) => {
36
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) {
36
+ if (!treeNode?.children || !treeNode.parent?.data.path) {
37
37
  return
38
38
  }
39
39
 
@@ -1,30 +1,35 @@
1
1
  import type { Visitor } from '@kubb/ast/types'
2
- import type { CompatibilityPreset, Preset, Presets, Resolver } from '../types.ts'
2
+ import type { CompatibilityPreset, Generator, Preset, Presets, Resolver } from '../types.ts'
3
3
  import { mergeResolvers } from './mergeResolvers.ts'
4
4
 
5
5
  type GetPresetParams<TResolver extends Resolver> = {
6
6
  preset: CompatibilityPreset
7
7
  presets: Presets<TResolver>
8
8
  resolvers: Array<TResolver>
9
+ /**
10
+ * User-supplied generators to append after the preset's generators.
11
+ */
12
+ generators: Array<Generator<any>>
9
13
  transformers?: Array<Visitor>
10
14
  }
11
15
 
12
16
  type GetPresetResult<TResolver extends Resolver> = {
13
- baseResolver: TResolver
14
17
  resolver: TResolver
15
18
  transformers: Array<Visitor>
19
+ generators: Array<Generator<any>>
16
20
  preset: Preset<TResolver> | undefined
17
21
  }
18
22
 
19
23
  /**
20
- * Resolves a named preset into merged resolvers and transformers.
24
+ * Resolves a named preset into merged resolvers, transformers, and generators.
21
25
  *
22
- * - Merges the preset's resolvers on top of the first (default) resolver to produce `baseResolver`.
26
+ * - Merges the preset's resolvers on top of the first (default)
23
27
  * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
24
28
  * - Concatenates preset transformers before user-supplied transformers.
29
+ * - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
25
30
  */
26
31
  export function getPreset<TResolver extends Resolver = Resolver>(params: GetPresetParams<TResolver>): GetPresetResult<TResolver> {
27
- const { preset: presetName, presets, resolvers, transformers: userTransformers } = params
32
+ const { preset: presetName, presets, resolvers, transformers: userTransformers, generators: userGenerators } = params
28
33
  const [defaultResolver, ...userResolvers] = resolvers
29
34
  const preset = presets[presetName]
30
35
 
@@ -32,10 +37,16 @@ export function getPreset<TResolver extends Resolver = Resolver>(params: GetPres
32
37
  const resolver = mergeResolvers(baseResolver, ...(userResolvers ?? []))
33
38
  const transformers = [...(preset?.transformers ?? []), ...(userTransformers ?? [])]
34
39
 
40
+ const presetGenerators = preset?.generators ?? []
41
+ const defaultPresetGenerators = presets['default']?.generators ?? []
42
+ const generators = (presetGenerators.length > 0 || userGenerators.length
43
+ ? [...presetGenerators, ...userGenerators]
44
+ : defaultPresetGenerators) as unknown as Array<Generator<any>>
45
+
35
46
  return {
36
- baseResolver,
37
47
  resolver,
38
48
  transformers,
49
+ generators,
39
50
  preset,
40
51
  }
41
52
  }
@@ -1,160 +0,0 @@
1
- import path from 'node:path'
2
- import type { RootNode } from '@kubb/ast/types'
3
- import type { KubbFile } from '@kubb/fabric-core/types'
4
- import { useFabric } from '@kubb/react-fabric'
5
- import type { GetFileOptions, PluginDriver } from '../PluginDriver.ts'
6
- import type { Config, Plugin, PluginFactoryOptions, ResolveNameParams, ResolvePathParams } from '../types.ts'
7
-
8
- type ResolvePathOptions = {
9
- pluginName?: string
10
- group?: {
11
- tag?: string
12
- path?: string
13
- }
14
- type?: ResolveNameParams['type']
15
- }
16
-
17
- type UseKubbReturn<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
18
- plugin: Plugin<TOptions>
19
- mode: KubbFile.Mode
20
- config: Config
21
- /**
22
- * Returns the plugin whose `name` matches `pluginName`, defaulting to the current plugin.
23
- */
24
- getPluginByName: (pluginName?: string) => Plugin | undefined
25
- /**
26
- * Resolves a file reference, defaulting `pluginName` to the current plugin.
27
- */
28
- getFile: (params: Omit<GetFileOptions<ResolvePathOptions>, 'pluginName'> & { pluginName?: string }) => KubbFile.File<{ pluginName: string }>
29
- /**
30
- * Resolves a name, defaulting `pluginName` to the current plugin.
31
- * @deprecated user `resolver` from options instead
32
- */
33
- resolveName: (params: Omit<ResolveNameParams, 'pluginName'> & { pluginName?: string }) => string
34
- /**
35
- * Resolves a path, defaulting `pluginName` to the current plugin.
36
- */
37
- resolvePath: <TPathOptions = object>(params: Omit<ResolvePathParams<TPathOptions>, 'pluginName'> & { pluginName?: string }) => KubbFile.Path
38
- /**
39
- * Resolves the banner using the plugin's `output.banner` option.
40
- * Falls back to the default "Generated by Kubb" banner when `output.banner` is unset.
41
- * When `output.banner` is a function and no node is provided, returns the default banner.
42
- */
43
- resolveBanner: (node?: RootNode) => string | undefined
44
- /**
45
- * Resolves the footer using the plugin's `output.footer` option.
46
- * Returns `undefined` when no footer is configured.
47
- * When `output.footer` is a function and no node is provided, returns `undefined`.
48
- */
49
- resolveFooter: (node?: RootNode) => string | undefined
50
- }
51
-
52
- /**
53
- * Generates the default "Generated by Kubb" banner from node metadata.
54
- */
55
- function buildDefaultBanner({ title, description, version, config }: { title?: string; description?: string; version?: string; config: Config }): string {
56
- try {
57
- let source = ''
58
- if (Array.isArray(config.input)) {
59
- const first = config.input[0]
60
- if (first && 'path' in first) {
61
- source = path.basename(first.path)
62
- }
63
- } else if ('path' in config.input) {
64
- source = path.basename(config.input.path)
65
- } else if ('data' in config.input) {
66
- source = 'text content'
67
- }
68
-
69
- let banner = '/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n'
70
-
71
- if (config.output.defaultBanner === 'simple') {
72
- banner += '*/\n'
73
- return banner
74
- }
75
-
76
- if (source) {
77
- banner += `* Source: ${source}\n`
78
- }
79
-
80
- if (title) {
81
- banner += `* Title: ${title}\n`
82
- }
83
-
84
- if (description) {
85
- const formattedDescription = description.replace(/\n/gm, '\n* ')
86
- banner += `* Description: ${formattedDescription}\n`
87
- }
88
-
89
- if (version) {
90
- banner += `* OpenAPI spec version: ${version}\n`
91
- }
92
-
93
- banner += '*/\n'
94
- return banner
95
- } catch (_error) {
96
- return '/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/'
97
- }
98
- }
99
-
100
- /**
101
- * React-Fabric hook that exposes the current plugin context inside a generator component.
102
- *
103
- * Returns the active `plugin`, `mode`, `config`, and a set of resolver helpers
104
- * (`getFile`, `resolveName`, `resolvePath`, `resolveBanner`, `resolveFooter`) that
105
- * all default to the current plugin when no explicit `pluginName` is provided.
106
- *
107
- * @example
108
- * ```ts
109
- * function Operation({ node }: OperationProps) {
110
- * const { config, resolvePath } = useKubb()
111
- * const filePath = resolvePath({ baseName: node.operationId })
112
- * return <File path={filePath}>...</File>
113
- * }
114
- * ```
115
- */
116
- export function useKubb<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(): UseKubbReturn<TOptions> {
117
- const { meta } = useFabric<{
118
- plugin: Plugin<TOptions>
119
- mode: KubbFile.Mode
120
- driver: PluginDriver
121
- }>()
122
-
123
- const config = meta.driver.config
124
- const defaultPluginName = meta.plugin.name
125
-
126
- const output = (
127
- meta.plugin.options as { output?: { banner?: string | ((node: RootNode) => string); footer?: string | ((node: RootNode) => string) } } | undefined
128
- )?.output
129
-
130
- return {
131
- plugin: meta.plugin as Plugin<TOptions>,
132
- mode: meta.mode,
133
- config,
134
- getPluginByName: (pluginName = defaultPluginName) => meta.driver.getPluginByName.call(meta.driver, pluginName),
135
- getFile: ({ pluginName = defaultPluginName, ...rest }) => meta.driver.getFile.call(meta.driver, { pluginName, ...rest }),
136
- resolveName: ({ pluginName = defaultPluginName, ...rest }) => meta.driver.resolveName.call(meta.driver, { pluginName, ...rest }),
137
- resolvePath: ({ pluginName = defaultPluginName, ...rest }) => meta.driver.resolvePath.call(meta.driver, { pluginName, ...rest }),
138
- resolveBanner: (node?: RootNode) => {
139
- if (typeof output?.banner === 'function') {
140
- return node ? output.banner(node) : buildDefaultBanner({ config })
141
- }
142
- if (typeof output?.banner === 'string') {
143
- return output.banner
144
- }
145
- if (config.output.defaultBanner === false) {
146
- return undefined
147
- }
148
- return buildDefaultBanner({ config })
149
- },
150
- resolveFooter: (node?: RootNode) => {
151
- if (typeof output?.footer === 'function') {
152
- return node ? output.footer(node) : undefined
153
- }
154
- if (typeof output?.footer === 'string') {
155
- return output.footer
156
- }
157
- return undefined
158
- },
159
- }
160
- }