@kubb/core 4.35.0 → 4.36.0

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.
@@ -2,7 +2,7 @@ import { t as __name } from "./chunk--u3MIqq1.js";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { Fabric } from "@kubb/react-fabric";
4
4
  import { KubbFile } from "@kubb/fabric-core/types";
5
- import { Printer, RootNode } from "@kubb/ast/types";
5
+ import { Printer, PrinterFactoryOptions, RootNode } from "@kubb/ast/types";
6
6
 
7
7
  //#region ../../internals/utils/dist/index.d.ts
8
8
  /**
@@ -69,6 +69,61 @@ declare const formatters: {
69
69
  };
70
70
  };
71
71
  //#endregion
72
+ //#region src/defineStorage.d.ts
73
+ /**
74
+ * Storage interface for persisting Kubb output.
75
+ *
76
+ * Keys are root-relative forward-slash paths (e.g. `src/gen/api/getPets.ts`).
77
+ * Implement this interface to route generated files to any backend — filesystem,
78
+ * S3, Redis, in-memory, etc.
79
+ *
80
+ * Use `defineStorage` to create a typed storage driver.
81
+ */
82
+ interface DefineStorage {
83
+ /** Identifier used for logging and debugging (e.g. `'fs'`, `'s3'`). */
84
+ readonly name: string;
85
+ /** Returns `true` when an entry for `key` exists in storage. */
86
+ hasItem(key: string): Promise<boolean>;
87
+ /** Returns the stored string value, or `null` when `key` does not exist. */
88
+ getItem(key: string): Promise<string | null>;
89
+ /** Persists `value` under `key`, creating any required structure. */
90
+ setItem(key: string, value: string): Promise<void>;
91
+ /** Removes the entry for `key`. No-ops when the key does not exist. */
92
+ removeItem(key: string): Promise<void>;
93
+ /** Returns all keys, optionally filtered to those starting with `base`. */
94
+ getKeys(base?: string): Promise<Array<string>>;
95
+ /** Removes all entries, optionally scoped to those starting with `base`. */
96
+ clear(base?: string): Promise<void>;
97
+ /** Optional teardown hook called after the build completes. */
98
+ dispose?(): Promise<void>;
99
+ }
100
+ /**
101
+ * Wraps a storage builder so the `options` argument is optional, following the
102
+ * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
103
+ *
104
+ * The builder receives the resolved options object and must return a
105
+ * `DefineStorage`-compatible object that includes a `name` string.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { defineStorage } from '@kubb/core'
110
+ *
111
+ * export const memoryStorage = defineStorage((_options) => {
112
+ * const store = new Map<string, string>()
113
+ * return {
114
+ * name: 'memory',
115
+ * async hasItem(key) { return store.has(key) },
116
+ * async getItem(key) { return store.get(key) ?? null },
117
+ * async setItem(key, value) { store.set(key, value) },
118
+ * async removeItem(key) { store.delete(key) },
119
+ * async getKeys() { return [...store.keys()] },
120
+ * async clear() { store.clear() },
121
+ * }
122
+ * })
123
+ * ```
124
+ */
125
+ declare function defineStorage<TOptions = Record<string, never>>(build: (options: TOptions) => DefineStorage): (options?: TOptions) => DefineStorage;
126
+ //#endregion
72
127
  //#region src/PluginManager.d.ts
73
128
  type RequiredPluginLifecycle = Required<PluginLifecycle>;
74
129
  type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookSeq';
@@ -551,8 +606,22 @@ type Config<TInput = Input> = {
551
606
  /**
552
607
  * Save files to the file system.
553
608
  * @default true
609
+ * @deprecated Use `storage` to control where files are written.
554
610
  */
555
611
  write?: boolean;
612
+ /**
613
+ * Storage backend for generated files.
614
+ * Defaults to `fsStorage()` — the built-in filesystem driver.
615
+ * Accepts any object implementing the {@link DefineStorage} interface.
616
+ * Keys are root-relative paths (e.g. `src/gen/api/getPets.ts`).
617
+ * @default fsStorage()
618
+ * @example
619
+ * ```ts
620
+ * import { defineStorage, fsStorage } from '@kubb/core'
621
+ * storage: defineStorage(fsStorage())
622
+ * ```
623
+ */
624
+ storage?: DefineStorage;
556
625
  /**
557
626
  * Specifies the formatting tool to be used.
558
627
  * - 'auto' automatically detects and uses biome or prettier (in that order of preference).
@@ -849,5 +918,5 @@ type Logger<TOptions extends LoggerOptions = LoggerOptions> = {
849
918
  };
850
919
  type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Omit<Logger<TOptions>, 'logLevel'>;
851
920
  //#endregion
852
- export { UserPluginWithLifeCycle as A, Printer as C, UserConfig as D, UnknownUserPlugin as E, linters as F, logLevel as I, AsyncEventEmitter as L, PluginManager as M, getMode as N, UserLogger as O, formatters as P, PluginWithLifeCycle as S, ResolvePathParams as T, PluginFactoryOptions as _, Config as a, PluginLifecycleHooks as b, Group as c, Logger as d, LoggerContext as f, PluginContext as g, Plugin as h, BarrelType as i, KubbEvents as j, UserPlugin as k, InputData as l, Output as m, AdapterFactoryOptions as n, DevtoolsOptions as o, LoggerOptions as p, AdapterSource as r, GetPluginFactoryOptions as s, Adapter as t, InputPath as u, PluginKey as v, ResolveNameParams as w, PluginParameter as x, PluginLifecycle as y };
853
- //# sourceMappingURL=types-7DgxNmCG.d.ts.map
921
+ export { UserPlugin as A, AsyncEventEmitter as B, Printer as C, UnknownUserPlugin as D, ResolvePathParams as E, DefineStorage as F, defineStorage as I, formatters as L, KubbEvents as M, PluginManager as N, UserConfig as O, getMode as P, linters as R, PluginWithLifeCycle as S, ResolveNameParams as T, PluginFactoryOptions as _, Config as a, PluginLifecycleHooks as b, Group as c, Logger as d, LoggerContext as f, PluginContext as g, Plugin as h, BarrelType as i, UserPluginWithLifeCycle as j, UserLogger as k, InputData as l, Output as m, AdapterFactoryOptions as n, DevtoolsOptions as o, LoggerOptions as p, AdapterSource as r, GetPluginFactoryOptions as s, Adapter as t, InputPath as u, PluginKey as v, PrinterFactoryOptions as w, PluginParameter as x, PluginLifecycle as y, logLevel as z };
922
+ //# sourceMappingURL=types-D-MpaA4Z.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "4.35.0",
3
+ "version": "4.36.0",
4
4
  "description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -71,7 +71,7 @@
71
71
  "remeda": "^2.33.6",
72
72
  "semver": "^7.7.4",
73
73
  "tinyexec": "^1.0.4",
74
- "@kubb/ast": "4.35.0"
74
+ "@kubb/ast": "4.36.0"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/semver": "^7.7.1",
package/src/build.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { dirname, resolve } from 'node:path'
2
- import { AsyncEventEmitter, clean, exists, formatMs, getElapsedMs, getRelativePath, URLPath, write } from '@internals/utils'
1
+ import { dirname, relative, resolve } from 'node:path'
2
+ import { AsyncEventEmitter, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
3
  import type { KubbFile } from '@kubb/fabric-core/types'
4
4
  import type { Fabric } from '@kubb/react-fabric'
5
5
  import { createFabric } from '@kubb/react-fabric'
@@ -9,7 +9,8 @@ import { isInputPath } from './config.ts'
9
9
  import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
10
10
  import { BuildError } from './errors.ts'
11
11
  import { PluginManager } from './PluginManager.ts'
12
- import type { AdapterSource, Config, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
12
+ import { fsStorage } from './storages/fsStorage.ts'
13
+ import type { AdapterSource, Config, DefineStorage, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
13
14
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
14
15
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
15
16
 
@@ -54,7 +55,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
54
55
  ` • Output: ${userConfig.output?.path || 'not specified'}`,
55
56
  ` • Plugins: ${userConfig.plugins?.length || 0}`,
56
57
  'Output Settings:',
57
- ` • Write: ${userConfig.output?.write !== false ? 'enabled' : 'disabled'}`,
58
+ ` • Storage: ${userConfig.output?.storage ? `custom(${userConfig.output.storage.name})` : userConfig.output?.write === false ? 'disabled' : 'filesystem (default)'}`,
58
59
  ` • Formatter: ${userConfig.output?.format || 'none'}`,
59
60
  ` • Linter: ${userConfig.output?.lint || 'none'}`,
60
61
  'Environment:',
@@ -105,12 +106,18 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
105
106
  plugins: userConfig.plugins as Config['plugins'],
106
107
  }
107
108
 
109
+ // write: false is the explicit dry-run opt-out; otherwise use the provided
110
+ // storage or fall back to fsStorage (backwards-compatible default).
111
+ // Keys are root-relative (e.g. `src/gen/api/getPets.ts`) so fsStorage()
112
+ // needs no configuration — it resolves them against process.cwd().
113
+ const storage: DefineStorage | null = definedConfig.output.write === false ? null : (definedConfig.output.storage ?? fsStorage())
114
+
108
115
  if (definedConfig.output.clean) {
109
116
  await events.emit('debug', {
110
117
  date: new Date(),
111
118
  logs: ['Cleaning output directories', ` • Output: ${definedConfig.output.path}`],
112
119
  })
113
- await clean(definedConfig.output.path)
120
+ await storage?.clear(resolve(definedConfig.root, definedConfig.output.path))
114
121
  }
115
122
 
116
123
  const fabric = createFabric()
@@ -134,10 +141,9 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
134
141
  })
135
142
 
136
143
  if (source) {
137
- if (definedConfig.output.write) {
138
- await write(file.path, source, { sanity: false })
139
- }
140
-
144
+ // Key is root-relative so it's meaningful for any backend (fs, S3, Redis…)
145
+ const key = relative(resolve(definedConfig.root), file.path)
146
+ await storage?.setItem(key, source)
141
147
  sources.set(file.path, source)
142
148
  }
143
149
  })
@@ -154,7 +160,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
154
160
  date: new Date(),
155
161
  logs: [
156
162
  '✓ Fabric initialized',
157
- ` • File writing: ${definedConfig.output.write ? 'enabled' : 'disabled (dry-run)'}`,
163
+ ` • Storage: ${storage ? storage.name : 'disabled (dry-run)'}`,
158
164
  ` • Barrel type: ${definedConfig.output.barrelType || 'none'}`,
159
165
  ],
160
166
  })
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Storage interface for persisting Kubb output.
3
+ *
4
+ * Keys are root-relative forward-slash paths (e.g. `src/gen/api/getPets.ts`).
5
+ * Implement this interface to route generated files to any backend — filesystem,
6
+ * S3, Redis, in-memory, etc.
7
+ *
8
+ * Use `defineStorage` to create a typed storage driver.
9
+ */
10
+ export interface DefineStorage {
11
+ /** Identifier used for logging and debugging (e.g. `'fs'`, `'s3'`). */
12
+ readonly name: string
13
+ /** Returns `true` when an entry for `key` exists in storage. */
14
+ hasItem(key: string): Promise<boolean>
15
+ /** Returns the stored string value, or `null` when `key` does not exist. */
16
+ getItem(key: string): Promise<string | null>
17
+ /** Persists `value` under `key`, creating any required structure. */
18
+ setItem(key: string, value: string): Promise<void>
19
+ /** Removes the entry for `key`. No-ops when the key does not exist. */
20
+ removeItem(key: string): Promise<void>
21
+ /** Returns all keys, optionally filtered to those starting with `base`. */
22
+ getKeys(base?: string): Promise<Array<string>>
23
+ /** Removes all entries, optionally scoped to those starting with `base`. */
24
+ clear(base?: string): Promise<void>
25
+ /** Optional teardown hook called after the build completes. */
26
+ dispose?(): Promise<void>
27
+ }
28
+
29
+ /**
30
+ * Wraps a storage builder so the `options` argument is optional, following the
31
+ * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
32
+ *
33
+ * The builder receives the resolved options object and must return a
34
+ * `DefineStorage`-compatible object that includes a `name` string.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { defineStorage } from '@kubb/core'
39
+ *
40
+ * export const memoryStorage = defineStorage((_options) => {
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() { return [...store.keys()] },
49
+ * async clear() { store.clear() },
50
+ * }
51
+ * })
52
+ * ```
53
+ */
54
+ export function defineStorage<TOptions = Record<string, never>>(build: (options: TOptions) => DefineStorage): (options?: TOptions) => DefineStorage {
55
+ return (options) => build(options ?? ({} as TOptions))
56
+ }
package/src/index.ts CHANGED
@@ -6,9 +6,12 @@ export { formatters, linters, logLevel } from './constants.ts'
6
6
  export { defineAdapter } from './defineAdapter.ts'
7
7
  export { defineLogger } from './defineLogger.ts'
8
8
  export { definePlugin } from './definePlugin.ts'
9
+ export { defineStorage } from './defineStorage.ts'
9
10
  export { PackageManager } from './PackageManager.ts'
10
11
  export { getMode, PluginManager } from './PluginManager.ts'
11
12
  export { PromiseManager } from './PromiseManager.ts'
13
+ export { fsStorage } from './storages/fsStorage.ts'
14
+ export { memoryStorage } from './storages/memoryStorage.ts'
12
15
  export * from './types.ts'
13
16
  export type { FunctionParamsAST } from './utils/FunctionParams.ts'
14
17
  export { FunctionParams } from './utils/FunctionParams.ts'
@@ -0,0 +1,84 @@
1
+ import type { Dirent } from 'node:fs'
2
+ import { access, readdir, readFile, rm } from 'node:fs/promises'
3
+ import { join, resolve } from 'node:path'
4
+ import { clean, write } from '@internals/utils'
5
+ import { defineStorage } from '../defineStorage.ts'
6
+
7
+ /**
8
+ * Built-in filesystem storage driver.
9
+ *
10
+ * This is the default storage when no `storage` option is configured in `output`.
11
+ * Keys are resolved against `process.cwd()`, so root-relative paths such as
12
+ * `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
13
+ *
14
+ * Internally uses the `write` utility from `@internals/utils`, which:
15
+ * - trims leading/trailing whitespace before writing
16
+ * - skips the write when file content is already identical (deduplication)
17
+ * - creates missing parent directories automatically
18
+ * - supports Bun's native file API when running under Bun
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { defineConfig, fsStorage } from '@kubb/core'
23
+ *
24
+ * export default defineConfig({
25
+ * input: { path: './petStore.yaml' },
26
+ * output: { path: './src/gen', storage: fsStorage() },
27
+ * })
28
+ * ```
29
+ */
30
+ export const fsStorage = defineStorage(() => ({
31
+ name: 'fs',
32
+ async hasItem(key: string) {
33
+ try {
34
+ await access(resolve(key))
35
+ return true
36
+ } catch {
37
+ return false
38
+ }
39
+ },
40
+ async getItem(key: string) {
41
+ try {
42
+ return await readFile(resolve(key), 'utf8')
43
+ } catch {
44
+ return null
45
+ }
46
+ },
47
+ async setItem(key: string, value: string) {
48
+ await write(resolve(key), value, { sanity: false })
49
+ },
50
+ async removeItem(key: string) {
51
+ await rm(resolve(key), { force: true })
52
+ },
53
+ async getKeys(base?: string) {
54
+ const keys: Array<string> = []
55
+
56
+ async function walk(dir: string, prefix: string): Promise<void> {
57
+ let entries: Array<Dirent>
58
+ try {
59
+ entries = (await readdir(dir, { withFileTypes: true })) as Array<Dirent>
60
+ } catch {
61
+ return
62
+ }
63
+ for (const entry of entries) {
64
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name
65
+ if (entry.isDirectory()) {
66
+ await walk(join(dir, entry.name), rel)
67
+ } else {
68
+ keys.push(rel)
69
+ }
70
+ }
71
+ }
72
+
73
+ await walk(resolve(base ?? process.cwd()), '')
74
+
75
+ return keys
76
+ },
77
+ async clear(base?: string) {
78
+ if (!base) {
79
+ return
80
+ }
81
+
82
+ await clean(resolve(base))
83
+ },
84
+ }))
@@ -0,0 +1,53 @@
1
+ import { defineStorage } from '../defineStorage.ts'
2
+
3
+ /**
4
+ * In-memory storage driver. Useful for testing and dry-run scenarios where
5
+ * generated output should be captured without touching the filesystem.
6
+ *
7
+ * All data lives in a `Map` scoped to the storage instance and is discarded
8
+ * when the instance is garbage-collected.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { defineConfig, memoryStorage } from '@kubb/core'
13
+ *
14
+ * export default defineConfig({
15
+ * input: { path: './petStore.yaml' },
16
+ * output: { path: './src/gen', storage: memoryStorage() },
17
+ * })
18
+ * ```
19
+ */
20
+ export const memoryStorage = defineStorage(() => {
21
+ const store = new Map<string, string>()
22
+
23
+ return {
24
+ name: 'memory',
25
+ async hasItem(key: string) {
26
+ return store.has(key)
27
+ },
28
+ async getItem(key: string) {
29
+ return store.get(key) ?? null
30
+ },
31
+ async setItem(key: string, value: string) {
32
+ store.set(key, value)
33
+ },
34
+ async removeItem(key: string) {
35
+ store.delete(key)
36
+ },
37
+ async getKeys(base?: string) {
38
+ const keys = [...store.keys()]
39
+ return base ? keys.filter((k) => k.startsWith(base)) : keys
40
+ },
41
+ async clear(base?: string) {
42
+ if (!base) {
43
+ store.clear()
44
+ return
45
+ }
46
+ for (const key of store.keys()) {
47
+ if (key.startsWith(base)) {
48
+ store.delete(key)
49
+ }
50
+ }
51
+ },
52
+ }
53
+ })
package/src/types.ts CHANGED
@@ -3,10 +3,11 @@ import type { RootNode } from '@kubb/ast/types'
3
3
  import type { KubbFile } from '@kubb/fabric-core/types'
4
4
  import type { Fabric } from '@kubb/react-fabric'
5
5
  import type { DEFAULT_STUDIO_URL, logLevel } from './constants.ts'
6
+ import type { DefineStorage } from './defineStorage.ts'
6
7
  import type { KubbEvents } from './Kubb.ts'
7
8
  import type { PluginManager } from './PluginManager.ts'
8
9
 
9
- export type { Printer } from '@kubb/ast/types'
10
+ export type { Printer, PrinterFactoryOptions } from '@kubb/ast/types'
10
11
 
11
12
  declare global {
12
13
  namespace Kubb {
@@ -158,8 +159,22 @@ export type Config<TInput = Input> = {
158
159
  /**
159
160
  * Save files to the file system.
160
161
  * @default true
162
+ * @deprecated Use `storage` to control where files are written.
161
163
  */
162
164
  write?: boolean
165
+ /**
166
+ * Storage backend for generated files.
167
+ * Defaults to `fsStorage()` — the built-in filesystem driver.
168
+ * Accepts any object implementing the {@link DefineStorage} interface.
169
+ * Keys are root-relative paths (e.g. `src/gen/api/getPets.ts`).
170
+ * @default fsStorage()
171
+ * @example
172
+ * ```ts
173
+ * import { defineStorage, fsStorage } from '@kubb/core'
174
+ * storage: defineStorage(fsStorage())
175
+ * ```
176
+ */
177
+ storage?: DefineStorage
163
178
  /**
164
179
  * Specifies the formatting tool to be used.
165
180
  * - 'auto' automatically detects and uses biome or prettier (in that order of preference).
@@ -483,4 +498,5 @@ export type Logger<TOptions extends LoggerOptions = LoggerOptions> = {
483
498
 
484
499
  export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Omit<Logger<TOptions>, 'logLevel'>
485
500
 
501
+ export type { DefineStorage } from './defineStorage.ts'
486
502
  export type { KubbEvents } from './Kubb.ts'