@kubb/core 5.0.0-alpha.34 → 5.0.0-alpha.36

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.
Files changed (45) hide show
  1. package/dist/PluginDriver-B_65W4fv.js +1677 -0
  2. package/dist/PluginDriver-B_65W4fv.js.map +1 -0
  3. package/dist/{PluginDriver-BBi_41VF.d.ts → PluginDriver-C9iBgYbk.d.ts} +743 -376
  4. package/dist/PluginDriver-CCdkwR14.cjs +1806 -0
  5. package/dist/PluginDriver-CCdkwR14.cjs.map +1 -0
  6. package/dist/hooks.d.ts +1 -1
  7. package/dist/index.cjs +272 -1666
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +62 -141
  10. package/dist/index.js +231 -1623
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +165 -0
  13. package/dist/mocks.cjs.map +1 -0
  14. package/dist/mocks.d.ts +74 -0
  15. package/dist/mocks.js +159 -0
  16. package/dist/mocks.js.map +1 -0
  17. package/package.json +11 -5
  18. package/src/FileManager.ts +1 -1
  19. package/src/FileProcessor.ts +1 -1
  20. package/src/Kubb.ts +145 -38
  21. package/src/PluginDriver.ts +318 -40
  22. package/src/constants.ts +1 -1
  23. package/src/{build.ts → createKubb.ts} +180 -122
  24. package/src/createPlugin.ts +1 -0
  25. package/src/createRenderer.ts +57 -0
  26. package/src/defineGenerator.ts +57 -84
  27. package/src/defineLogger.ts +2 -2
  28. package/src/defineParser.ts +3 -2
  29. package/src/definePlugin.ts +95 -0
  30. package/src/defineResolver.ts +1 -1
  31. package/src/devtools.ts +1 -1
  32. package/src/index.ts +7 -6
  33. package/src/mocks.ts +234 -0
  34. package/src/renderNode.ts +35 -0
  35. package/src/types.ts +275 -210
  36. package/src/utils/TreeNode.ts +1 -1
  37. package/src/utils/getBarrelFiles.ts +3 -3
  38. package/src/utils/getFunctionParams.ts +14 -7
  39. package/src/utils/isInputPath.ts +2 -2
  40. package/src/utils/packageJSON.ts +2 -3
  41. package/src/defineConfig.ts +0 -51
  42. package/src/definePresets.ts +0 -16
  43. package/src/renderNode.tsx +0 -28
  44. package/src/utils/getConfigs.ts +0 -16
  45. package/src/utils/getPreset.ts +0 -78
@@ -1,28 +1,31 @@
1
1
  import { dirname, resolve } from 'node:path'
2
2
  import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
+ import type { ExportNode, FileNode, OperationNode } from '@kubb/ast'
3
4
  import { createExport, createFile, transform, walk } from '@kubb/ast'
4
- import type { ExportNode, FileNode, OperationNode } from '@kubb/ast/types'
5
5
  import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
6
+ import type { RendererFactory } from './createRenderer.ts'
7
+ import type { Generator } from './defineGenerator.ts'
6
8
  import type { Parser } from './defineParser.ts'
7
9
  import { FileProcessor } from './FileProcessor.ts'
10
+ import type { Kubb } from './Kubb.ts'
8
11
  import { PluginDriver } from './PluginDriver.ts'
9
- import { applyHookResult } from './renderNode.tsx'
12
+ import { applyHookResult } from './renderNode.ts'
10
13
  import { fsStorage } from './storages/fsStorage.ts'
11
- import type { AdapterSource, Config, KubbEvents, Plugin, PluginContext, Storage, UserConfig } from './types.ts'
14
+ import type { AdapterSource, Config, GeneratorContext, KubbHooks, Plugin, PluginContext, Storage } from './types.ts'
12
15
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
13
16
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
14
17
  import { getBarrelFiles } from './utils/getBarrelFiles.ts'
15
18
  import { isInputPath } from './utils/isInputPath.ts'
16
19
 
17
20
  type BuildOptions = {
18
- config: UserConfig
19
- events?: AsyncEventEmitter<KubbEvents>
21
+ config: Config
22
+ hooks?: AsyncEventEmitter<KubbHooks>
20
23
  }
21
24
 
22
25
  /**
23
26
  * Full output produced by a successful or failed build.
24
27
  */
25
- type BuildOutput = {
28
+ export type BuildOutput = {
26
29
  /**
27
30
  * Plugins that threw during installation, paired with the caught error.
28
31
  */
@@ -40,39 +43,26 @@ type BuildOutput = {
40
43
  sources: Map<string, string>
41
44
  }
42
45
 
43
- /**
44
- * Intermediate result returned by {@link setup} and accepted by {@link safeBuild}.
45
- */
46
46
  type SetupResult = {
47
- events: AsyncEventEmitter<KubbEvents>
47
+ hooks: AsyncEventEmitter<KubbHooks>
48
48
  driver: PluginDriver
49
49
  sources: Map<string, string>
50
50
  config: Config
51
51
  storage: Storage | null
52
52
  }
53
53
 
54
- /**
55
- * Initializes all Kubb infrastructure for a build without executing any plugins.
56
- *
57
- * - Validates the input path (when applicable).
58
- * - Applies config defaults (`root`, `output.*`, `devtools`).
59
- * - Runs the adapter (if configured) to produce the universal `InputNode`.
60
- * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
61
- *
62
- * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
63
- * via the `overrides` argument to reuse the same infrastructure across multiple runs.
64
- */
65
- export async function setup(options: BuildOptions): Promise<SetupResult> {
66
- const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
54
+ async function setup(options: BuildOptions): Promise<SetupResult> {
55
+ const { config: userConfig } = options
56
+ const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
67
57
 
68
58
  const sources: Map<string, string> = new Map<string, string>()
69
59
  const diagnosticInfo = getDiagnosticInfo()
70
60
 
71
61
  if (Array.isArray(userConfig.input)) {
72
- await events.emit('warn', 'This feature is still under development — use with caution')
62
+ await hooks.emit('kubb:warn', 'This feature is still under development — use with caution')
73
63
  }
74
64
 
75
- await events.emit('debug', {
65
+ await hooks.emit('kubb:debug', {
76
66
  date: new Date(),
77
67
  logs: [
78
68
  'Configuration:',
@@ -95,7 +85,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
95
85
  if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
96
86
  await exists(userConfig.input.path)
97
87
 
98
- await events.emit('debug', {
88
+ await hooks.emit('kubb:debug', {
99
89
  date: new Date(),
100
90
  logs: [`✓ Input file validated: ${userConfig.input.path}`],
101
91
  })
@@ -118,8 +108,8 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
118
108
  }
119
109
 
120
110
  const config: Config = {
121
- root: userConfig.root || process.cwd(),
122
111
  ...userConfig,
112
+ root: userConfig.root || process.cwd(),
123
113
  parsers: userConfig.parsers ?? [],
124
114
  adapter: userConfig.adapter,
125
115
  output: {
@@ -135,17 +125,13 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
135
125
  ...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
136
126
  }
137
127
  : undefined,
138
- plugins: userConfig.plugins as Config['plugins'],
128
+ plugins: userConfig.plugins as unknown as Config['plugins'],
139
129
  }
140
130
 
141
- // write: false is the explicit dry-run opt-out; otherwise use the provided
142
- // storage or fall back to fsStorage (backwards-compatible default).
143
- // Storage keys are the absolute file.path values so fsStorage() resolves
144
- // them correctly regardless of the current working directory.
145
131
  const storage: Storage | null = config.output.write === false ? null : (config.output.storage ?? fsStorage())
146
132
 
147
133
  if (config.output.clean) {
148
- await events.emit('debug', {
134
+ await hooks.emit('kubb:debug', {
149
135
  date: new Date(),
150
136
  logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
151
137
  })
@@ -153,19 +139,17 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
153
139
  }
154
140
 
155
141
  const driver = new PluginDriver(config, {
156
- events,
142
+ hooks,
157
143
  concurrency: DEFAULT_CONCURRENCY,
158
144
  })
159
145
 
160
- // Run the adapter to produce the universal InputNode.
161
-
162
146
  const adapter = config.adapter
163
147
  if (!adapter) {
164
148
  throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
165
149
  }
166
150
  const source = inputToAdapterSource(config)
167
151
 
168
- await events.emit('debug', {
152
+ await hooks.emit('kubb:debug', {
169
153
  date: new Date(),
170
154
  logs: [`Running adapter: ${adapter.name}`],
171
155
  })
@@ -173,7 +157,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
173
157
  driver.adapter = adapter
174
158
  driver.inputNode = await adapter.parse(source)
175
159
 
176
- await events.emit('debug', {
160
+ await hooks.emit('kubb:debug', {
177
161
  date: new Date(),
178
162
  logs: [
179
163
  `✓ Adapter '${adapter.name}' resolved InputNode`,
@@ -184,51 +168,16 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
184
168
 
185
169
  return {
186
170
  config,
187
- events,
171
+ hooks,
188
172
  driver,
189
173
  sources,
190
174
  storage,
191
175
  }
192
176
  }
193
177
 
194
- /**
195
- * Runs a full Kubb build and throws on any error or plugin failure.
196
- *
197
- * Internally delegates to {@link safeBuild} and rethrows collected errors.
198
- * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
199
- */
200
- export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
201
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
202
-
203
- if (error) {
204
- throw error
205
- }
206
-
207
- if (failedPlugins.size > 0) {
208
- const errors = [...failedPlugins].map(({ error }) => error)
209
-
210
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
211
- }
212
-
213
- return {
214
- failedPlugins,
215
- files,
216
- driver,
217
- pluginTimings,
218
- error: undefined,
219
- sources,
220
- }
221
- }
222
-
223
178
  /**
224
179
  * Walks the AST and dispatches nodes to a plugin's direct AST hooks
225
180
  * (`schema`, `operation`, `operations`).
226
- *
227
- * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
228
- * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
229
- * - Return values are handled via `applyHookResult`: React elements are rendered,
230
- * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
231
- * - Barrel files are generated automatically when `output.barrelType` is set.
232
181
  */
233
182
  async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
234
183
  const { adapter, inputNode, resolver, driver } = context
@@ -238,59 +187,87 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
238
187
  throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
239
188
  }
240
189
 
190
+ function resolveRenderer(gen: Generator<any>): RendererFactory | undefined {
191
+ return gen.renderer === null ? undefined : (gen.renderer ?? plugin.renderer ?? context.config.renderer)
192
+ }
193
+
194
+ const generators = plugin.generators ?? []
241
195
  const collectedOperations: Array<OperationNode> = []
242
196
 
197
+ const baseGeneratorContext = context as GeneratorContext
198
+ const generatorContext = {
199
+ ...baseGeneratorContext,
200
+ resolver: driver.getResolver(plugin.name),
201
+ }
202
+
243
203
  await walk(inputNode, {
244
204
  depth: 'shallow',
245
205
  async schema(node) {
246
- if (!plugin.schema) return
247
206
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
248
207
  const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
249
208
  if (options === null) return
250
- const result = await plugin.schema.call(context, transformedNode, options)
251
209
 
252
- await applyHookResult(result, driver)
210
+ const ctx = { ...generatorContext, options }
211
+
212
+ for (const gen of generators) {
213
+ if (!gen.schema) continue
214
+ const result = await gen.schema(transformedNode, ctx)
215
+ await applyHookResult(result, driver, resolveRenderer(gen))
216
+ }
217
+
218
+ await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
253
219
  },
254
220
  async operation(node) {
255
221
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
256
222
  const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
257
223
  if (options !== null) {
258
224
  collectedOperations.push(transformedNode)
259
- if (plugin.operation) {
260
- const result = await plugin.operation.call(context, transformedNode, options)
261
- await applyHookResult(result, driver)
225
+
226
+ const ctx = { ...generatorContext, options }
227
+
228
+ for (const gen of generators) {
229
+ if (!gen.operation) continue
230
+ const result = await gen.operation(transformedNode, ctx)
231
+ await applyHookResult(result, driver, resolveRenderer(gen))
262
232
  }
233
+
234
+ await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
263
235
  }
264
236
  },
265
237
  })
266
238
 
267
- if (plugin.operations && collectedOperations.length > 0) {
268
- const result = await plugin.operations.call(context, collectedOperations, plugin.options)
239
+ if (collectedOperations.length > 0) {
240
+ const ctx = { ...generatorContext, options: plugin.options }
269
241
 
270
- await applyHookResult(result, driver)
242
+ for (const gen of generators) {
243
+ if (!gen.operations) continue
244
+ const result = await gen.operations(collectedOperations, ctx)
245
+ await applyHookResult(result, driver, resolveRenderer(gen))
246
+ }
247
+
248
+ await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
271
249
  }
272
250
  }
273
251
 
274
- /**
275
- * Runs a full Kubb build and captures errors instead of throwing.
276
- *
277
- * - Installs each plugin in order, recording failures in `failedPlugins`.
278
- * - Generates the root barrel file when `output.barrelType` is set.
279
- * - Writes all files through the driver's FileManager and FileProcessor.
280
- *
281
- * Returns a {@link BuildOutput} even on failure — inspect `error` and
282
- * `failedPlugins` to determine whether the build succeeded.
283
- */
284
- export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
285
- const setupResult = overrides ? overrides : await setup(options)
286
- const { driver, events, sources, storage } = setupResult
252
+ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
253
+ const { driver, hooks, sources, storage } = setupResult
287
254
 
288
255
  const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
289
- // in ms
290
256
  const pluginTimings = new Map<string, number>()
291
257
  const config = driver.config
292
258
 
293
259
  try {
260
+ await driver.emitSetupHooks()
261
+
262
+ if (driver.adapter && driver.inputNode) {
263
+ await hooks.emit('kubb:build:start', {
264
+ config,
265
+ adapter: driver.adapter,
266
+ inputNode: driver.inputNode,
267
+ getPlugin: (name) => driver.getPlugin(name),
268
+ })
269
+ }
270
+
294
271
  for (const plugin of driver.plugins.values()) {
295
272
  const context = driver.getContext(plugin)
296
273
  const hrStart = process.hrtime()
@@ -300,18 +277,16 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
300
277
  try {
301
278
  const timestamp = new Date()
302
279
 
303
- await events.emit('plugin:start', plugin)
280
+ await hooks.emit('kubb:plugin:start', plugin)
304
281
 
305
- await events.emit('debug', {
282
+ await hooks.emit('kubb:debug', {
306
283
  date: timestamp,
307
284
  logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
308
285
  })
309
286
 
310
- // Call buildStart() for any custom plugin logic
311
287
  await plugin.buildStart.call(context)
312
288
 
313
- // Dispatch schema/operation/operations hooks (direct hooks or composed via composeGenerators)
314
- if (plugin.schema || plugin.operation || plugin.operations) {
289
+ if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
315
290
  await runPluginAstHooks(plugin, context)
316
291
  }
317
292
 
@@ -328,9 +303,9 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
328
303
  const duration = getElapsedMs(hrStart)
329
304
  pluginTimings.set(plugin.name, duration)
330
305
 
331
- await events.emit('plugin:end', plugin, { duration, success: true })
306
+ await hooks.emit('kubb:plugin:end', plugin, { duration, success: true })
332
307
 
333
- await events.emit('debug', {
308
+ await hooks.emit('kubb:debug', {
334
309
  date: new Date(),
335
310
  logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
336
311
  })
@@ -339,13 +314,13 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
339
314
  const errorTimestamp = new Date()
340
315
  const duration = getElapsedMs(hrStart)
341
316
 
342
- await events.emit('plugin:end', plugin, {
317
+ await hooks.emit('kubb:plugin:end', plugin, {
343
318
  duration,
344
319
  success: false,
345
320
  error,
346
321
  })
347
322
 
348
- await events.emit('debug', {
323
+ await hooks.emit('kubb:debug', {
349
324
  date: errorTimestamp,
350
325
  logs: [
351
326
  '✗ Plugin start failed',
@@ -365,7 +340,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
365
340
  const rootPath = resolve(root, config.output.path, BARREL_FILENAME)
366
341
  const rootDir = dirname(rootPath)
367
342
 
368
- await events.emit('debug', {
343
+ await hooks.emit('kubb:debug', {
369
344
  date: new Date(),
370
345
  logs: ['Generating barrel file', ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}`],
371
346
  })
@@ -374,7 +349,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
374
349
  return file.sources.some((source) => source.isIndexable)
375
350
  })
376
351
 
377
- await events.emit('debug', {
352
+ await hooks.emit('kubb:debug', {
378
353
  date: new Date(),
379
354
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`],
380
355
  })
@@ -395,7 +370,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
395
370
 
396
371
  driver.fileManager.upsert(rootFile)
397
372
 
398
- await events.emit('debug', {
373
+ await hooks.emit('kubb:debug', {
399
374
  date: new Date(),
400
375
  logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`],
401
376
  })
@@ -403,7 +378,6 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
403
378
 
404
379
  const files = driver.fileManager.files
405
380
 
406
- // Build a parsers map from config.parsers
407
381
  const parsersMap = new Map<FileNode['extname'], Parser>()
408
382
  for (const parser of config.parsers) {
409
383
  if (parser.extNames) {
@@ -415,7 +389,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
415
389
 
416
390
  const fileProcessor = new FileProcessor()
417
391
 
418
- await events.emit('debug', {
392
+ await hooks.emit('kubb:debug', {
419
393
  date: new Date(),
420
394
  logs: [`Writing ${files.length} files...`],
421
395
  })
@@ -424,10 +398,10 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
424
398
  parsers: parsersMap,
425
399
  extension: config.output.extension,
426
400
  onStart: async (processingFiles) => {
427
- await events.emit('files:processing:start', processingFiles)
401
+ await hooks.emit('kubb:files:processing:start', processingFiles)
428
402
  },
429
403
  onUpdate: async ({ file, source, processed, total, percentage }) => {
430
- await events.emit('file:processing:update', {
404
+ await hooks.emit('kubb:file:processing:update', {
431
405
  file,
432
406
  source,
433
407
  processed,
@@ -436,22 +410,19 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
436
410
  config,
437
411
  })
438
412
  if (source) {
439
- // Use the absolute file.path as the storage key so fsStorage resolves
440
- // it correctly regardless of the current working directory.
441
413
  await storage?.setItem(file.path, source)
442
414
  sources.set(file.path, source)
443
415
  }
444
416
  },
445
417
  onEnd: async (processedFiles) => {
446
- await events.emit('files:processing:end', processedFiles)
447
- await events.emit('debug', {
418
+ await hooks.emit('kubb:files:processing:end', processedFiles)
419
+ await hooks.emit('kubb:debug', {
448
420
  date: new Date(),
449
421
  logs: [`✓ File write process completed for ${processedFiles.length} files`],
450
422
  })
451
423
  },
452
424
  })
453
425
 
454
- // Call buildEnd() on each plugin after all files are written
455
426
  for (const plugin of driver.plugins.values()) {
456
427
  if (plugin.buildEnd) {
457
428
  const context = driver.getContext(plugin)
@@ -459,6 +430,12 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
459
430
  }
460
431
  }
461
432
 
433
+ await hooks.emit('kubb:build:end', {
434
+ files,
435
+ config,
436
+ outputDir: resolve(config.root, config.output.path),
437
+ })
438
+
462
439
  return {
463
440
  failedPlugins,
464
441
  files,
@@ -475,6 +452,31 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
475
452
  error: error as Error,
476
453
  sources,
477
454
  }
455
+ } finally {
456
+ driver.dispose()
457
+ }
458
+ }
459
+
460
+ async function build(setupResult: SetupResult): Promise<BuildOutput> {
461
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult)
462
+
463
+ if (error) {
464
+ throw error
465
+ }
466
+
467
+ if (failedPlugins.size > 0) {
468
+ const errors = [...failedPlugins].map(({ error }) => error)
469
+
470
+ throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
471
+ }
472
+
473
+ return {
474
+ failedPlugins,
475
+ files,
476
+ driver,
477
+ pluginTimings,
478
+ error: undefined,
479
+ sources,
478
480
  }
479
481
  }
480
482
 
@@ -524,10 +526,6 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
524
526
  })
525
527
  }
526
528
 
527
- /**
528
- * Maps the resolved `Config['input']` shape into an `AdapterSource` that
529
- * the adapter's `parse()` can consume.
530
- */
531
529
  function inputToAdapterSource(config: Config): AdapterSource {
532
530
  if (Array.isArray(config.input)) {
533
531
  return {
@@ -547,3 +545,63 @@ function inputToAdapterSource(config: Config): AdapterSource {
547
545
  const resolved = resolve(config.root, config.input.path)
548
546
  return { type: 'path', path: resolved }
549
547
  }
548
+
549
+ type KubbOptions = {
550
+ config: Config
551
+ hooks?: AsyncEventEmitter<KubbHooks>
552
+ }
553
+
554
+ /**
555
+ * Creates a Kubb instance bound to a single config entry.
556
+ *
557
+ * The instance holds shared state (`hooks`, `sources`, `driver`, `config`) across the
558
+ * `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
559
+ * calling `setup()` or `build()`.
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * const kubb = createKubb({ config })
564
+ *
565
+ * kubb.hooks.on('kubb:plugin:end', (plugin, { duration }) => {
566
+ * console.log(`${plugin.name} completed in ${duration}ms`)
567
+ * })
568
+ *
569
+ * const { files, failedPlugins } = await kubb.safeBuild()
570
+ * ```
571
+ */
572
+ export function createKubb(options: KubbOptions): Kubb {
573
+ const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
574
+ let setupResult: SetupResult | undefined
575
+
576
+ const instance: Kubb = {
577
+ get hooks() {
578
+ return hooks
579
+ },
580
+ get sources() {
581
+ return setupResult?.sources ?? new Map()
582
+ },
583
+ get driver() {
584
+ return setupResult?.driver
585
+ },
586
+ get config() {
587
+ return setupResult?.config
588
+ },
589
+ async setup() {
590
+ setupResult = await setup({ config: options.config, hooks })
591
+ },
592
+ async build() {
593
+ if (!setupResult) {
594
+ await instance.setup()
595
+ }
596
+ return build(setupResult!)
597
+ },
598
+ async safeBuild() {
599
+ if (!setupResult) {
600
+ await instance.setup()
601
+ }
602
+ return safeBuild(setupResult!)
603
+ },
604
+ }
605
+
606
+ return instance
607
+ }
@@ -22,6 +22,7 @@ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (opt
22
22
  * // instantiate
23
23
  * const plugin = myPlugin({ output: { path: 'src/gen' } })
24
24
  * ```
25
+ * @deprecated use definePlugin instead
25
26
  */
26
27
  export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
27
28
  build: PluginBuilder<T>,
@@ -0,0 +1,57 @@
1
+ import type { FileNode } from '@kubb/ast'
2
+
3
+ /**
4
+ * Minimal interface any Kubb renderer must satisfy.
5
+ *
6
+ * The generic `TElement` is the type of the element the renderer accepts —
7
+ * e.g. `KubbReactElement` for `@kubb/renderer-jsx`, or a custom type for
8
+ * your own renderer. Defaults to `unknown` so that generators which do not
9
+ * care about the element type continue to work without specifying it.
10
+ *
11
+ * This allows core to drive rendering without a hard dependency on
12
+ * `@kubb/renderer-jsx` or any specific renderer implementation.
13
+ */
14
+ export type Renderer<TElement = unknown> = {
15
+ render(element: TElement): Promise<void>
16
+ unmount(error?: Error | number | null): void
17
+ readonly files: Array<FileNode>
18
+ }
19
+
20
+ /**
21
+ * A factory function that produces a fresh {@link Renderer} per render.
22
+ *
23
+ * Generators use this to declare which renderer handles their output.
24
+ */
25
+ export type RendererFactory<TElement = unknown> = () => Renderer<TElement>
26
+
27
+ /**
28
+ * Creates a renderer factory for use in generator definitions.
29
+ *
30
+ * Wrap your renderer factory function with this helper to register it as the
31
+ * renderer for a generator. Core will call this factory once per render cycle
32
+ * to obtain a fresh renderer instance.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // packages/renderer-jsx/src/index.ts
37
+ * export const jsxRenderer = createRenderer(() => {
38
+ * const runtime = new Runtime()
39
+ * return {
40
+ * async render(element) { await runtime.render(element) },
41
+ * get files() { return runtime.nodes },
42
+ * unmount(error) { runtime.unmount(error) },
43
+ * }
44
+ * })
45
+ *
46
+ * // packages/plugin-zod/src/generators/zodGenerator.tsx
47
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
48
+ * export const zodGenerator = defineGenerator<PluginZod>({
49
+ * name: 'zod',
50
+ * renderer: jsxRenderer,
51
+ * schema(node, options) { return <File ...>...</File> },
52
+ * })
53
+ * ```
54
+ */
55
+ export function createRenderer<TElement = unknown>(factory: RendererFactory<TElement>): RendererFactory<TElement> {
56
+ return factory
57
+ }