@kubb/core 5.0.0-alpha.30 → 5.0.0-alpha.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/build.ts CHANGED
@@ -1,13 +1,10 @@
1
- import { dirname, relative, resolve } from 'node:path'
1
+ import { dirname, resolve } from 'node:path'
2
2
  import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
- import { transform, walk } from '@kubb/ast'
4
- import type { OperationNode } from '@kubb/ast/types'
5
- import type { FabricFile, Fabric as FabricType } from '@kubb/fabric-core/types'
6
- import { createFabric } from '@kubb/react-fabric'
7
- import { typescriptParser } from '@kubb/react-fabric/parsers'
8
- import { fsPlugin } from '@kubb/react-fabric/plugins'
9
- import { isInputPath } from './config.ts'
3
+ import { createExport, createFile, transform, walk } from '@kubb/ast'
4
+ import type { ExportNode, FileNode, OperationNode } from '@kubb/ast/types'
10
5
  import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
6
+ import type { Parser } from './defineParser.ts'
7
+ import { FileProcessor } from './FileProcessor.ts'
11
8
  import { PluginDriver } from './PluginDriver.ts'
12
9
  import { applyHookResult } from './renderNode.tsx'
13
10
  import { fsStorage } from './storages/fsStorage.ts'
@@ -15,6 +12,7 @@ import type { AdapterSource, Config, KubbEvents, Plugin, PluginContext, Storage,
15
12
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
16
13
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
17
14
  import { getBarrelFiles } from './utils/getBarrelFiles.ts'
15
+ import { isInputPath } from './utils/isInputPath.ts'
18
16
 
19
17
  type BuildOptions = {
20
18
  config: UserConfig
@@ -29,8 +27,7 @@ type BuildOutput = {
29
27
  * Plugins that threw during installation, paired with the caught error.
30
28
  */
31
29
  failedPlugins: Set<{ plugin: Plugin; error: Error }>
32
- fabric: FabricType
33
- files: Array<FabricFile.ResolvedFile>
30
+ files: Array<FileNode>
34
31
  driver: PluginDriver
35
32
  /**
36
33
  * Elapsed time in milliseconds for each plugin, keyed by plugin name.
@@ -40,7 +37,7 @@ type BuildOutput = {
40
37
  /**
41
38
  * Raw generated source, keyed by absolute file path.
42
39
  */
43
- sources: Map<FabricFile.Path, string>
40
+ sources: Map<string, string>
44
41
  }
45
42
 
46
43
  /**
@@ -48,9 +45,10 @@ type BuildOutput = {
48
45
  */
49
46
  type SetupResult = {
50
47
  events: AsyncEventEmitter<KubbEvents>
51
- fabric: FabricType
52
48
  driver: PluginDriver
53
- sources: Map<FabricFile.Path, string>
49
+ sources: Map<string, string>
50
+ config: Config
51
+ storage: Storage | null
54
52
  }
55
53
 
56
54
  /**
@@ -59,7 +57,8 @@ type SetupResult = {
59
57
  * - Validates the input path (when applicable).
60
58
  * - Applies config defaults (`root`, `output.*`, `devtools`).
61
59
  * - Creates the Fabric instance and wires storage, format, and lint hooks.
62
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
60
+ * - Runs the adapter (if configured) to produce the universal `InputNode`.
61
+ * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
63
62
  *
64
63
  * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
65
64
  * via the `overrides` argument to reuse the same infrastructure across multiple runs.
@@ -67,7 +66,7 @@ type SetupResult = {
67
66
  export async function setup(options: BuildOptions): Promise<SetupResult> {
68
67
  const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
69
68
 
70
- const sources: Map<FabricFile.Path, string> = new Map<FabricFile.Path, string>()
69
+ const sources: Map<string, string> = new Map<string, string>()
71
70
  const diagnosticInfo = getDiagnosticInfo()
72
71
 
73
72
  if (Array.isArray(userConfig.input)) {
@@ -115,9 +114,15 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
115
114
  }
116
115
  }
117
116
 
118
- const definedConfig: Config = {
117
+ if (!userConfig.adapter) {
118
+ throw new Error('Adapter should be defined')
119
+ }
120
+
121
+ const config: Config = {
119
122
  root: userConfig.root || process.cwd(),
120
123
  ...userConfig,
124
+ parsers: userConfig.parsers ?? [],
125
+ adapter: userConfig.adapter,
121
126
  output: {
122
127
  write: true,
123
128
  barrelType: 'named',
@@ -136,96 +141,54 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
136
141
 
137
142
  // write: false is the explicit dry-run opt-out; otherwise use the provided
138
143
  // storage or fall back to fsStorage (backwards-compatible default).
139
- // Keys are root-relative (e.g. `src/gen/api/getPets.ts`) so fsStorage()
140
- // needs no configuration it resolves them against process.cwd().
141
- const storage: Storage | null = definedConfig.output.write === false ? null : (definedConfig.output.storage ?? fsStorage())
144
+ // Storage keys are the absolute file.path values so fsStorage() resolves
145
+ // them correctly regardless of the current working directory.
146
+ const storage: Storage | null = config.output.write === false ? null : (config.output.storage ?? fsStorage())
142
147
 
143
- if (definedConfig.output.clean) {
148
+ if (config.output.clean) {
144
149
  await events.emit('debug', {
145
150
  date: new Date(),
146
- logs: ['Cleaning output directories', ` • Output: ${definedConfig.output.path}`],
151
+ logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
147
152
  })
148
- await storage?.clear(resolve(definedConfig.root, definedConfig.output.path))
153
+ await storage?.clear(resolve(config.root, config.output.path))
149
154
  }
150
155
 
151
- const fabric = createFabric()
152
- fabric.use(fsPlugin)
153
- fabric.use(typescriptParser)
154
-
155
- fabric.context.on('files:processing:start', (files) => {
156
- events.emit('files:processing:start', files)
157
- events.emit('debug', {
158
- date: new Date(),
159
- logs: [`Writing ${files.length} files...`],
160
- })
156
+ const driver = new PluginDriver(config, {
157
+ events,
158
+ concurrency: DEFAULT_CONCURRENCY,
161
159
  })
162
160
 
163
- fabric.context.on('file:processing:update', async (params) => {
164
- const { file, source } = params
165
- await events.emit('file:processing:update', {
166
- ...params,
167
- config: definedConfig,
168
- source,
169
- })
161
+ // Run the adapter to produce the universal InputNode.
170
162
 
171
- if (source) {
172
- // Key is root-relative so it's meaningful for any backend (fs, S3, Redis…)
173
- const key = relative(resolve(definedConfig.root), file.path)
174
- await storage?.setItem(key, source)
175
- sources.set(file.path, source)
176
- }
177
- })
163
+ const adapter = config.adapter
164
+ if (!adapter) {
165
+ throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
166
+ }
167
+ const source = inputToAdapterSource(config)
178
168
 
179
- fabric.context.on('files:processing:end', async (files) => {
180
- await events.emit('files:processing:end', files)
181
- await events.emit('debug', {
182
- date: new Date(),
183
- logs: [`✓ File write process completed for ${files.length} files`],
184
- })
169
+ await events.emit('debug', {
170
+ date: new Date(),
171
+ logs: [`Running adapter: ${adapter.name}`],
185
172
  })
186
173
 
174
+ driver.adapter = adapter
175
+ driver.inputNode = await adapter.parse(source)
176
+
187
177
  await events.emit('debug', {
188
178
  date: new Date(),
189
179
  logs: [
190
- ' Fabric initialized',
191
- ` • Storage: ${storage ? storage.name : 'disabled (dry-run)'}`,
192
- ` • Barrel type: ${definedConfig.output.barrelType || 'none'}`,
180
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
181
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
182
+ ` • Operations: ${driver.inputNode.operations.length}`,
193
183
  ],
194
184
  })
195
185
 
196
- const pluginDriver = new PluginDriver(definedConfig, {
197
- fabric,
198
- events,
199
- concurrency: DEFAULT_CONCURRENCY,
200
- })
201
-
202
- // Run the adapter (if provided) to produce the universal RootNode
203
- if (definedConfig.adapter) {
204
- const source = inputToAdapterSource(definedConfig)
205
-
206
- await events.emit('debug', {
207
- date: new Date(),
208
- logs: [`Running adapter: ${definedConfig.adapter.name}`],
209
- })
210
-
211
- pluginDriver.adapter = definedConfig.adapter
212
- pluginDriver.rootNode = await definedConfig.adapter.parse(source)
213
-
214
- await events.emit('debug', {
215
- date: new Date(),
216
- logs: [
217
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
218
- ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
219
- ` • Operations: ${pluginDriver.rootNode.operations.length}`,
220
- ],
221
- })
222
- }
223
-
224
186
  return {
187
+ config,
225
188
  events,
226
- fabric,
227
- driver: pluginDriver,
189
+ driver,
228
190
  sources,
191
+ storage,
229
192
  }
230
193
  }
231
194
 
@@ -236,7 +199,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
236
199
  * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
237
200
  */
238
201
  export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
239
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
202
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
240
203
 
241
204
  if (error) {
242
205
  throw error
@@ -250,7 +213,6 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
250
213
 
251
214
  return {
252
215
  failedPlugins,
253
- fabric,
254
216
  files,
255
217
  driver,
256
218
  pluginTimings,
@@ -266,20 +228,20 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
266
228
  * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
267
229
  * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
268
230
  * - Return values are handled via `applyHookResult`: React elements are rendered,
269
- * `FabricFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
231
+ * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
270
232
  * - Barrel files are generated automatically when `output.barrelType` is set.
271
233
  */
272
234
  async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
273
- const { adapter, rootNode, resolver, fabric } = context
235
+ const { adapter, inputNode, resolver, driver } = context
274
236
  const { exclude, include, override } = plugin.options
275
237
 
276
- if (!adapter || !rootNode) {
238
+ if (!adapter || !inputNode) {
277
239
  throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
278
240
  }
279
241
 
280
242
  const collectedOperations: Array<OperationNode> = []
281
243
 
282
- await walk(rootNode, {
244
+ await walk(inputNode, {
283
245
  depth: 'shallow',
284
246
  async schema(node) {
285
247
  if (!plugin.schema) return
@@ -288,7 +250,7 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
288
250
  if (options === null) return
289
251
  const result = await plugin.schema.call(context, transformedNode, options)
290
252
 
291
- await applyHookResult(result, fabric)
253
+ await applyHookResult(result, driver)
292
254
  },
293
255
  async operation(node) {
294
256
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
@@ -297,7 +259,7 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
297
259
  collectedOperations.push(transformedNode)
298
260
  if (plugin.operation) {
299
261
  const result = await plugin.operation.call(context, transformedNode, options)
300
- await applyHookResult(result, fabric)
262
+ await applyHookResult(result, driver)
301
263
  }
302
264
  }
303
265
  },
@@ -306,7 +268,7 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
306
268
  if (plugin.operations && collectedOperations.length > 0) {
307
269
  const result = await plugin.operations.call(context, collectedOperations, plugin.options)
308
270
 
309
- await applyHookResult(result, fabric)
271
+ await applyHookResult(result, driver)
310
272
  }
311
273
  }
312
274
 
@@ -315,13 +277,14 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
315
277
  *
316
278
  * - Installs each plugin in order, recording failures in `failedPlugins`.
317
279
  * - Generates the root barrel file when `output.barrelType` is set.
318
- * - Writes all files through Fabric.
280
+ * - Writes all files through the driver's FileManager and FileProcessor.
319
281
  *
320
282
  * Returns a {@link BuildOutput} even on failure — inspect `error` and
321
283
  * `failedPlugins` to determine whether the build succeeded.
322
284
  */
323
285
  export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
324
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options)
286
+ const setupResult = overrides ? overrides : await setup(options)
287
+ const { driver, events, sources, storage } = setupResult
325
288
 
326
289
  const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
327
290
  // in ms
@@ -354,7 +317,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
354
317
  }
355
318
 
356
319
  if (output) {
357
- const barrelFiles = await getBarrelFiles(fabric.files, {
320
+ const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
358
321
  type: output.barrelType ?? 'named',
359
322
  root,
360
323
  output,
@@ -408,7 +371,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
408
371
  logs: ['Generating barrel file', ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}`],
409
372
  })
410
373
 
411
- const barrelFiles = fabric.files.filter((file) => {
374
+ const barrelFiles = driver.fileManager.files.filter((file) => {
412
375
  return file.sources.some((source) => source.isIndexable)
413
376
  })
414
377
 
@@ -417,21 +380,21 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
417
380
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`],
418
381
  })
419
382
 
420
- const existingBarrel = fabric.files.find((f) => f.path === rootPath)
383
+ const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath)
421
384
  const existingExports = new Set(
422
385
  existingBarrel?.exports?.flatMap((e) => (Array.isArray(e.name) ? e.name : [e.name])).filter((n): n is string => Boolean(n)) ?? [],
423
386
  )
424
387
 
425
- const rootFile: FabricFile.File = {
388
+ const rootFile = createFile<object>({
426
389
  path: rootPath,
427
390
  baseName: BARREL_FILENAME,
428
- exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }),
391
+ exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }).map((e) => createExport(e)),
429
392
  sources: [],
430
393
  imports: [],
431
394
  meta: {},
432
- }
395
+ })
433
396
 
434
- await fabric.upsertFile(rootFile)
397
+ driver.fileManager.upsert(rootFile)
435
398
 
436
399
  await events.emit('debug', {
437
400
  date: new Date(),
@@ -439,9 +402,55 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
439
402
  })
440
403
  }
441
404
 
442
- const files = [...fabric.files]
405
+ const files = driver.fileManager.files
406
+
407
+ // Build a parsers map from config.parsers
408
+ const parsersMap = new Map<FileNode['extname'], Parser>()
409
+ for (const parser of config.parsers) {
410
+ if (parser.extNames) {
411
+ for (const extname of parser.extNames) {
412
+ parsersMap.set(extname, parser)
413
+ }
414
+ }
415
+ }
443
416
 
444
- await fabric.write({ extension: config.output.extension })
417
+ const fileProcessor = new FileProcessor()
418
+
419
+ await events.emit('debug', {
420
+ date: new Date(),
421
+ logs: [`Writing ${files.length} files...`],
422
+ })
423
+
424
+ await fileProcessor.run(files, {
425
+ parsers: parsersMap,
426
+ extension: config.output.extension,
427
+ onStart: async (processingFiles) => {
428
+ await events.emit('files:processing:start', processingFiles)
429
+ },
430
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
431
+ await events.emit('file:processing:update', {
432
+ file,
433
+ source,
434
+ processed,
435
+ total,
436
+ percentage,
437
+ config,
438
+ })
439
+ if (source) {
440
+ // Use the absolute file.path as the storage key so fsStorage resolves
441
+ // it correctly regardless of the current working directory.
442
+ await storage?.setItem(file.path, source)
443
+ sources.set(file.path, source)
444
+ }
445
+ },
446
+ onEnd: async (processedFiles) => {
447
+ await events.emit('files:processing:end', processedFiles)
448
+ await events.emit('debug', {
449
+ date: new Date(),
450
+ logs: [`✓ File write process completed for ${processedFiles.length} files`],
451
+ })
452
+ },
453
+ })
445
454
 
446
455
  // Call buildEnd() on each plugin after all files are written
447
456
  for (const plugin of driver.plugins.values()) {
@@ -453,7 +462,6 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
453
462
 
454
463
  return {
455
464
  failedPlugins,
456
- fabric,
457
465
  files,
458
466
  driver,
459
467
  pluginTimings,
@@ -462,7 +470,6 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
462
470
  } catch (error) {
463
471
  return {
464
472
  failedPlugins,
465
- fabric,
466
473
  files: [],
467
474
  driver,
468
475
  pluginTimings,
@@ -473,14 +480,14 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
473
480
  }
474
481
 
475
482
  type BuildBarrelExportsParams = {
476
- barrelFiles: FabricFile.ResolvedFile[]
483
+ barrelFiles: FileNode[]
477
484
  rootDir: string
478
485
  existingExports: Set<string>
479
486
  config: Config
480
487
  driver: PluginDriver
481
488
  }
482
489
 
483
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }: BuildBarrelExportsParams): FabricFile.Export[] {
490
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }: BuildBarrelExportsParams): ExportNode[] {
484
491
  const pluginNameMap = new Map<string, Plugin>()
485
492
  for (const plugin of driver.plugins.values()) {
486
493
  pluginNameMap.set(plugin.name, plugin)
@@ -508,11 +515,11 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
508
515
  }
509
516
 
510
517
  return [
511
- {
518
+ createExport({
512
519
  name: exportName,
513
520
  path: getRelativePath(rootDir, file.path),
514
521
  isTypeOnly: config.output.barrelType === 'all' ? containsOnlyTypes : source.isTypeOnly,
515
- } satisfies FabricFile.Export,
522
+ }),
516
523
  ]
517
524
  })
518
525
  })
package/src/constants.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FabricFile } from '@kubb/fabric-core/types'
1
+ import type { FileNode } from '@kubb/ast/types'
2
2
 
3
3
  /**
4
4
  * Base URL for the Kubb Studio web app.
@@ -10,6 +10,11 @@ export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
10
10
  */
11
11
  export const DEFAULT_CONCURRENCY = 15
12
12
 
13
+ /**
14
+ * Maximum number of files processed in parallel by FileProcessor.
15
+ */
16
+ export const PARALLEL_CONCURRENCY_LIMIT = 100
17
+
13
18
  /**
14
19
  * File name used for generated barrel (index) files.
15
20
  */
@@ -23,7 +28,7 @@ export const DEFAULT_BANNER = 'simple' as const
23
28
  /**
24
29
  * Default file-extension mapping used when no explicit mapping is configured.
25
30
  */
26
- export const DEFAULT_EXTENSION: Record<FabricFile.Extname, FabricFile.Extname | ''> = { '.ts': '.ts' }
31
+ export const DEFAULT_EXTENSION: Record<FileNode['extname'], FileNode['extname'] | ''> = { '.ts': '.ts' }
27
32
 
28
33
  /**
29
34
  * Characters recognized as path separators on both POSIX and Windows.
@@ -1,5 +1,5 @@
1
1
  import type { PossiblePromise } from '@internals/utils'
2
- import type { InputPath, UserConfig } from './types.ts'
2
+ import type { UserConfig } from './types.ts'
3
3
 
4
4
  /**
5
5
  * CLI options derived from command-line flags.
@@ -42,16 +42,10 @@ export type ConfigInput = PossiblePromise<UserConfig | UserConfig[]> | ((cli: CL
42
42
  * root: 'src',
43
43
  * plugins: [myPlugin()],
44
44
  * }))
45
+ * @deprecated as of Kubb v5, @kubb/core will not expose `defineConfig` anymore. use the `kubb` package instead
45
46
  */
46
47
  export function defineConfig(config: (cli: CLIOptions) => PossiblePromise<UserConfig | UserConfig[]>): typeof config
47
48
  export function defineConfig(config: PossiblePromise<UserConfig | UserConfig[]>): typeof config
48
49
  export function defineConfig(config: ConfigInput): ConfigInput {
49
50
  return config
50
51
  }
51
-
52
- /**
53
- * Type guard to check if a given config has an `input.path`.
54
- */
55
- export function isInputPath(config: UserConfig | undefined): config is UserConfig<InputPath> {
56
- return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
57
- }
@@ -1,6 +1,5 @@
1
1
  import type { PossiblePromise } from '@internals/utils'
2
- import type { OperationNode, SchemaNode } from '@kubb/ast/types'
3
- import type { FabricFile } from '@kubb/fabric-core/types'
2
+ import type { FileNode, OperationNode, SchemaNode } from '@kubb/ast/types'
4
3
  import type { FabricReactNode } from '@kubb/react-fabric/types'
5
4
  import { applyHookResult } from './renderNode.tsx'
6
5
  import type { GeneratorContext, PluginFactoryOptions } from './types.ts'
@@ -10,10 +9,10 @@ export type { GeneratorContext } from './types.ts'
10
9
  /**
11
10
  * A generator is a named object with optional `schema`, `operation`, and `operations`
12
11
  * methods. Each method is called with `this = PluginContext` of the parent plugin,
13
- * giving full access to `this.config`, `this.resolver`, `this.adapter`, `this.fabric`,
12
+ * giving full access to `this.config`, `this.resolver`, `this.adapter`,
14
13
  * `this.driver`, etc.
15
14
  *
16
- * Return a React element, an array of `FabricFile.File`, or `void` to handle file
15
+ * Return a React element, an array of `FileNode`, or `void` to handle file
17
16
  * writing manually via `this.upsertFile`. Both React and core (non-React) generators
18
17
  * use the same method signatures — the return type determines how output is handled.
19
18
  *
@@ -36,32 +35,32 @@ export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptio
36
35
  name: string
37
36
  /**
38
37
  * Called for each schema node in the AST walk.
39
- * `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
38
+ * `this` is the parent plugin's context with `adapter` and `inputNode` guaranteed present.
40
39
  * `options` contains the per-node resolved options (after exclude/include/override).
41
40
  */
42
41
  schema?: (
43
42
  this: GeneratorContext<TOptions>,
44
43
  node: SchemaNode,
45
44
  options: TOptions['resolvedOptions'],
46
- ) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
45
+ ) => PossiblePromise<FabricReactNode | Array<FileNode> | void>
47
46
  /**
48
47
  * Called for each operation node in the AST walk.
49
- * `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
48
+ * `this` is the parent plugin's context with `adapter` and `inputNode` guaranteed present.
50
49
  */
51
50
  operation?: (
52
51
  this: GeneratorContext<TOptions>,
53
52
  node: OperationNode,
54
53
  options: TOptions['resolvedOptions'],
55
- ) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
54
+ ) => PossiblePromise<FabricReactNode | Array<FileNode> | void>
56
55
  /**
57
56
  * Called once after all operations have been walked.
58
- * `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
57
+ * `this` is the parent plugin's context with `adapter` and `inputNode` guaranteed present.
59
58
  */
60
59
  operations?: (
61
60
  this: GeneratorContext<TOptions>,
62
61
  nodes: Array<OperationNode>,
63
62
  options: TOptions['resolvedOptions'],
64
- ) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
63
+ ) => PossiblePromise<FabricReactNode | Array<FileNode> | void>
65
64
  }
66
65
 
67
66
  /**
@@ -103,7 +102,7 @@ export function mergeGenerators<TOptions extends PluginFactoryOptions = PluginFa
103
102
  if (!gen.schema) continue
104
103
  const result = await gen.schema.call(this, node, options)
105
104
 
106
- await applyHookResult(result, this.fabric)
105
+ await applyHookResult(result, this.driver)
107
106
  }
108
107
  },
109
108
  async operation(node, options) {
@@ -111,7 +110,7 @@ export function mergeGenerators<TOptions extends PluginFactoryOptions = PluginFa
111
110
  if (!gen.operation) continue
112
111
  const result = await gen.operation.call(this, node, options)
113
112
 
114
- await applyHookResult(result, this.fabric)
113
+ await applyHookResult(result, this.driver)
115
114
  }
116
115
  },
117
116
  async operations(nodes, options) {
@@ -119,7 +118,7 @@ export function mergeGenerators<TOptions extends PluginFactoryOptions = PluginFa
119
118
  if (!gen.operations) continue
120
119
  const result = await gen.operations.call(this, nodes, options)
121
120
 
122
- await applyHookResult(result, this.fabric)
121
+ await applyHookResult(result, this.driver)
123
122
  }
124
123
  },
125
124
  }
@@ -0,0 +1,57 @@
1
+ import type { FileNode } from '@kubb/ast/types'
2
+
3
+ type PrintOptions = {
4
+ extname?: FileNode['extname']
5
+ }
6
+
7
+ export type Parser<TMeta extends object = any> = {
8
+ name: string
9
+ type: 'parser'
10
+ /**
11
+ * File extensions this parser handles.
12
+ * Use `undefined` to create a catch-all fallback parser.
13
+ *
14
+ * @example ['.ts', '.js']
15
+ */
16
+ extNames: Array<FileNode['extname']> | undefined
17
+ /**
18
+ * @deprecated Will be removed once Fabric no longer requires it.
19
+ * @default () => {}
20
+ */
21
+ install(...args: unknown[]): void | Promise<void>
22
+ /**
23
+ * Convert a resolved file to a string.
24
+ */
25
+ parse(file: FileNode<TMeta>, options?: PrintOptions): Promise<string> | string
26
+ }
27
+
28
+ export type UserParser<TMeta extends object = any> = Omit<Parser<TMeta>, 'type' | 'install'> & {
29
+ install?(...args: unknown[]): void | Promise<void>
30
+ }
31
+
32
+ /**
33
+ * Defines a parser with type safety.
34
+ *
35
+ * Use this function to create parsers that transform generated files to strings
36
+ * based on their extension.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { defineParser } from '@kubb/core'
41
+ *
42
+ * export const jsonParser = defineParser({
43
+ * name: 'json',
44
+ * extNames: ['.json'],
45
+ * parse(file) {
46
+ * return file.sources.map((s) => s.value).join('\n')
47
+ * },
48
+ * })
49
+ * ```
50
+ */
51
+ export function defineParser<TMeta extends object = any>(parser: UserParser<TMeta>): Parser<TMeta> {
52
+ return {
53
+ install() {},
54
+ type: 'parser',
55
+ ...parser,
56
+ }
57
+ }