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