@kubb/core 4.34.0 → 4.35.0

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.
@@ -2,6 +2,7 @@ import { t as __name } from "./chunk--u3MIqq1.js";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { Fabric } from "@kubb/react-fabric";
4
4
  import { KubbFile } from "@kubb/fabric-core/types";
5
+ import { Printer, RootNode } from "@kubb/ast/types";
5
6
 
6
7
  //#region ../../internals/utils/dist/index.d.ts
7
8
  /**
@@ -24,6 +25,7 @@ declare var AsyncEventEmitter: {
24
25
  };
25
26
  //#endregion
26
27
  //#region src/constants.d.ts
28
+ declare const DEFAULT_STUDIO_URL: "https://studio.kubb.dev";
27
29
  declare const logLevel: {
28
30
  readonly silent: number;
29
31
  readonly error: 0;
@@ -95,6 +97,11 @@ declare class PluginManager {
95
97
  #private;
96
98
  readonly config: Config;
97
99
  readonly options: Options;
100
+ /**
101
+ * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
102
+ * the build pipeline after the adapter's `parse()` resolves.
103
+ */
104
+ rootNode: RootNode | undefined;
98
105
  constructor(config: Config, options: Options);
99
106
  get events(): AsyncEventEmitter<KubbEvents>;
100
107
  getContext<TOptions extends PluginFactoryOptions>(plugin: Plugin<TOptions>): PluginContext<TOptions> & Record<string, unknown>;
@@ -437,7 +444,65 @@ type InputData = {
437
444
  data: string | unknown;
438
445
  };
439
446
  type Input = InputPath | InputData | Array<InputPath>;
447
+ /**
448
+ * The raw source passed to an adapter's `parse` function.
449
+ * Mirrors the shape of `Config['input']` with paths already resolved to absolute.
450
+ */
451
+ type AdapterSource = {
452
+ type: 'path';
453
+ path: string;
454
+ } | {
455
+ type: 'data';
456
+ data: string | unknown;
457
+ } | {
458
+ type: 'paths';
459
+ paths: Array<string>;
460
+ };
461
+ /**
462
+ * Type parameters for an adapter definition.
463
+ *
464
+ * Mirrors `PluginFactoryOptions` but scoped to the adapter lifecycle:
465
+ * - `TName` — unique string identifier (e.g. `'oas'`, `'asyncapi'`)
466
+ * - `TOptions` — raw user-facing options passed to the adapter factory
467
+ * - `TResolvedOptions` — defaults applied; what the adapter stores as `options`
468
+ */
469
+ type AdapterFactoryOptions<TName extends string = string, TOptions extends object = object, TResolvedOptions extends object = TOptions> = {
470
+ name: TName;
471
+ options: TOptions;
472
+ resolvedOptions: TResolvedOptions;
473
+ };
474
+ /**
475
+ * An adapter converts a source file or data into a `@kubb/ast` `RootNode`.
476
+ *
477
+ * Adapters are the single entry-point for different schema formats
478
+ * (OpenAPI, AsyncAPI, Drizzle, …) and produce the universal `RootNode`
479
+ * that all Kubb plugins consume.
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * import { oasAdapter } from '@kubb/adapter-oas'
484
+ *
485
+ * export default defineConfig({
486
+ * adapter: adapterOas(), // default — OpenAPI / Swagger
487
+ * input: { path: './openapi.yaml' },
488
+ * plugins: [pluginTs(), pluginZod()],
489
+ * })
490
+ * ```
491
+ */
492
+ type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
493
+ /** Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`. */name: TOptions['name']; /** Resolved options (after defaults have been applied). */
494
+ options: TOptions['resolvedOptions']; /** Convert the raw source into a universal `RootNode`. */
495
+ parse: (source: AdapterSource) => PossiblePromise<RootNode>;
496
+ };
440
497
  type BarrelType = 'all' | 'named' | 'propagate';
498
+ type DevtoolsOptions = {
499
+ /**
500
+ * Open the AST inspector view (`/ast`) in Kubb Studio.
501
+ * When `false`, opens the main Studio page instead.
502
+ * @default false
503
+ */
504
+ ast?: boolean;
505
+ };
441
506
  /**
442
507
  * @private
443
508
  */
@@ -451,6 +516,24 @@ type Config<TInput = Input> = {
451
516
  * @default process.cwd()
452
517
  */
453
518
  root: string;
519
+ /**
520
+ * Adapter that converts the input file into a `@kubb/ast` `RootNode` — the universal
521
+ * intermediate representation consumed by all Kubb plugins.
522
+ *
523
+ * - Omit (or pass `undefined`) to use the built-in OpenAPI/Swagger adapter.
524
+ * - Use `@kubb/adapter-oas` for explicit OpenAPI configuration (validate, contentType, …).
525
+ * - Use `@kubb/adapter-drizzle` or `@kubb/adapter-asyncapi` for other formats.
526
+ *
527
+ * @example
528
+ * ```ts
529
+ * import { drizzleAdapter } from '@kubb/adapter-drizzle'
530
+ * export default defineConfig({
531
+ * adapter: drizzleAdapter(),
532
+ * input: { path: './src/schema.ts' },
533
+ * })
534
+ * ```
535
+ */
536
+ adapter?: Adapter;
454
537
  /**
455
538
  * You can use either `input.path` or `input.data`, depending on your specific needs.
456
539
  */
@@ -522,6 +605,16 @@ type Config<TInput = Input> = {
522
605
  * If a plugin depends on another plugin, an error is returned if the required dependency is missing. See pre for more details.
523
606
  */
524
607
  plugins?: Array<Plugin>;
608
+ /**
609
+ * Devtools configuration for Kubb Studio integration.
610
+ */
611
+ devtools?: true | {
612
+ /**
613
+ * Override the Kubb Studio base URL.
614
+ * @default 'https://studio.kubb.dev'
615
+ */
616
+ studioUrl?: typeof DEFAULT_STUDIO_URL | (string & {});
617
+ };
525
618
  /**
526
619
  * Hooks triggered when a specific action occurs in Kubb.
527
620
  */
@@ -684,6 +777,17 @@ type PluginContext<TOptions extends PluginFactoryOptions = PluginFactoryOptions>
684
777
  * Current plugin
685
778
  */
686
779
  plugin: Plugin<TOptions>;
780
+ /**
781
+ * Returns the universal `@kubb/ast` `RootNode` produced by the configured adapter.
782
+ * Returns `undefined` when no adapter was set (legacy OAS-only usage).
783
+ */
784
+ rootNode: RootNode | undefined;
785
+ /**
786
+ * Opens the Kubb Studio URL for the current `rootNode` in the default browser.
787
+ * Falls back to printing the URL if the browser cannot be launched.
788
+ * No-ops silently when no adapter has set a `rootNode`.
789
+ */
790
+ openInStudio: (options?: DevtoolsOptions) => Promise<void>;
687
791
  } & Kubb.PluginContext;
688
792
  /**
689
793
  * Specify the export location for the files and define the behavior of the output
@@ -745,5 +849,5 @@ type Logger<TOptions extends LoggerOptions = LoggerOptions> = {
745
849
  };
746
850
  type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Omit<Logger<TOptions>, 'logLevel'>;
747
851
  //#endregion
748
- export { linters as A, UserLogger as C, PluginManager as D, KubbEvents as E, AsyncEventEmitter as M, getMode as O, UserConfig as S, UserPluginWithLifeCycle as T, PluginParameter as _, InputData as a, ResolvePathParams as b, LoggerContext as c, Plugin as d, PluginContext as f, PluginLifecycleHooks as g, PluginLifecycle as h, Group as i, logLevel as j, formatters as k, LoggerOptions as l, PluginKey as m, Config as n, InputPath as o, PluginFactoryOptions as p, GetPluginFactoryOptions as r, Logger as s, BarrelType as t, Output as u, PluginWithLifeCycle as v, UserPlugin as w, UnknownUserPlugin as x, ResolveNameParams as y };
749
- //# sourceMappingURL=types-DfjjJb2r.d.ts.map
852
+ export { UserPluginWithLifeCycle as A, Printer as C, UserConfig as D, UnknownUserPlugin as E, linters as F, logLevel as I, AsyncEventEmitter as L, PluginManager as M, getMode as N, UserLogger as O, formatters as P, PluginWithLifeCycle as S, ResolvePathParams as T, PluginFactoryOptions as _, Config as a, PluginLifecycleHooks as b, Group as c, Logger as d, LoggerContext as f, PluginContext as g, Plugin as h, BarrelType as i, KubbEvents as j, UserPlugin as k, InputData as l, Output as m, AdapterFactoryOptions as n, DevtoolsOptions as o, LoggerOptions as p, AdapterSource as r, GetPluginFactoryOptions as s, Adapter as t, InputPath as u, PluginKey as v, ResolveNameParams as w, PluginParameter as x, PluginLifecycle as y };
853
+ //# sourceMappingURL=types-7DgxNmCG.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "4.34.0",
3
+ "version": "4.35.0",
4
4
  "description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -67,9 +67,11 @@
67
67
  "@kubb/fabric-core": "0.13.3",
68
68
  "@kubb/react-fabric": "0.13.3",
69
69
  "empathic": "^2.0.0",
70
+ "fflate": "^0.8.2",
70
71
  "remeda": "^2.33.6",
71
72
  "semver": "^7.7.4",
72
- "tinyexec": "^1.0.2"
73
+ "tinyexec": "^1.0.4",
74
+ "@kubb/ast": "4.35.0"
73
75
  },
74
76
  "devDependencies": {
75
77
  "@types/semver": "^7.7.1",
@@ -90,8 +92,8 @@
90
92
  "main": "./dist/index.cjs",
91
93
  "module": "./dist/index.js",
92
94
  "inlinedDependencies": {
93
- "yocto-queue": "1.2.2",
94
- "p-limit": "7.3.0"
95
+ "p-limit": "7.3.0",
96
+ "yocto-queue": "1.2.2"
95
97
  },
96
98
  "scripts": {
97
99
  "build": "tsdown && size-limit",
@@ -2,13 +2,16 @@ import path from 'node:path'
2
2
  import { performance } from 'node:perf_hooks'
3
3
  import type { AsyncEventEmitter } from '@internals/utils'
4
4
  import { setUniqueName, transformReservedWord } from '@internals/utils'
5
+ import type { RootNode } from '@kubb/ast/types'
5
6
  import type { KubbFile } from '@kubb/fabric-core/types'
6
7
  import type { Fabric } from '@kubb/react-fabric'
7
- import { CORE_PLUGIN_NAME } from './constants.ts'
8
+ import { CORE_PLUGIN_NAME, DEFAULT_STUDIO_URL } from './constants.ts'
9
+ import { openInStudio as openInStudioFn } from './devtools.ts'
8
10
  import { ValidationPluginError } from './errors.ts'
9
11
  import { isPromiseRejectedResult, PromiseManager } from './PromiseManager.ts'
10
12
  import type {
11
13
  Config,
14
+ DevtoolsOptions,
12
15
  KubbEvents,
13
16
  Plugin,
14
17
  PluginContext,
@@ -63,6 +66,13 @@ export class PluginManager {
63
66
  readonly config: Config
64
67
  readonly options: Options
65
68
 
69
+ /**
70
+ * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
71
+ * the build pipeline after the adapter's `parse()` resolves.
72
+ */
73
+ rootNode: RootNode | undefined = undefined
74
+ #studioIsOpen = false
75
+
66
76
  readonly #plugins = new Set<Plugin>()
67
77
  readonly #usedPluginNames: Record<string, number> = {}
68
78
  readonly #promiseManager
@@ -86,6 +96,8 @@ export class PluginManager {
86
96
 
87
97
  getContext<TOptions extends PluginFactoryOptions>(plugin: Plugin<TOptions>): PluginContext<TOptions> & Record<string, unknown> {
88
98
  const plugins = [...this.#plugins]
99
+ const pluginManager = this
100
+
89
101
  const baseContext = {
90
102
  fabric: this.options.fabric,
91
103
  config: this.config,
@@ -99,6 +111,28 @@ export class PluginManager {
99
111
  upsertFile: async (...files: Array<KubbFile.File>) => {
100
112
  await this.options.fabric.upsertFile(...files)
101
113
  },
114
+ get rootNode(): RootNode | undefined {
115
+ return pluginManager.rootNode
116
+ },
117
+ openInStudio(options?: DevtoolsOptions) {
118
+ if (typeof pluginManager.config.devtools !== 'object') {
119
+ throw new Error('Devtools must be an object')
120
+ }
121
+
122
+ if (!pluginManager.rootNode) {
123
+ throw new Error('RootNode is not defined, make sure you have set the parser in kubb.config.ts')
124
+ }
125
+
126
+ if (pluginManager.#studioIsOpen) {
127
+ return
128
+ }
129
+
130
+ pluginManager.#studioIsOpen = true
131
+
132
+ const studioUrl = pluginManager.config.devtools?.studioUrl ?? DEFAULT_STUDIO_URL
133
+
134
+ return openInStudioFn(pluginManager.rootNode, studioUrl, options)
135
+ },
102
136
  } as unknown as PluginContext<TOptions>
103
137
 
104
138
  const mergedExtras: Record<string, unknown> = {}
package/src/build.ts CHANGED
@@ -6,10 +6,10 @@ import { createFabric } from '@kubb/react-fabric'
6
6
  import { typescriptParser } from '@kubb/react-fabric/parsers'
7
7
  import { fsPlugin } from '@kubb/react-fabric/plugins'
8
8
  import { isInputPath } from './config.ts'
9
- import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION } from './constants.ts'
9
+ import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
10
10
  import { BuildError } from './errors.ts'
11
11
  import { PluginManager } from './PluginManager.ts'
12
- import type { Config, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
12
+ import type { AdapterSource, Config, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
13
13
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
14
14
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
15
15
 
@@ -96,6 +96,12 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
96
96
  defaultBanner: DEFAULT_BANNER,
97
97
  ...userConfig.output,
98
98
  },
99
+ devtools: userConfig.devtools
100
+ ? {
101
+ studioUrl: DEFAULT_STUDIO_URL,
102
+ ...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
103
+ }
104
+ : undefined,
99
105
  plugins: userConfig.plugins as Config['plugins'],
100
106
  }
101
107
 
@@ -159,6 +165,27 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
159
165
  concurrency: DEFAULT_CONCURRENCY,
160
166
  })
161
167
 
168
+ // Run the adapter (if provided) to produce the universal RootNode
169
+ if (definedConfig.adapter) {
170
+ const source = inputToAdapterSource(definedConfig)
171
+
172
+ await events.emit('debug', {
173
+ date: new Date(),
174
+ logs: [`Running adapter: ${definedConfig.adapter.name}`],
175
+ })
176
+
177
+ pluginManager.rootNode = await definedConfig.adapter.parse(source)
178
+
179
+ await events.emit('debug', {
180
+ date: new Date(),
181
+ logs: [
182
+ `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
183
+ ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
184
+ ` • Operations: ${pluginManager.rootNode.operations.length}`,
185
+ ],
186
+ })
187
+ }
188
+
162
189
  return {
163
190
  events,
164
191
  fabric,
@@ -364,3 +391,23 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
364
391
  })
365
392
  })
366
393
  }
394
+
395
+ /**
396
+ * Maps the resolved `Config['input']` shape into an `AdapterSource` that
397
+ * the adapter's `parse()` can consume.
398
+ */
399
+ function inputToAdapterSource(config: Config): AdapterSource {
400
+ if (Array.isArray(config.input)) {
401
+ return {
402
+ type: 'paths',
403
+ paths: config.input.map((i) => resolve(config.root, i.path)),
404
+ }
405
+ }
406
+
407
+ if ('data' in config.input) {
408
+ return { type: 'data', data: config.input.data }
409
+ }
410
+
411
+ const resolved = resolve(config.root, config.input.path)
412
+ return { type: 'path', path: resolved }
413
+ }
package/src/constants.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { KubbFile } from '@kubb/fabric-core/types'
2
2
 
3
+ export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
4
+
3
5
  export const CORE_PLUGIN_NAME = 'core' as const
4
6
 
5
7
  export const DEFAULT_MAX_LISTENERS = 100
@@ -0,0 +1,22 @@
1
+ import type { Adapter, AdapterFactoryOptions } from './types.ts'
2
+
3
+ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>
4
+
5
+ /**
6
+ * Wraps an adapter builder to make the options parameter optional.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * export const adapterOas = defineAdapter<OasAdapter>((options) => {
11
+ * const { validate = true, dateType = 'string' } = options
12
+ * return {
13
+ * name: adapterOasName,
14
+ * options: { validate, dateType, ... },
15
+ * parse(source) { ... },
16
+ * }
17
+ * })
18
+ * ```
19
+ */
20
+ export function defineAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T> {
21
+ return (options) => build(options ?? ({} as T['options']))
22
+ }
@@ -0,0 +1,59 @@
1
+ import type { RootNode } from '@kubb/ast/types'
2
+ import { deflateSync, inflateSync } from 'fflate'
3
+ import { x } from 'tinyexec'
4
+ import type { DevtoolsOptions } from './types.ts'
5
+
6
+ /**
7
+ * Encodes a `RootNode` as a compressed, URL-safe string.
8
+ *
9
+ * The JSON representation is deflate-compressed with {@link deflateSync} before
10
+ * base64url encoding, which typically reduces payload size by 70–80 % and
11
+ * keeps URLs well within browser and server path-length limits.
12
+ *
13
+ * Use {@link decodeAst} to reverse.
14
+ */
15
+ export function encodeAst(root: RootNode): string {
16
+ const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(root)))
17
+ return Buffer.from(compressed).toString('base64url')
18
+ }
19
+
20
+ /**
21
+ * Decodes a `RootNode` from a string produced by {@link encodeAst}.
22
+ *
23
+ * Works in both Node.js and the browser — no streaming APIs required.
24
+ */
25
+ export function decodeAst(encoded: string): RootNode {
26
+ const bytes = Buffer.from(encoded, 'base64url')
27
+ return JSON.parse(new TextDecoder().decode(inflateSync(bytes))) as RootNode
28
+ }
29
+
30
+ /**
31
+ * Constructs the Kubb Studio URL for the given `RootNode`.
32
+ * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
33
+ * The `root` is encoded and attached as the `?root=` query parameter so Studio
34
+ * can decode and render it without a round-trip to any server.
35
+ */
36
+ export function getStudioUrl(root: RootNode, studioUrl: string, options: DevtoolsOptions = {}): string {
37
+ const baseUrl = studioUrl.replace(/\/$/, '')
38
+ const path = options.ast ? '/ast' : ''
39
+
40
+ return `${baseUrl}${path}?root=${encodeAst(root)}`
41
+ }
42
+
43
+ /**
44
+ * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
45
+ *
46
+ * Falls back to printing the URL if the browser cannot be launched.
47
+ */
48
+ export async function openInStudio(root: RootNode, studioUrl: string, options: DevtoolsOptions = {}): Promise<void> {
49
+ const url = getStudioUrl(root, studioUrl, options)
50
+
51
+ const cmd = process.platform === 'win32' ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open'
52
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url]
53
+
54
+ try {
55
+ await x(cmd, args)
56
+ } catch {
57
+ console.log(`\n ${url}\n`)
58
+ }
59
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
+ export { AsyncEventEmitter } from '@internals/utils'
2
+ export { definePrinter } from '@kubb/ast'
1
3
  export { build, build as default, safeBuild, setup } from './build.ts'
2
4
  export { type CLIOptions, type ConfigInput, defineConfig, isInputPath } from './config.ts'
3
5
  export { formatters, linters, logLevel } from './constants.ts'
6
+ export { defineAdapter } from './defineAdapter.ts'
4
7
  export { defineLogger } from './defineLogger.ts'
5
8
  export { definePlugin } from './definePlugin.ts'
6
9
  export { PackageManager } from './PackageManager.ts'
package/src/types.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import type { AsyncEventEmitter, PossiblePromise } from '@internals/utils'
2
+ import type { RootNode } from '@kubb/ast/types'
2
3
  import type { KubbFile } from '@kubb/fabric-core/types'
3
4
  import type { Fabric } from '@kubb/react-fabric'
4
- import type { logLevel } from './constants.ts'
5
+ import type { DEFAULT_STUDIO_URL, logLevel } from './constants.ts'
5
6
  import type { KubbEvents } from './Kubb.ts'
6
7
  import type { PluginManager } from './PluginManager.ts'
7
8
 
9
+ export type { Printer } from '@kubb/ast/types'
10
+
8
11
  declare global {
9
12
  namespace Kubb {
10
13
  interface PluginContext {}
@@ -49,8 +52,64 @@ export type InputData = {
49
52
 
50
53
  type Input = InputPath | InputData | Array<InputPath>
51
54
 
55
+ /**
56
+ * The raw source passed to an adapter's `parse` function.
57
+ * Mirrors the shape of `Config['input']` with paths already resolved to absolute.
58
+ */
59
+ export type AdapterSource = { type: 'path'; path: string } | { type: 'data'; data: string | unknown } | { type: 'paths'; paths: Array<string> }
60
+
61
+ /**
62
+ * Type parameters for an adapter definition.
63
+ *
64
+ * Mirrors `PluginFactoryOptions` but scoped to the adapter lifecycle:
65
+ * - `TName` — unique string identifier (e.g. `'oas'`, `'asyncapi'`)
66
+ * - `TOptions` — raw user-facing options passed to the adapter factory
67
+ * - `TResolvedOptions` — defaults applied; what the adapter stores as `options`
68
+ */
69
+ export type AdapterFactoryOptions<TName extends string = string, TOptions extends object = object, TResolvedOptions extends object = TOptions> = {
70
+ name: TName
71
+ options: TOptions
72
+ resolvedOptions: TResolvedOptions
73
+ }
74
+
75
+ /**
76
+ * An adapter converts a source file or data into a `@kubb/ast` `RootNode`.
77
+ *
78
+ * Adapters are the single entry-point for different schema formats
79
+ * (OpenAPI, AsyncAPI, Drizzle, …) and produce the universal `RootNode`
80
+ * that all Kubb plugins consume.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { oasAdapter } from '@kubb/adapter-oas'
85
+ *
86
+ * export default defineConfig({
87
+ * adapter: adapterOas(), // default — OpenAPI / Swagger
88
+ * input: { path: './openapi.yaml' },
89
+ * plugins: [pluginTs(), pluginZod()],
90
+ * })
91
+ * ```
92
+ */
93
+ export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
94
+ /** Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`. */
95
+ name: TOptions['name']
96
+ /** Resolved options (after defaults have been applied). */
97
+ options: TOptions['resolvedOptions']
98
+ /** Convert the raw source into a universal `RootNode`. */
99
+ parse: (source: AdapterSource) => PossiblePromise<RootNode>
100
+ }
101
+
52
102
  export type BarrelType = 'all' | 'named' | 'propagate'
53
103
 
104
+ export type DevtoolsOptions = {
105
+ /**
106
+ * Open the AST inspector view (`/ast`) in Kubb Studio.
107
+ * When `false`, opens the main Studio page instead.
108
+ * @default false
109
+ */
110
+ ast?: boolean
111
+ }
112
+
54
113
  /**
55
114
  * @private
56
115
  */
@@ -64,6 +123,24 @@ export type Config<TInput = Input> = {
64
123
  * @default process.cwd()
65
124
  */
66
125
  root: string
126
+ /**
127
+ * Adapter that converts the input file into a `@kubb/ast` `RootNode` — the universal
128
+ * intermediate representation consumed by all Kubb plugins.
129
+ *
130
+ * - Omit (or pass `undefined`) to use the built-in OpenAPI/Swagger adapter.
131
+ * - Use `@kubb/adapter-oas` for explicit OpenAPI configuration (validate, contentType, …).
132
+ * - Use `@kubb/adapter-drizzle` or `@kubb/adapter-asyncapi` for other formats.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * import { drizzleAdapter } from '@kubb/adapter-drizzle'
137
+ * export default defineConfig({
138
+ * adapter: drizzleAdapter(),
139
+ * input: { path: './src/schema.ts' },
140
+ * })
141
+ * ```
142
+ */
143
+ adapter?: Adapter
67
144
  /**
68
145
  * You can use either `input.path` or `input.data`, depending on your specific needs.
69
146
  */
@@ -135,6 +212,18 @@ export type Config<TInput = Input> = {
135
212
  * If a plugin depends on another plugin, an error is returned if the required dependency is missing. See pre for more details.
136
213
  */
137
214
  plugins?: Array<Plugin>
215
+ /**
216
+ * Devtools configuration for Kubb Studio integration.
217
+ */
218
+ devtools?:
219
+ | true
220
+ | {
221
+ /**
222
+ * Override the Kubb Studio base URL.
223
+ * @default 'https://studio.kubb.dev'
224
+ */
225
+ studioUrl?: typeof DEFAULT_STUDIO_URL | (string & {})
226
+ }
138
227
  /**
139
228
  * Hooks triggered when a specific action occurs in Kubb.
140
229
  */
@@ -315,6 +404,17 @@ export type PluginContext<TOptions extends PluginFactoryOptions = PluginFactoryO
315
404
  * Current plugin
316
405
  */
317
406
  plugin: Plugin<TOptions>
407
+ /**
408
+ * Returns the universal `@kubb/ast` `RootNode` produced by the configured adapter.
409
+ * Returns `undefined` when no adapter was set (legacy OAS-only usage).
410
+ */
411
+ rootNode: RootNode | undefined
412
+ /**
413
+ * Opens the Kubb Studio URL for the current `rootNode` in the default browser.
414
+ * Falls back to printing the URL if the browser cannot be launched.
415
+ * No-ops silently when no adapter has set a `rootNode`.
416
+ */
417
+ openInStudio: (options?: DevtoolsOptions) => Promise<void>
318
418
  } & Kubb.PluginContext
319
419
  /**
320
420
  * Specify the export location for the files and define the behavior of the output
@@ -1,4 +1,5 @@
1
1
  import { camelCase } from '@internals/utils'
2
+ // TODO replace with @internals/utils
2
3
  import { sortBy } from 'remeda'
3
4
 
4
5
  type FunctionParamsASTWithoutType = {