@kubb/core 5.0.0-beta.15 → 5.0.0-beta.16

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/dist/mocks.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_PluginDriver = require("./PluginDriver-C1OsqGBJ.cjs");
2
+ const require_PluginDriver = require("./PluginDriver-C5hyNJfM.cjs");
3
3
  let node_path = require("node:path");
4
4
  let _kubb_ast = require("@kubb/ast");
5
5
  //#region src/mocks.ts
package/dist/mocks.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __name } from "./chunk--u3MIqq1.js";
2
- import { H as NormalizedPlugin, K as PluginFactoryOptions, P as PluginDriver, j as Generator, r as Config, vt as Adapter, yt as AdapterFactoryOptions } from "./createKubb-uVWTlN_w.js";
2
+ import { H as NormalizedPlugin, K as PluginFactoryOptions, P as PluginDriver, bt as AdapterFactoryOptions, j as Generator, r as Config, yt as Adapter } from "./createKubb-BncBLGm_.js";
3
3
  import { InputNode, OperationNode, SchemaNode, Visitor } from "@kubb/ast";
4
4
 
5
5
  //#region src/mocks.d.ts
package/dist/mocks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./chunk--u3MIqq1.js";
2
- import { n as applyHookResult, r as FileManager, t as PluginDriver } from "./PluginDriver-CGypdXHg.js";
2
+ import { n as applyHookResult, r as FileManager, t as PluginDriver } from "./PluginDriver-CT33kVoQ.js";
3
3
  import { resolve } from "node:path";
4
4
  import { transform } from "@kubb/ast";
5
5
  //#region src/mocks.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-beta.15",
3
+ "version": "5.0.0-beta.16",
4
4
  "description": "Core engine for Kubb's plugin-based code generation system. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",
5
5
  "keywords": [
6
6
  "code-generator",
@@ -58,15 +58,14 @@
58
58
  "dependencies": {
59
59
  "fflate": "^0.8.2",
60
60
  "tinyexec": "^1.1.2",
61
- "@kubb/ast": "5.0.0-beta.15"
61
+ "@kubb/ast": "5.0.0-beta.16"
62
62
  },
63
63
  "devDependencies": {
64
- "p-limit": "^7.3.0",
65
64
  "@internals/utils": "0.0.0",
66
- "@kubb/renderer-jsx": "5.0.0-beta.15"
65
+ "@kubb/renderer-jsx": "5.0.0-beta.16"
67
66
  },
68
67
  "peerDependencies": {
69
- "@kubb/renderer-jsx": "5.0.0-beta.15"
68
+ "@kubb/renderer-jsx": "5.0.0-beta.16"
70
69
  },
71
70
  "size-limit": [
72
71
  {
@@ -78,10 +77,6 @@
78
77
  "engines": {
79
78
  "node": ">=22"
80
79
  },
81
- "inlinedDependencies": {
82
- "p-limit": "7.3.0",
83
- "yocto-queue": "1.2.2"
84
- },
85
80
  "scripts": {
86
81
  "build": "tsdown && size-limit",
87
82
  "clean": "npx rimraf ./dist",
@@ -1,8 +1,6 @@
1
1
  import type { CodeNode, FileNode } from '@kubb/ast'
2
2
  import { extractStringsFromNodes } from '@kubb/ast'
3
3
  import { AsyncEventEmitter } from '@internals/utils'
4
- import pLimit from 'p-limit'
5
- import { PARALLEL_CONCURRENCY_LIMIT } from './constants.ts'
6
4
  import type { Parser } from './defineParser.ts'
7
5
 
8
6
  type ParseOptions = {
@@ -10,19 +8,20 @@ type ParseOptions = {
10
8
  extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
11
9
  }
12
10
 
13
- type RunOptions = ParseOptions & {
14
- /**
15
- * @default 'sequential'
16
- */
17
- mode?: 'sequential' | 'parallel'
18
- }
19
-
20
11
  export type FileProcessorEvents = {
21
12
  start: [files: Array<FileNode>]
22
13
  update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]
23
14
  end: [files: Array<FileNode>]
24
15
  }
25
16
 
17
+ export type ParsedFile = {
18
+ file: FileNode
19
+ source: string
20
+ processed: number
21
+ total: number
22
+ percentage: number
23
+ }
24
+
26
25
  function joinSources(file: FileNode): string {
27
26
  return file.sources
28
27
  .map((item) => extractStringsFromNodes(item.nodes as Array<CodeNode>))
@@ -38,7 +37,6 @@ function joinSources(file: FileNode): string {
38
37
  */
39
38
  export class FileProcessor {
40
39
  readonly events = new AsyncEventEmitter<FileProcessorEvents>()
41
- readonly #limit = pLimit(PARALLEL_CONCURRENCY_LIMIT)
42
40
 
43
41
  async parse(file: FileNode, { parsers, extension }: ParseOptions = {}): Promise<string> {
44
42
  const parseExtName = extension?.[file.extname] || undefined
@@ -56,32 +54,29 @@ export class FileProcessor {
56
54
  return parser.parse(file, { extname: parseExtName })
57
55
  }
58
56
 
59
- async run(files: Array<FileNode>, { parsers, mode = 'sequential', extension }: RunOptions = {}): Promise<Array<FileNode>> {
60
- await this.events.emit('start', files)
61
-
57
+ /**
58
+ * Streams parsed files one at a time as each is processed.
59
+ *
60
+ * Unlike `run()`, files are yielded immediately after parsing rather than batched.
61
+ * Storage writes can begin as soon as the first file is ready, keeping peak
62
+ * memory proportional to one file at a time instead of the full batch.
63
+ */
64
+ async *stream(files: ReadonlyArray<FileNode>, options: ParseOptions = {}): AsyncGenerator<ParsedFile> {
62
65
  const total = files.length
63
66
  let processed = 0
64
67
 
65
- const processOne = async (file: FileNode) => {
66
- const source = await this.parse(file, { extension, parsers })
67
- const currentProcessed = ++processed
68
- const percentage = (currentProcessed / total) * 100
69
-
70
- await this.events.emit('update', {
71
- file,
72
- source,
73
- processed: currentProcessed,
74
- percentage,
75
- total,
76
- })
68
+ for (const file of files) {
69
+ const source = await this.parse(file, options)
70
+ processed++
71
+ yield { file, source, processed, total, percentage: (processed / total) * 100 }
77
72
  }
73
+ }
74
+
75
+ async run(files: Array<FileNode>, options: ParseOptions = {}): Promise<Array<FileNode>> {
76
+ await this.events.emit('start', files)
78
77
 
79
- if (mode === 'sequential') {
80
- for (const file of files) {
81
- await processOne(file)
82
- }
83
- } else {
84
- await Promise.all(files.map((file) => this.#limit(() => processOne(file))))
78
+ for await (const { file, source, processed, total, percentage } of this.stream(files, options)) {
79
+ await this.events.emit('update', { file, source, processed, percentage, total })
85
80
  }
86
81
 
87
82
  await this.events.emit('end', files)
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'node:path'
2
2
  import type { AsyncEventEmitter } from '@internals/utils'
3
- import type { FileNode, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
3
+ import type { FileNode, InputNode, InputStreamNode, OperationNode, SchemaNode } from '@kubb/ast'
4
4
  import { createFile } from '@kubb/ast'
5
5
  import { DEFAULT_STUDIO_URL } from './constants.ts'
6
6
  import type { Generator } from './defineGenerator.ts'
@@ -55,6 +55,12 @@ export class PluginDriver {
55
55
  * the build pipeline after the adapter's `parse()` resolves.
56
56
  */
57
57
  inputNode: InputNode | undefined = undefined
58
+ /**
59
+ * Set when the adapter returns a streaming `InputStreamNode` (large specs).
60
+ * Mutually exclusive with `inputNode` — exactly one is set after adapter setup.
61
+ */
62
+ inputStreamNode: InputStreamNode | undefined = undefined
63
+
58
64
  adapter: Adapter | undefined = undefined
59
65
  #studioIsOpen = false
60
66
 
@@ -246,7 +252,7 @@ export class PluginDriver {
246
252
  }
247
253
 
248
254
  if (gen.operations) {
249
- const operationsHandler = async (nodes: Array<OperationNode>, ctx: GeneratorContext) => {
255
+ const operationsHandler = async (nodes: AsyncIterable<OperationNode> | Array<OperationNode>, ctx: GeneratorContext) => {
250
256
  if (ctx.plugin.name !== pluginName) return
251
257
  const result = await gen.operations!(nodes, ctx)
252
258
  await applyHookResult(result, this, resolveRenderer())
@@ -293,6 +299,7 @@ export class PluginDriver {
293
299
  // any FileNodes the caller needs to inspect.
294
300
  this.fileManager.dispose()
295
301
  this.inputNode = undefined
302
+ this.inputStreamNode = undefined
296
303
  }
297
304
 
298
305
  #trackHookListener(event: keyof KubbHooks, handler: (...args: never[]) => void | Promise<void>): void {
@@ -368,8 +375,8 @@ export class PluginDriver {
368
375
  upsertFile: async (...files: Array<FileNode>) => {
369
376
  driver.fileManager.upsert(...files)
370
377
  },
371
- get inputNode(): InputNode | undefined {
372
- return driver.inputNode
378
+ get inputNode(): InputNode {
379
+ return driver.inputNode ?? { kind: 'Input' as const, schemas: [], operations: [], meta: driver.inputStreamNode?.meta }
373
380
  },
374
381
  get adapter(): Adapter | undefined {
375
382
  return driver.adapter
package/src/constants.ts CHANGED
@@ -5,15 +5,6 @@ import type { FileNode } from '@kubb/ast'
5
5
  */
6
6
  export const DEFAULT_STUDIO_URL = 'https://kubb.studio' as const
7
7
 
8
- /**
9
- * Maximum number of files processed in parallel by FileProcessor.
10
- *
11
- * Capped at 16 to bound the number of CodeNode trees that are alive simultaneously
12
- * during rendering; I/O latency is the real bottleneck so higher values offer no
13
- * meaningful throughput improvement.
14
- */
15
- export const PARALLEL_CONCURRENCY_LIMIT = 16
16
-
17
8
  /**
18
9
  * Default banner style written at the top of every generated file.
19
10
  */
@@ -24,6 +15,16 @@ export const DEFAULT_BANNER = 'simple' as const
24
15
  */
25
16
  export const DEFAULT_EXTENSION: Record<FileNode['extname'], FileNode['extname'] | ''> = { '.ts': '.ts' }
26
17
 
18
+ /**
19
+ * Schema count above which the adapter's `stream()` path is used instead of `parse()`.
20
+ */
21
+ export const STREAM_SCHEMA_THRESHOLD = 100
22
+
23
+ /**
24
+ * In streaming mode, flush generated files to disk every N schemas to bound in-memory file buffers.
25
+ */
26
+ export const STREAM_FLUSH_EVERY = 50
27
+
27
28
  /**
28
29
  * Numeric log-level thresholds used internally to compare verbosity.
29
30
  *
@@ -1,5 +1,5 @@
1
1
  import type { PossiblePromise } from '@internals/utils'
2
- import type { ImportNode, InputNode, SchemaNode } from '@kubb/ast'
2
+ import type { ImportNode, InputNode, InputStreamNode, SchemaNode } from '@kubb/ast'
3
3
 
4
4
  /**
5
5
  * Source data passed to an adapter's `parse` function.
@@ -73,6 +73,22 @@ export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptio
73
73
  * Validate the document at the given path or URL.
74
74
  */
75
75
  validate: (input: string, options?: { throwOnError?: boolean }) => Promise<void>
76
+ /**
77
+ * Lightweight pre-flight count of schemas and operations without parsing AST nodes.
78
+ * The adapter should cache the loaded document so subsequent `parse()` or `stream()` calls
79
+ * do not reload it.
80
+ *
81
+ * Used by the core to decide whether to use `parse()` or `stream()`.
82
+ */
83
+ count?: (source: AdapterSource) => Promise<{ schemas: number; operations: number }>
84
+ /**
85
+ * Memory-efficient streaming variant of `parse()`.
86
+ *
87
+ * Returns an `InputStreamNode` whose `schemas` and `operations` are `AsyncIterable`.
88
+ * Each `for await` loop creates a fresh parse pass over the cached in-memory document —
89
+ * no pre-built arrays are held in memory.
90
+ */
91
+ stream?: (source: AdapterSource) => Promise<InputStreamNode>
76
92
  }
77
93
 
78
94
  type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>