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

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 (54) hide show
  1. package/dist/PluginDriver-D110FoJ-.d.ts +1632 -0
  2. package/dist/hooks.cjs +12 -27
  3. package/dist/hooks.cjs.map +1 -1
  4. package/dist/hooks.d.ts +11 -36
  5. package/dist/hooks.js +13 -27
  6. package/dist/hooks.js.map +1 -1
  7. package/dist/index.cjs +1410 -823
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +597 -95
  10. package/dist/index.js +1391 -818
  11. package/dist/index.js.map +1 -1
  12. package/package.json +7 -7
  13. package/src/Kubb.ts +40 -58
  14. package/src/{PluginManager.ts → PluginDriver.ts} +165 -177
  15. package/src/build.ts +167 -44
  16. package/src/config.ts +9 -8
  17. package/src/constants.ts +40 -7
  18. package/src/createAdapter.ts +25 -0
  19. package/src/createPlugin.ts +30 -0
  20. package/src/createStorage.ts +58 -0
  21. package/src/defineGenerator.ts +126 -0
  22. package/src/defineLogger.ts +13 -3
  23. package/src/definePresets.ts +16 -0
  24. package/src/defineResolver.ts +457 -0
  25. package/src/hooks/index.ts +1 -6
  26. package/src/hooks/useDriver.ts +11 -0
  27. package/src/hooks/useMode.ts +5 -5
  28. package/src/hooks/usePlugin.ts +3 -3
  29. package/src/index.ts +18 -7
  30. package/src/renderNode.tsx +25 -0
  31. package/src/storages/fsStorage.ts +2 -2
  32. package/src/storages/memoryStorage.ts +2 -2
  33. package/src/types.ts +589 -52
  34. package/src/utils/FunctionParams.ts +2 -2
  35. package/src/utils/TreeNode.ts +45 -7
  36. package/src/utils/diagnostics.ts +4 -1
  37. package/src/utils/executeStrategies.ts +29 -10
  38. package/src/utils/formatters.ts +10 -21
  39. package/src/utils/getBarrelFiles.ts +83 -10
  40. package/src/utils/getConfigs.ts +8 -22
  41. package/src/utils/getPreset.ts +78 -0
  42. package/src/utils/linters.ts +23 -3
  43. package/src/utils/packageJSON.ts +76 -0
  44. package/dist/types-CiPWLv-5.d.ts +0 -1001
  45. package/src/BarrelManager.ts +0 -74
  46. package/src/PackageManager.ts +0 -180
  47. package/src/PromiseManager.ts +0 -40
  48. package/src/defineAdapter.ts +0 -22
  49. package/src/definePlugin.ts +0 -12
  50. package/src/defineStorage.ts +0 -56
  51. package/src/errors.ts +0 -1
  52. package/src/hooks/useKubb.ts +0 -22
  53. package/src/hooks/usePluginManager.ts +0 -11
  54. package/src/utils/getPlugins.ts +0 -23
package/src/build.ts CHANGED
@@ -1,45 +1,73 @@
1
1
  import { dirname, relative, resolve } from 'node:path'
2
- import { AsyncEventEmitter, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
- import type { KubbFile } from '@kubb/fabric-core/types'
4
- import type { Fabric } from '@kubb/react-fabric'
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'
5
6
  import { createFabric } from '@kubb/react-fabric'
6
7
  import { typescriptParser } from '@kubb/react-fabric/parsers'
7
8
  import { fsPlugin } from '@kubb/react-fabric/plugins'
8
9
  import { isInputPath } from './config.ts'
9
10
  import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
10
- import { BuildError } from './errors.ts'
11
- import { PluginManager } from './PluginManager.ts'
11
+ import { PluginDriver } from './PluginDriver.ts'
12
+ import { applyHookResult } from './renderNode.tsx'
12
13
  import { fsStorage } from './storages/fsStorage.ts'
13
- import type { AdapterSource, Config, DefineStorage, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
14
+ import type { AdapterSource, Config, KubbEvents, Plugin, PluginContext, Storage, UserConfig } from './types.ts'
14
15
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
15
16
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
17
+ import { getBarrelFiles } from './utils/getBarrelFiles.ts'
16
18
 
17
19
  type BuildOptions = {
18
20
  config: UserConfig
19
21
  events?: AsyncEventEmitter<KubbEvents>
20
22
  }
21
23
 
24
+ /**
25
+ * Full output produced by a successful or failed build.
26
+ */
22
27
  type BuildOutput = {
28
+ /**
29
+ * Plugins that threw during installation, paired with the caught error.
30
+ */
23
31
  failedPlugins: Set<{ plugin: Plugin; error: Error }>
24
- fabric: Fabric
25
- files: Array<KubbFile.ResolvedFile>
26
- pluginManager: PluginManager
32
+ fabric: FabricType
33
+ files: Array<FabricFile.ResolvedFile>
34
+ driver: PluginDriver
35
+ /**
36
+ * Elapsed time in milliseconds for each plugin, keyed by plugin name.
37
+ */
27
38
  pluginTimings: Map<string, number>
28
39
  error?: Error
29
- sources: Map<KubbFile.Path, string>
40
+ /**
41
+ * Raw generated source, keyed by absolute file path.
42
+ */
43
+ sources: Map<FabricFile.Path, string>
30
44
  }
31
45
 
46
+ /**
47
+ * Intermediate result returned by {@link setup} and accepted by {@link safeBuild}.
48
+ */
32
49
  type SetupResult = {
33
50
  events: AsyncEventEmitter<KubbEvents>
34
- fabric: Fabric
35
- pluginManager: PluginManager
36
- sources: Map<KubbFile.Path, string>
51
+ fabric: FabricType
52
+ driver: PluginDriver
53
+ sources: Map<FabricFile.Path, string>
37
54
  }
38
55
 
56
+ /**
57
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
58
+ *
59
+ * - Validates the input path (when applicable).
60
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
61
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
62
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
63
+ *
64
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
65
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
66
+ */
39
67
  export async function setup(options: BuildOptions): Promise<SetupResult> {
40
68
  const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
41
69
 
42
- const sources: Map<KubbFile.Path, string> = new Map<KubbFile.Path, string>()
70
+ const sources: Map<FabricFile.Path, string> = new Map<FabricFile.Path, string>()
43
71
  const diagnosticInfo = getDiagnosticInfo()
44
72
 
45
73
  if (Array.isArray(userConfig.input)) {
@@ -110,7 +138,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
110
138
  // storage or fall back to fsStorage (backwards-compatible default).
111
139
  // Keys are root-relative (e.g. `src/gen/api/getPets.ts`) so fsStorage()
112
140
  // needs no configuration — it resolves them against process.cwd().
113
- const storage: DefineStorage | null = definedConfig.output.write === false ? null : (definedConfig.output.storage ?? fsStorage())
141
+ const storage: Storage | null = definedConfig.output.write === false ? null : (definedConfig.output.storage ?? fsStorage())
114
142
 
115
143
  if (definedConfig.output.clean) {
116
144
  await events.emit('debug', {
@@ -165,7 +193,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
165
193
  ],
166
194
  })
167
195
 
168
- const pluginManager = new PluginManager(definedConfig, {
196
+ const pluginDriver = new PluginDriver(definedConfig, {
169
197
  fabric,
170
198
  events,
171
199
  concurrency: DEFAULT_CONCURRENCY,
@@ -180,15 +208,15 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
180
208
  logs: [`Running adapter: ${definedConfig.adapter.name}`],
181
209
  })
182
210
 
183
- pluginManager.adapter = definedConfig.adapter
184
- pluginManager.rootNode = await definedConfig.adapter.parse(source)
211
+ pluginDriver.adapter = definedConfig.adapter
212
+ pluginDriver.rootNode = await definedConfig.adapter.parse(source)
185
213
 
186
214
  await events.emit('debug', {
187
215
  date: new Date(),
188
216
  logs: [
189
217
  `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
190
- ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
191
- ` • Operations: ${pluginManager.rootNode.operations.length}`,
218
+ ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
219
+ ` • Operations: ${pluginDriver.rootNode.operations.length}`,
192
220
  ],
193
221
  })
194
222
  }
@@ -196,13 +224,19 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
196
224
  return {
197
225
  events,
198
226
  fabric,
199
- pluginManager,
227
+ driver: pluginDriver,
200
228
  sources,
201
229
  }
202
230
  }
203
231
 
232
+ /**
233
+ * Runs a full Kubb build and throws on any error or plugin failure.
234
+ *
235
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
236
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
237
+ */
204
238
  export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
205
- const { fabric, files, pluginManager, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
239
+ const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
206
240
 
207
241
  if (error) {
208
242
  throw error
@@ -218,27 +252,88 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
218
252
  failedPlugins,
219
253
  fabric,
220
254
  files,
221
- pluginManager,
255
+ driver,
222
256
  pluginTimings,
223
257
  error: undefined,
224
258
  sources,
225
259
  }
226
260
  }
227
261
 
262
+ /**
263
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
264
+ * (`schema`, `operation`, `operations`).
265
+ *
266
+ * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
267
+ * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
268
+ * - 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).
270
+ * - Barrel files are generated automatically when `output.barrelType` is set.
271
+ */
272
+ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
273
+ const { adapter, rootNode, resolver, fabric } = context
274
+ const { exclude, include, override } = plugin.options
275
+
276
+ if (!adapter || !rootNode) {
277
+ throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
278
+ }
279
+
280
+ const collectedOperations: Array<OperationNode> = []
281
+
282
+ await walk(rootNode, {
283
+ depth: 'shallow',
284
+ async schema(node) {
285
+ if (!plugin.schema) return
286
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
287
+ const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
288
+ if (options === null) return
289
+ const result = await plugin.schema.call(context, transformedNode, options)
290
+
291
+ await applyHookResult(result, fabric)
292
+ },
293
+ async operation(node) {
294
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
295
+ const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
296
+ if (options !== null) {
297
+ collectedOperations.push(transformedNode)
298
+ if (plugin.operation) {
299
+ const result = await plugin.operation.call(context, transformedNode, options)
300
+ await applyHookResult(result, fabric)
301
+ }
302
+ }
303
+ },
304
+ })
305
+
306
+ if (plugin.operations && collectedOperations.length > 0) {
307
+ const result = await plugin.operations.call(context, collectedOperations, plugin.options)
308
+
309
+ await applyHookResult(result, fabric)
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Runs a full Kubb build and captures errors instead of throwing.
315
+ *
316
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
317
+ * - Generates the root barrel file when `output.barrelType` is set.
318
+ * - Writes all files through Fabric.
319
+ *
320
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
321
+ * `failedPlugins` to determine whether the build succeeded.
322
+ */
228
323
  export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
229
- const { fabric, pluginManager, events, sources } = overrides ? overrides : await setup(options)
324
+ const { fabric, driver, events, sources } = overrides ? overrides : await setup(options)
230
325
 
231
326
  const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
232
327
  // in ms
233
328
  const pluginTimings = new Map<string, number>()
234
- const config = pluginManager.config
329
+ const config = driver.config
235
330
 
236
331
  try {
237
- for (const plugin of pluginManager.plugins) {
238
- const context = pluginManager.getContext(plugin)
332
+ for (const plugin of driver.plugins.values()) {
333
+ const context = driver.getContext(plugin)
239
334
  const hrStart = process.hrtime()
240
-
241
- const installer = plugin.install.bind(context)
335
+ const { output } = plugin.options ?? {}
336
+ const root = resolve(config.root, config.output.path)
242
337
 
243
338
  try {
244
339
  const timestamp = new Date()
@@ -247,10 +342,26 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
247
342
 
248
343
  await events.emit('debug', {
249
344
  date: timestamp,
250
- logs: ['Installing plugin...', ` • Plugin Name: ${plugin.name}`],
345
+ logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
251
346
  })
252
347
 
253
- await installer(context)
348
+ // Call buildStart() for any custom plugin logic
349
+ await plugin.buildStart.call(context)
350
+
351
+ // Dispatch schema/operation/operations hooks (direct hooks or composed via composeGenerators)
352
+ if (plugin.schema || plugin.operation || plugin.operations) {
353
+ await runPluginAstHooks(plugin, context)
354
+ }
355
+
356
+ if (output) {
357
+ const barrelFiles = await getBarrelFiles(fabric.files, {
358
+ type: output.barrelType ?? 'named',
359
+ root,
360
+ output,
361
+ meta: { pluginName: plugin.name },
362
+ })
363
+ await context.upsertFile(...barrelFiles)
364
+ }
254
365
 
255
366
  const duration = getElapsedMs(hrStart)
256
367
  pluginTimings.set(plugin.name, duration)
@@ -259,7 +370,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
259
370
 
260
371
  await events.emit('debug', {
261
372
  date: new Date(),
262
- logs: [`✓ Plugin installed successfully (${formatMs(duration)})`],
373
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
263
374
  })
264
375
  } catch (caughtError) {
265
376
  const error = caughtError as Error
@@ -275,7 +386,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
275
386
  await events.emit('debug', {
276
387
  date: errorTimestamp,
277
388
  logs: [
278
- '✗ Plugin installation failed',
389
+ '✗ Plugin start failed',
279
390
  ` • Plugin Name: ${plugin.name}`,
280
391
  ` • Error: ${error.constructor.name} - ${error.message}`,
281
392
  ' • Stack Trace:',
@@ -311,10 +422,10 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
311
422
  existingBarrel?.exports?.flatMap((e) => (Array.isArray(e.name) ? e.name : [e.name])).filter((n): n is string => Boolean(n)) ?? [],
312
423
  )
313
424
 
314
- const rootFile: KubbFile.File = {
425
+ const rootFile: FabricFile.File = {
315
426
  path: rootPath,
316
427
  baseName: BARREL_FILENAME,
317
- exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }),
428
+ exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }),
318
429
  sources: [],
319
430
  imports: [],
320
431
  meta: {},
@@ -332,11 +443,19 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
332
443
 
333
444
  await fabric.write({ extension: config.output.extension })
334
445
 
446
+ // Call buildEnd() on each plugin after all files are written
447
+ for (const plugin of driver.plugins.values()) {
448
+ if (plugin.buildEnd) {
449
+ const context = driver.getContext(plugin)
450
+ await plugin.buildEnd.call(context)
451
+ }
452
+ }
453
+
335
454
  return {
336
455
  failedPlugins,
337
456
  fabric,
338
457
  files,
339
- pluginManager,
458
+ driver,
340
459
  pluginTimings,
341
460
  sources,
342
461
  }
@@ -345,7 +464,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
345
464
  failedPlugins,
346
465
  fabric,
347
466
  files: [],
348
- pluginManager,
467
+ driver,
349
468
  pluginTimings,
350
469
  error: error as Error,
351
470
  sources,
@@ -354,16 +473,16 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
354
473
  }
355
474
 
356
475
  type BuildBarrelExportsParams = {
357
- barrelFiles: KubbFile.ResolvedFile[]
476
+ barrelFiles: FabricFile.ResolvedFile[]
358
477
  rootDir: string
359
478
  existingExports: Set<string>
360
479
  config: Config
361
- pluginManager: PluginManager
480
+ driver: PluginDriver
362
481
  }
363
482
 
364
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }: BuildBarrelExportsParams): KubbFile.Export[] {
483
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }: BuildBarrelExportsParams): FabricFile.Export[] {
365
484
  const pluginNameMap = new Map<string, Plugin>()
366
- for (const plugin of pluginManager.plugins) {
485
+ for (const plugin of driver.plugins.values()) {
367
486
  pluginNameMap.set(plugin.name, plugin)
368
487
  }
369
488
 
@@ -377,7 +496,7 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
377
496
 
378
497
  const meta = file.meta as FileMetaBase | undefined
379
498
  const plugin = meta?.pluginName ? pluginNameMap.get(meta.pluginName) : undefined
380
- const pluginOptions = plugin?.options as { output?: Output<unknown> } | undefined
499
+ const pluginOptions = plugin?.options
381
500
 
382
501
  if (!pluginOptions || pluginOptions.output?.barrelType === false) {
383
502
  return []
@@ -393,7 +512,7 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
393
512
  name: exportName,
394
513
  path: getRelativePath(rootDir, file.path),
395
514
  isTypeOnly: config.output.barrelType === 'all' ? containsOnlyTypes : source.isTypeOnly,
396
- } satisfies KubbFile.Export,
515
+ } satisfies FabricFile.Export,
397
516
  ]
398
517
  })
399
518
  })
@@ -407,7 +526,7 @@ function inputToAdapterSource(config: Config): AdapterSource {
407
526
  if (Array.isArray(config.input)) {
408
527
  return {
409
528
  type: 'paths',
410
- paths: config.input.map((i) => resolve(config.root, i.path)),
529
+ paths: config.input.map((i) => (new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))),
411
530
  }
412
531
  }
413
532
 
@@ -415,6 +534,10 @@ function inputToAdapterSource(config: Config): AdapterSource {
415
534
  return { type: 'data', data: config.input.data }
416
535
  }
417
536
 
537
+ if (new URLPath(config.input.path).isURL) {
538
+ return { type: 'path', path: config.input.path }
539
+ }
540
+
418
541
  const resolved = resolve(config.root, config.input.path)
419
542
  return { type: 'path', path: resolved }
420
543
  }
package/src/config.ts CHANGED
@@ -5,12 +5,14 @@ import type { InputPath, UserConfig } from './types.ts'
5
5
  * CLI options derived from command-line flags.
6
6
  */
7
7
  export type CLIOptions = {
8
- /** Path to `kubb.config.js` */
8
+ /**
9
+ * Path to `kubb.config.js`.
10
+ */
9
11
  config?: string
10
-
11
- /** Enable watch mode for input files */
12
+ /**
13
+ * Enable watch mode for input files.
14
+ */
12
15
  watch?: boolean
13
-
14
16
  /**
15
17
  * Logging verbosity for CLI usage.
16
18
  *
@@ -20,12 +22,11 @@ export type CLIOptions = {
20
22
  * @default 'silent'
21
23
  */
22
24
  logLevel?: 'silent' | 'info' | 'debug'
23
-
24
- /** Run Kubb with Bun */
25
- bun?: boolean
26
25
  }
27
26
 
28
- /** All accepted forms of a Kubb configuration. */
27
+ /**
28
+ * All accepted forms of a Kubb configuration.
29
+ */
29
30
  export type ConfigInput = PossiblePromise<UserConfig | UserConfig[]> | ((cli: CLIOptions) => PossiblePromise<UserConfig | UserConfig[]>)
30
31
 
31
32
  /**
package/src/constants.ts CHANGED
@@ -1,21 +1,40 @@
1
- import type { KubbFile } from '@kubb/fabric-core/types'
1
+ import type { FabricFile } from '@kubb/fabric-core/types'
2
2
 
3
+ /**
4
+ * Base URL for the Kubb Studio web app.
5
+ */
3
6
  export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
4
7
 
5
- export const CORE_PLUGIN_NAME = 'core' as const
6
-
7
- export const DEFAULT_MAX_LISTENERS = 100
8
-
8
+ /**
9
+ * Default number of plugins that may run concurrently during a build.
10
+ */
9
11
  export const DEFAULT_CONCURRENCY = 15
10
12
 
13
+ /**
14
+ * File name used for generated barrel (index) files.
15
+ */
11
16
  export const BARREL_FILENAME = 'index.ts' as const
12
17
 
18
+ /**
19
+ * Default banner style written at the top of every generated file.
20
+ */
13
21
  export const DEFAULT_BANNER = 'simple' as const
14
22
 
15
- export const DEFAULT_EXTENSION: Record<KubbFile.Extname, KubbFile.Extname | ''> = { '.ts': '.ts' }
23
+ /**
24
+ * Default file-extension mapping used when no explicit mapping is configured.
25
+ */
26
+ export const DEFAULT_EXTENSION: Record<FabricFile.Extname, FabricFile.Extname | ''> = { '.ts': '.ts' }
16
27
 
17
- export const PATH_SEPARATORS = ['/', '\\'] as const
28
+ /**
29
+ * Characters recognized as path separators on both POSIX and Windows.
30
+ */
31
+ export const PATH_SEPARATORS = new Set(['/', '\\'] as const)
18
32
 
33
+ /**
34
+ * Numeric log-level thresholds used internally to compare verbosity.
35
+ *
36
+ * Higher numbers are more verbose.
37
+ */
19
38
  export const logLevel = {
20
39
  silent: Number.NEGATIVE_INFINITY,
21
40
  error: 0,
@@ -25,6 +44,13 @@ export const logLevel = {
25
44
  debug: 5,
26
45
  } as const
27
46
 
47
+ /**
48
+ * CLI command descriptors for each supported linter.
49
+ *
50
+ * Each entry contains the executable `command`, an `args` factory that maps an
51
+ * output path to the correct argument list, and an `errorMessage` shown when
52
+ * the linter is not found.
53
+ */
28
54
  export const linters = {
29
55
  eslint: {
30
56
  command: 'eslint',
@@ -43,6 +69,13 @@ export const linters = {
43
69
  },
44
70
  } as const
45
71
 
72
+ /**
73
+ * CLI command descriptors for each supported code formatter.
74
+ *
75
+ * Each entry contains the executable `command`, an `args` factory that maps an
76
+ * output path to the correct argument list, and an `errorMessage` shown when
77
+ * the formatter is not found.
78
+ */
46
79
  export const formatters = {
47
80
  prettier: {
48
81
  command: 'prettier',
@@ -0,0 +1,25 @@
1
+ import type { Adapter, AdapterFactoryOptions } from './types.ts'
2
+
3
+ /**
4
+ * Builder type for an {@link Adapter} — takes options and returns the adapter instance.
5
+ */
6
+ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>
7
+
8
+ /**
9
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
10
+ *
11
+ * @example
12
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
13
+ * return {
14
+ * name: 'my-adapter',
15
+ * options,
16
+ * async parse(source) { ... },
17
+ * }
18
+ * })
19
+ *
20
+ * // instantiate
21
+ * const adapter = myAdapter({ validate: true })
22
+ */
23
+ export function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T> {
24
+ return (options) => build(options ?? ({} as T['options']))
25
+ }
@@ -0,0 +1,30 @@
1
+ import type { PluginFactoryOptions, UserPluginWithLifeCycle } from './types.ts'
2
+
3
+ /**
4
+ * Builder type for a {@link UserPluginWithLifeCycle} — takes options and returns the plugin instance.
5
+ */
6
+ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (options: T['options']) => UserPluginWithLifeCycle<T>
7
+
8
+ /**
9
+ * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * export const myPlugin = createPlugin<MyPlugin>((options) => {
14
+ * return {
15
+ * name: 'my-plugin',
16
+ * get options() { return options },
17
+ * resolvePath(baseName) { ... },
18
+ * resolveName(name, type) { ... },
19
+ * }
20
+ * })
21
+ *
22
+ * // instantiate
23
+ * const plugin = myPlugin({ output: { path: 'src/gen' } })
24
+ * ```
25
+ */
26
+ export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
27
+ build: PluginBuilder<T>,
28
+ ): (options?: T['options']) => UserPluginWithLifeCycle<T> {
29
+ return (options) => build(options ?? ({} as T['options']))
30
+ }
@@ -0,0 +1,58 @@
1
+ export type Storage = {
2
+ /**
3
+ * Identifier used for logging and debugging (e.g. `'fs'`, `'s3'`).
4
+ */
5
+ readonly name: string
6
+ /**
7
+ * Returns `true` when an entry for `key` exists in storage.
8
+ */
9
+ hasItem(key: string): Promise<boolean>
10
+ /**
11
+ * Returns the stored string value, or `null` when `key` does not exist.
12
+ */
13
+ getItem(key: string): Promise<string | null>
14
+ /**
15
+ * Persists `value` under `key`, creating any required structure.
16
+ */
17
+ setItem(key: string, value: string): Promise<void>
18
+ /**
19
+ * Removes the entry for `key`. No-ops when the key does not exist.
20
+ */
21
+ removeItem(key: string): Promise<void>
22
+ /**
23
+ * Returns all keys, optionally filtered to those starting with `base`.
24
+ */
25
+ getKeys(base?: string): Promise<Array<string>>
26
+ /**
27
+ * Removes all entries, optionally scoped to those starting with `base`.
28
+ */
29
+ clear(base?: string): Promise<void>
30
+ /**
31
+ * Optional teardown hook called after the build completes.
32
+ */
33
+ dispose?(): Promise<void>
34
+ }
35
+
36
+ /**
37
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
38
+ *
39
+ * @example
40
+ * export const memoryStorage = createStorage(() => {
41
+ * const store = new Map<string, string>()
42
+ * return {
43
+ * name: 'memory',
44
+ * async hasItem(key) { return store.has(key) },
45
+ * async getItem(key) { return store.get(key) ?? null },
46
+ * async setItem(key, value) { store.set(key, value) },
47
+ * async removeItem(key) { store.delete(key) },
48
+ * async getKeys(base) {
49
+ * const keys = [...store.keys()]
50
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
51
+ * },
52
+ * async clear(base) { if (!base) store.clear() },
53
+ * }
54
+ * })
55
+ */
56
+ export function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {
57
+ return (options) => build(options ?? ({} as TOptions))
58
+ }