@kubb/core 5.0.0-alpha.42 → 5.0.0-alpha.44

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.
@@ -1,12 +1,10 @@
1
- import { basename, extname, resolve } from 'node:path'
2
- import { performance } from 'node:perf_hooks'
1
+ import { extname, resolve } from 'node:path'
3
2
  import type { AsyncEventEmitter } from '@internals/utils'
4
- import { isPromiseRejectedResult, transformReservedWord } from '@internals/utils'
5
- import type { FileNode, InputNode } from '@kubb/ast'
3
+ import type { FileNode, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
6
4
  import { createFile } from '@kubb/ast'
7
5
  import { DEFAULT_STUDIO_URL } from './constants.ts'
8
6
  import type { Generator } from './defineGenerator.ts'
9
- import { type HookStylePlugin, isHookStylePlugin } from './definePlugin.ts'
7
+ import type { Plugin } from './definePlugin.ts'
10
8
  import { defineResolver } from './defineResolver.ts'
11
9
  import { openInStudio as openInStudioFn } from './devtools.ts'
12
10
  import { FileManager } from './FileManager.ts'
@@ -16,66 +14,20 @@ import type {
16
14
  Adapter,
17
15
  Config,
18
16
  DevtoolsOptions,
19
- Group,
17
+ GeneratorContext,
20
18
  KubbHooks,
21
19
  KubbPluginSetupContext,
22
- Output,
23
- Plugin,
24
- PluginContext,
20
+ NormalizedPlugin,
25
21
  PluginFactoryOptions,
26
- PluginLifecycle,
27
- PluginLifecycleHooks,
28
- PluginParameter,
29
- PluginWithLifeCycle,
30
- ResolveNameParams,
31
- ResolvePathParams,
32
22
  Resolver,
33
23
  } from './types.ts'
34
- import { hookFirst, hookParallel, hookSeq } from './utils/executeStrategies.ts'
35
-
36
- type RequiredPluginLifecycle = Required<PluginLifecycle>
37
-
38
- /**
39
- * Hook dispatch strategy used by the `PluginDriver`.
40
- *
41
- * - `hookFirst` — stops at the first non-null result.
42
- * - `hookForPlugin` — calls only the matching plugin.
43
- * - `hookParallel` — calls all plugins concurrently.
44
- * - `hookSeq` — calls all plugins in order, threading the result.
45
- */
46
- export type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookSeq'
47
-
48
- type ParseResult<H extends PluginLifecycleHooks> = RequiredPluginLifecycle[H]
49
-
50
- type SafeParseResult<H extends PluginLifecycleHooks, Result = ReturnType<ParseResult<H>>> = {
51
- result: Result
52
- plugin: Plugin
53
- }
54
24
 
55
25
  // inspired by: https://github.com/rollup/rollup/blob/master/src/utils/PluginDriver.ts#
56
26
 
57
27
  type Options = {
58
- hooks?: AsyncEventEmitter<KubbHooks>
59
- /**
60
- * @default Number.POSITIVE_INFINITY
61
- */
62
- concurrency?: number
63
- }
64
-
65
- /**
66
- * Parameters accepted by `PluginDriver.getFile` to resolve a generated file descriptor.
67
- */
68
- export type GetFileOptions<TOptions = object> = {
69
- name: string
70
- mode?: 'single' | 'split'
71
- extname: FileNode['extname']
72
- pluginName: string
73
- options?: TOptions
28
+ hooks: AsyncEventEmitter<KubbHooks>
74
29
  }
75
30
 
76
-
77
- const hookFirstNullCheck = (state: unknown) => !!(state as SafeParseResult<'resolveName'> | null)?.result
78
-
79
31
  export class PluginDriver {
80
32
  readonly config: Config
81
33
  readonly options: Options
@@ -94,7 +46,7 @@ export class PluginDriver {
94
46
  return 'split'
95
47
  }
96
48
  return extname(fileOrFolder) ? 'single' : 'split'
97
- }
49
+ }
98
50
 
99
51
  /**
100
52
  * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
@@ -111,7 +63,7 @@ export class PluginDriver {
111
63
  */
112
64
  readonly fileManager = new FileManager()
113
65
 
114
- readonly plugins = new Map<string, Plugin>()
66
+ readonly plugins = new Map<string, NormalizedPlugin>()
115
67
 
116
68
  /**
117
69
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
@@ -124,17 +76,9 @@ export class PluginDriver {
124
76
 
125
77
  constructor(config: Config, options: Options) {
126
78
  this.config = config
127
- this.options = {
128
- ...options,
129
- hooks: options.hooks,
130
- }
79
+ this.options = options
131
80
  config.plugins
132
- .map((rawPlugin) => {
133
- if (isHookStylePlugin(rawPlugin)) {
134
- return this.#normalizeHookStylePlugin(rawPlugin as HookStylePlugin)
135
- }
136
- return { ...rawPlugin, buildStart: rawPlugin.buildStart ?? (() => {}), buildEnd: rawPlugin.buildEnd ?? (() => {}) } as unknown as Plugin
137
- })
81
+ .map((rawPlugin) => this.#normalizePlugin(rawPlugin as Plugin))
138
82
  .filter((plugin) => {
139
83
  if (typeof plugin.apply === 'function') {
140
84
  return plugin.apply(config)
@@ -152,53 +96,20 @@ export class PluginDriver {
152
96
  }
153
97
 
154
98
  get hooks() {
155
- if (!this.options.hooks) {
156
- throw new Error('hooks are not defined')
157
- }
158
99
  return this.options.hooks
159
100
  }
160
101
 
161
102
  /**
162
- * Creates a `Plugin`-compatible object from a hook-style plugin and registers
103
+ * Creates an `NormalizedPlugin` from a hook-style plugin and registers
163
104
  * its lifecycle handlers on the `AsyncEventEmitter`.
164
- *
165
- * The normalized plugin has an empty `buildStart` — generators registered via
166
- * `addGenerator()` in `kubb:plugin:setup` are stored on `normalizedPlugin.generators`
167
- * and used by `runPluginAstHooks` during the build.
168
105
  */
169
- #normalizeHookStylePlugin(hookPlugin: HookStylePlugin): Plugin {
170
- const generators: Plugin['generators'] = []
171
- const driver = this
172
- // The options shape is the minimal struct required by Plugin. Hook-style plugins
173
- // use generators registered via addGenerator() and resolvers set via setResolver().
174
- // `inject` and `resolver` are required by the Plugin type but are irrelevant for hook-style
175
- // plugins: inject is a no-op and resolver is set dynamically via setResolver() in kubb:plugin:setup.
176
- //
177
- // `resolveName` and `resolvePath` bridge the legacy PluginDriver.resolveName/resolvePath
178
- // lifecycle so that other plugins calling `driver.resolveName({ pluginName })` or
179
- // `driver.getFile({ pluginName })` still get correct results from hook-style plugins.
106
+ #normalizePlugin(hookPlugin: Plugin): NormalizedPlugin {
180
107
  const normalizedPlugin = {
181
108
  name: hookPlugin.name,
182
109
  dependencies: hookPlugin.dependencies,
183
110
  options: { output: { path: '.' }, exclude: [], override: [] },
184
- generators,
185
- inject: () => undefined,
186
- resolveName(name: string, type?: ResolveNameParams['type']) {
187
- const resolver = driver.getResolver(hookPlugin.name)
188
- return resolver.default(name, type)
189
- },
190
- resolvePath(baseName: FileNode['baseName'], pathMode?: 'single' | 'split', resolveOptions?: Record<string, unknown>) {
191
- const resolver = driver.getResolver(hookPlugin.name)
192
- const opts = normalizedPlugin.options as Record<string, unknown>
193
- const group = resolveOptions?.group as Record<string, string> | undefined
194
- return resolver.resolvePath(
195
- { baseName, pathMode, tag: group?.tag, path: group?.path },
196
- { root: resolve(driver.config.root, driver.config.output.path), output: opts.output as Output, group: opts.group as Group | undefined },
197
- )
198
- },
199
- buildStart() {},
200
- buildEnd() {},
201
- } as unknown as Plugin
111
+ } as unknown as NormalizedPlugin
112
+
202
113
  this.registerPluginHooks(hookPlugin, normalizedPlugin)
203
114
  return normalizedPlugin
204
115
  }
@@ -215,8 +126,10 @@ export class PluginDriver {
215
126
  *
216
127
  * External tooling can subscribe to any of these events via `hooks.on(...)` to observe
217
128
  * the plugin lifecycle without modifying plugin behavior.
129
+ *
130
+ * @internal
218
131
  */
219
- registerPluginHooks(hookPlugin: HookStylePlugin, normalizedPlugin: Plugin): void {
132
+ registerPluginHooks(hookPlugin: Plugin, normalizedPlugin: NormalizedPlugin): void {
220
133
  const { hooks } = hookPlugin
221
134
 
222
135
  // kubb:plugin:setup gets special treatment: the globally emitted context is wrapped with
@@ -242,15 +155,8 @@ export class PluginDriver {
242
155
  setOptions: (opts) => {
243
156
  normalizedPlugin.options = { ...normalizedPlugin.options, ...opts }
244
157
  },
245
- injectFile: (file) => {
246
- const fileNode = createFile({
247
- baseName: file.baseName,
248
- path: file.path,
249
- sources: file.sources ?? [],
250
- imports: [],
251
- exports: [],
252
- })
253
- this.fileManager.add(fileNode)
158
+ injectFile: ({ sources = [], ...rest }) => {
159
+ this.fileManager.add(createFile({ imports: [], exports: [], sources, ...rest }))
254
160
  },
255
161
  }
256
162
  return hooks['kubb:plugin:setup']!(pluginCtx)
@@ -263,6 +169,7 @@ export class PluginDriver {
263
169
  // All other hooks are registered as direct pass-through listeners on the shared emitter.
264
170
  for (const [event, handler] of Object.entries(hooks) as Array<[keyof KubbHooks, ((...args: never[]) => void | Promise<void>) | undefined]>) {
265
171
  if (event === 'kubb:plugin:setup' || !handler) continue
172
+
266
173
  this.hooks.on(event, handler as never)
267
174
  this.#trackHookListener(event, handler as (...args: never[]) => void | Promise<void>)
268
175
  }
@@ -275,16 +182,17 @@ export class PluginDriver {
275
182
  * Call this once from `safeBuild` before the plugin execution loop begins.
276
183
  */
277
184
  async emitSetupHooks(): Promise<void> {
185
+ const noop = () => {}
278
186
  await this.hooks.emit('kubb:plugin:setup', {
279
187
  config: this.config,
280
- addGenerator: () => {},
281
- setResolver: () => {},
282
- setTransformer: () => {},
283
- setRenderer: () => {},
284
- setOptions: () => {},
285
- injectFile: () => {},
286
- updateConfig: () => {},
287
188
  options: {},
189
+ addGenerator: noop,
190
+ setResolver: noop,
191
+ setTransformer: noop,
192
+ setRenderer: noop,
193
+ setOptions: noop,
194
+ injectFile: noop,
195
+ updateConfig: noop,
288
196
  })
289
197
  }
290
198
 
@@ -309,7 +217,7 @@ export class PluginDriver {
309
217
  }
310
218
 
311
219
  if (gen.schema) {
312
- const schemaHandler = async (node: Parameters<NonNullable<typeof gen.schema>>[0], ctx: Parameters<NonNullable<typeof gen.schema>>[1]) => {
220
+ const schemaHandler = async (node: SchemaNode, ctx: GeneratorContext) => {
313
221
  if (ctx.plugin.name !== pluginName) return
314
222
  const result = await gen.schema!(node, ctx)
315
223
  await applyHookResult(result, this, resolveRenderer())
@@ -320,7 +228,7 @@ export class PluginDriver {
320
228
  }
321
229
 
322
230
  if (gen.operation) {
323
- const operationHandler = async (node: Parameters<NonNullable<typeof gen.operation>>[0], ctx: Parameters<NonNullable<typeof gen.operation>>[1]) => {
231
+ const operationHandler = async (node: OperationNode, ctx: GeneratorContext) => {
324
232
  if (ctx.plugin.name !== pluginName) return
325
233
  const result = await gen.operation!(node, ctx)
326
234
  await applyHookResult(result, this, resolveRenderer())
@@ -331,7 +239,7 @@ export class PluginDriver {
331
239
  }
332
240
 
333
241
  if (gen.operations) {
334
- const operationsHandler = async (nodes: Parameters<NonNullable<typeof gen.operations>>[0], ctx: Parameters<NonNullable<typeof gen.operations>>[1]) => {
242
+ const operationsHandler = async (nodes: Array<OperationNode>, ctx: GeneratorContext) => {
335
243
  if (ctx.plugin.name !== pluginName) return
336
244
  const result = await gen.operations!(nodes, ctx)
337
245
  await applyHookResult(result, this, resolveRenderer())
@@ -355,6 +263,12 @@ export class PluginDriver {
355
263
  return this.#pluginsWithEventGenerators.has(pluginName)
356
264
  }
357
265
 
266
+ /**
267
+ * Unregisters all plugin lifecycle listeners from the shared event emitter.
268
+ * Called at the end of a build to prevent listener leaks across repeated builds.
269
+ *
270
+ * @internal
271
+ */
358
272
  dispose(): void {
359
273
  for (const [event, handlers] of this.#hookListeners) {
360
274
  for (const handler of handlers) {
@@ -388,33 +302,32 @@ export class PluginDriver {
388
302
  return resolver
389
303
  }
390
304
 
305
+ /**
306
+ * Merges `partial` with the plugin's default resolver and stores the result.
307
+ * Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
308
+ * get the up-to-date resolver without going through `getResolver()`.
309
+ */
391
310
  setPluginResolver(pluginName: string, partial: Partial<Resolver>): void {
392
311
  const defaultResolver = this.#createDefaultResolver(pluginName)
393
312
  const merged = { ...defaultResolver, ...partial }
394
313
  this.#resolvers.set(pluginName, merged)
395
- // Mirror the resolved resolver onto the plugin so that consumers using
396
- // `getPlugin(name).resolver` get the correct resolver without going through getResolver().
397
314
  const plugin = this.plugins.get(pluginName)
398
315
  if (plugin) {
399
316
  plugin.resolver = merged
400
317
  }
401
318
  }
402
319
 
320
+ /**
321
+ * Returns the resolver for the given plugin.
322
+ *
323
+ * Resolution order: dynamic resolver set via `setPluginResolver` → static resolver on the
324
+ * plugin → lazily created default resolver (identity name, no path transforms).
325
+ */
403
326
  getResolver(pluginName: string): Resolver {
404
- const dynamicResolver = this.#resolvers.get(pluginName)
405
- if (dynamicResolver) {
406
- return dynamicResolver
407
- }
408
-
409
- const pluginResolver = this.plugins.get(pluginName)?.resolver
410
- if (pluginResolver) {
411
- return pluginResolver
412
- }
413
-
414
- return this.#createDefaultResolver(pluginName)
327
+ return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#createDefaultResolver(pluginName)
415
328
  }
416
329
 
417
- getContext<TOptions extends PluginFactoryOptions>(plugin: Plugin<TOptions>): PluginContext<TOptions> & Record<string, unknown> {
330
+ getContext<TOptions extends PluginFactoryOptions>(plugin: NormalizedPlugin<TOptions>): GeneratorContext<TOptions> & Record<string, unknown> {
418
331
  const driver = this
419
332
 
420
333
  const baseContext = {
@@ -429,7 +342,7 @@ export class PluginDriver {
429
342
  plugin,
430
343
  getPlugin: driver.getPlugin.bind(driver),
431
344
  requirePlugin: driver.requirePlugin.bind(driver),
432
- driver: driver,
345
+ driver,
433
346
  addFile: async (...files: Array<FileNode>) => {
434
347
  driver.fileManager.add(...files)
435
348
  },
@@ -476,326 +389,15 @@ export class PluginDriver {
476
389
 
477
390
  return openInStudioFn(driver.inputNode, studioUrl, options)
478
391
  },
479
- } as unknown as PluginContext<TOptions>
480
-
481
- let mergedExtras: Record<string, unknown> = {}
482
-
483
- for (const p of this.plugins.values()) {
484
- if (typeof p.inject === 'function') {
485
- const result = (p.inject as (this: PluginContext) => unknown).call(baseContext as unknown as PluginContext)
486
- if (result !== null && typeof result === 'object') {
487
- mergedExtras = { ...mergedExtras, ...(result as Record<string, unknown>) }
488
- }
489
- }
490
- }
491
-
492
- return {
493
- ...baseContext,
494
- ...mergedExtras,
495
- }
496
- }
497
- /**
498
- * @deprecated use resolvers context instead
499
- */
500
- getFile<TOptions = object>({ name, mode, extname, pluginName, options }: GetFileOptions<TOptions>): FileNode<{ pluginName: string }> {
501
- const resolvedName = mode ? (mode === 'single' ? '' : this.resolveName({ name, pluginName, type: 'file' })) : name
502
-
503
- const path = this.resolvePath({
504
- baseName: `${resolvedName}${extname}` as const,
505
- mode,
506
- pluginName,
507
- options,
508
- })
509
-
510
- if (!path) {
511
- throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`)
512
- }
513
-
514
- return createFile<{ pluginName: string }>({
515
- path,
516
- baseName: basename(path) as `${string}.${string}`,
517
- meta: {
518
- pluginName,
519
- },
520
- sources: [],
521
- imports: [],
522
- exports: [],
523
- })
524
- }
525
-
526
- /**
527
- * @deprecated use resolvers context instead
528
- */
529
- resolvePath = <TOptions = object>(params: ResolvePathParams<TOptions>): string => {
530
- const root = resolve(this.config.root, this.config.output.path)
531
- const defaultPath = resolve(root, params.baseName)
532
-
533
- if (params.pluginName) {
534
- const paths = this.hookForPluginSync({
535
- pluginName: params.pluginName,
536
- hookName: 'resolvePath',
537
- parameters: [params.baseName, params.mode, params.options as object],
538
- })
539
-
540
- return paths?.at(0) || defaultPath
541
- }
542
-
543
- const firstResult = this.hookFirstSync({
544
- hookName: 'resolvePath',
545
- parameters: [params.baseName, params.mode, params.options as object],
546
- })
547
-
548
- return firstResult?.result || defaultPath
549
- }
550
- /**
551
- * @deprecated use resolvers context instead
552
- */
553
- resolveName = (params: ResolveNameParams): string => {
554
- if (params.pluginName) {
555
- const names = this.hookForPluginSync({
556
- pluginName: params.pluginName,
557
- hookName: 'resolveName',
558
- parameters: [params.name.trim(), params.type],
559
- })
560
-
561
- return transformReservedWord(names?.at(0) ?? params.name)
562
- }
563
-
564
- const name = this.hookFirstSync({
565
- hookName: 'resolveName',
566
- parameters: [params.name.trim(), params.type],
567
- })?.result
568
-
569
- return transformReservedWord(name ?? params.name)
570
- }
571
-
572
- /**
573
- * Run a specific hookName for plugin x.
574
- */
575
- async hookForPlugin<H extends PluginLifecycleHooks>({
576
- pluginName,
577
- hookName,
578
- parameters,
579
- }: {
580
- pluginName: string
581
- hookName: H
582
- parameters: PluginParameter<H>
583
- }): Promise<Array<ReturnType<ParseResult<H>> | null>> {
584
- const plugin = this.plugins.get(pluginName)
585
-
586
- if (!plugin) {
587
- return [null]
588
- }
589
-
590
- this.hooks.emit('kubb:plugins:hook:progress:start', {
591
- hookName,
592
- plugins: [plugin],
593
- })
594
-
595
- const result = await this.#execute<H>({
596
- strategy: 'hookFirst',
597
- hookName,
598
- parameters,
599
- plugin,
600
- })
601
-
602
- this.hooks.emit('kubb:plugins:hook:progress:end', { hookName })
392
+ } as unknown as GeneratorContext<TOptions>
603
393
 
604
- return [result]
605
- }
606
-
607
- /**
608
- * Run a specific hookName for plugin x.
609
- */
610
- hookForPluginSync<H extends PluginLifecycleHooks>({
611
- pluginName,
612
- hookName,
613
- parameters,
614
- }: {
615
- pluginName: string
616
- hookName: H
617
- parameters: PluginParameter<H>
618
- }): Array<ReturnType<ParseResult<H>>> | null {
619
- const plugin = this.plugins.get(pluginName)
620
-
621
- if (!plugin) {
622
- return null
623
- }
624
-
625
- const result = this.#executeSync<H>({
626
- strategy: 'hookFirst',
627
- hookName,
628
- parameters,
629
- plugin,
630
- })
631
-
632
- return result !== null ? [result] : []
633
- }
634
-
635
- /**
636
- * Returns the first non-null result.
637
- */
638
- async hookFirst<H extends PluginLifecycleHooks>({
639
- hookName,
640
- parameters,
641
- skipped,
642
- }: {
643
- hookName: H
644
- parameters: PluginParameter<H>
645
- skipped?: ReadonlySet<Plugin> | null
646
- }): Promise<SafeParseResult<H>> {
647
- const plugins: Array<Plugin> = []
648
- for (const plugin of this.plugins.values()) {
649
- if (hookName in plugin && (skipped ? !skipped.has(plugin) : true)) plugins.push(plugin)
650
- }
651
-
652
- this.hooks.emit('kubb:plugins:hook:progress:start', { hookName, plugins })
653
-
654
- const promises = plugins.map((plugin) => {
655
- return async () => {
656
- const value = await this.#execute<H>({
657
- strategy: 'hookFirst',
658
- hookName,
659
- parameters,
660
- plugin,
661
- })
662
-
663
- return Promise.resolve({
664
- plugin,
665
- result: value,
666
- } as SafeParseResult<H>)
667
- }
668
- })
669
-
670
- const result = await hookFirst(promises, hookFirstNullCheck)
671
-
672
- this.hooks.emit('kubb:plugins:hook:progress:end', { hookName })
673
-
674
- return result
675
- }
676
-
677
- /**
678
- * Returns the first non-null result.
679
- */
680
- hookFirstSync<H extends PluginLifecycleHooks>({
681
- hookName,
682
- parameters,
683
- skipped,
684
- }: {
685
- hookName: H
686
- parameters: PluginParameter<H>
687
- skipped?: ReadonlySet<Plugin> | null
688
- }): SafeParseResult<H> | null {
689
- let parseResult: SafeParseResult<H> | null = null
690
-
691
- for (const plugin of this.plugins.values()) {
692
- if (!(hookName in plugin)) continue
693
- if (skipped?.has(plugin)) continue
694
-
695
- parseResult = {
696
- result: this.#executeSync<H>({
697
- strategy: 'hookFirst',
698
- hookName,
699
- parameters,
700
- plugin,
701
- }),
702
- plugin,
703
- } as SafeParseResult<H>
704
-
705
- if (parseResult.result != null) break
706
- }
707
-
708
- return parseResult
709
- }
710
-
711
- /**
712
- * Runs all plugins in parallel based on `this.plugin` order and `dependencies` settings.
713
- */
714
- async hookParallel<H extends PluginLifecycleHooks, TOutput = void>({
715
- hookName,
716
- parameters,
717
- }: {
718
- hookName: H
719
- parameters?: Parameters<RequiredPluginLifecycle[H]> | undefined
720
- }): Promise<Awaited<TOutput>[]> {
721
- const plugins: Array<Plugin> = []
722
- for (const plugin of this.plugins.values()) {
723
- if (hookName in plugin) plugins.push(plugin)
724
- }
725
- this.hooks.emit('kubb:plugins:hook:progress:start', { hookName, plugins })
726
-
727
- const pluginStartTimes = new Map<Plugin, number>()
728
-
729
- const promises = plugins.map((plugin) => {
730
- return () => {
731
- pluginStartTimes.set(plugin, performance.now())
732
- return this.#execute({
733
- strategy: 'hookParallel',
734
- hookName,
735
- parameters,
736
- plugin,
737
- }) as Promise<TOutput>
738
- }
739
- })
740
-
741
- const results = await hookParallel(promises, this.options.concurrency)
742
-
743
- results.forEach((result, index) => {
744
- if (isPromiseRejectedResult<Error>(result)) {
745
- const plugin = plugins[index]
746
-
747
- if (plugin) {
748
- const startTime = pluginStartTimes.get(plugin) ?? performance.now()
749
- this.hooks.emit('kubb:error', result.reason, {
750
- plugin,
751
- hookName,
752
- strategy: 'hookParallel',
753
- duration: Math.round(performance.now() - startTime),
754
- parameters,
755
- })
756
- }
757
- }
758
- })
759
-
760
- this.hooks.emit('kubb:plugins:hook:progress:end', { hookName })
761
-
762
- return results.reduce((acc, result) => {
763
- if (result.status === 'fulfilled') {
764
- acc.push(result.value)
765
- }
766
- return acc
767
- }, [] as Awaited<TOutput>[])
768
- }
769
-
770
- /**
771
- * Execute a lifecycle hook sequentially for all plugins that implement it.
772
- */
773
- async hookSeq<H extends PluginLifecycleHooks>({ hookName, parameters }: { hookName: H; parameters?: PluginParameter<H> }): Promise<void> {
774
- const plugins: Array<Plugin> = []
775
- for (const plugin of this.plugins.values()) {
776
- if (hookName in plugin) plugins.push(plugin)
777
- }
778
- this.hooks.emit('kubb:plugins:hook:progress:start', { hookName, plugins })
779
-
780
- const promises = plugins.map((plugin) => {
781
- return () =>
782
- this.#execute({
783
- strategy: 'hookSeq',
784
- hookName,
785
- parameters,
786
- plugin,
787
- })
788
- })
789
-
790
- await hookSeq(promises)
791
-
792
- this.hooks.emit('kubb:plugins:hook:progress:end', { hookName })
394
+ return baseContext
793
395
  }
794
396
 
795
397
  getPlugin<TName extends keyof Kubb.PluginRegistry>(pluginName: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
796
398
  getPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions> | undefined
797
399
  getPlugin(pluginName: string): Plugin | undefined {
798
- return this.plugins.get(pluginName) as Plugin | undefined
400
+ return this.plugins.get(pluginName)
799
401
  }
800
402
 
801
403
  /**
@@ -810,132 +412,4 @@ export class PluginDriver {
810
412
  }
811
413
  return plugin
812
414
  }
813
-
814
- /**
815
- * Emit hook-processing completion metadata after a plugin hook resolves.
816
- */
817
- #emitProcessingEnd<H extends PluginLifecycleHooks>({
818
- startTime,
819
- output,
820
- strategy,
821
- hookName,
822
- plugin,
823
- parameters,
824
- }: {
825
- startTime: number
826
- output: unknown
827
- strategy: Strategy
828
- hookName: H
829
- plugin: PluginWithLifeCycle
830
- parameters: unknown[] | undefined
831
- }): void {
832
- this.hooks.emit('kubb:plugins:hook:processing:end', {
833
- duration: Math.round(performance.now() - startTime),
834
- parameters,
835
- output,
836
- strategy,
837
- hookName,
838
- plugin,
839
- })
840
- }
841
-
842
- // Implementation signature
843
- #execute<H extends PluginLifecycleHooks>({
844
- strategy,
845
- hookName,
846
- parameters,
847
- plugin,
848
- }: {
849
- strategy: Strategy
850
- hookName: H
851
- parameters: unknown[] | undefined
852
- plugin: PluginWithLifeCycle
853
- }): Promise<ReturnType<ParseResult<H>> | null> | null {
854
- const hook = plugin[hookName]
855
-
856
- if (!hook) {
857
- return null
858
- }
859
-
860
- this.hooks.emit('kubb:plugins:hook:processing:start', {
861
- strategy,
862
- hookName,
863
- parameters,
864
- plugin,
865
- })
866
-
867
- const startTime = performance.now()
868
-
869
- const task = (async () => {
870
- try {
871
- const output =
872
- typeof hook === 'function' ? await Promise.resolve((hook as (...args: unknown[]) => unknown).apply(this.getContext(plugin), parameters ?? [])) : hook
873
-
874
- this.#emitProcessingEnd({ startTime, output, strategy, hookName, plugin, parameters })
875
-
876
- return output as ReturnType<ParseResult<H>>
877
- } catch (error) {
878
- this.hooks.emit('kubb:error', error as Error, {
879
- plugin,
880
- hookName,
881
- strategy,
882
- duration: Math.round(performance.now() - startTime),
883
- })
884
-
885
- return null
886
- }
887
- })()
888
-
889
- return task
890
- }
891
-
892
- /**
893
- * Execute a plugin lifecycle hook synchronously and return its output.
894
- */
895
- #executeSync<H extends PluginLifecycleHooks>({
896
- strategy,
897
- hookName,
898
- parameters,
899
- plugin,
900
- }: {
901
- strategy: Strategy
902
- hookName: H
903
- parameters: PluginParameter<H>
904
- plugin: PluginWithLifeCycle
905
- }): ReturnType<ParseResult<H>> | null {
906
- const hook = plugin[hookName]
907
-
908
- if (!hook) {
909
- return null
910
- }
911
-
912
- this.hooks.emit('kubb:plugins:hook:processing:start', {
913
- strategy,
914
- hookName,
915
- parameters,
916
- plugin,
917
- })
918
-
919
- const startTime = performance.now()
920
-
921
- try {
922
- const output =
923
- typeof hook === 'function'
924
- ? ((hook as (...args: unknown[]) => unknown).apply(this.getContext(plugin), parameters) as ReturnType<ParseResult<H>>)
925
- : (hook as ReturnType<ParseResult<H>>)
926
-
927
- this.#emitProcessingEnd({ startTime, output, strategy, hookName, plugin, parameters })
928
-
929
- return output
930
- } catch (error) {
931
- this.hooks.emit('kubb:error', error as Error, {
932
- plugin,
933
- hookName,
934
- strategy,
935
- duration: Math.round(performance.now() - startTime),
936
- })
937
-
938
- return null
939
- }
940
- }
941
415
  }