@kubb/core 5.0.0-beta.3 → 5.0.0-beta.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 (46) hide show
  1. package/README.md +8 -38
  2. package/dist/KubbDriver-CFx2DdhF.js +2131 -0
  3. package/dist/KubbDriver-CFx2DdhF.js.map +1 -0
  4. package/dist/KubbDriver-vyD7F0Ip.cjs +2252 -0
  5. package/dist/KubbDriver-vyD7F0Ip.cjs.map +1 -0
  6. package/dist/{types-CC09VtBt.d.ts → createKubb-6zii1jo-.d.ts} +1610 -1257
  7. package/dist/index.cjs +351 -1125
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +3 -186
  10. package/dist/index.js +341 -1119
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +30 -21
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +5 -5
  15. package/dist/mocks.js +29 -20
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +6 -18
  18. package/src/FileManager.ts +78 -61
  19. package/src/FileProcessor.ts +48 -38
  20. package/src/KubbDriver.ts +930 -0
  21. package/src/constants.ts +11 -6
  22. package/src/createAdapter.ts +113 -17
  23. package/src/createKubb.ts +1039 -478
  24. package/src/createRenderer.ts +58 -27
  25. package/src/createStorage.ts +36 -23
  26. package/src/defineGenerator.ts +127 -15
  27. package/src/defineLogger.ts +66 -7
  28. package/src/defineMiddleware.ts +19 -17
  29. package/src/defineParser.ts +30 -13
  30. package/src/definePlugin.ts +329 -14
  31. package/src/defineResolver.ts +365 -167
  32. package/src/devtools.ts +8 -1
  33. package/src/index.ts +2 -2
  34. package/src/mocks.ts +11 -14
  35. package/src/storages/fsStorage.ts +13 -37
  36. package/src/types.ts +48 -1292
  37. package/dist/PluginDriver-BXibeQk-.cjs +0 -1036
  38. package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
  39. package/dist/PluginDriver-DV3p2Hky.js +0 -945
  40. package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
  41. package/src/Kubb.ts +0 -300
  42. package/src/PluginDriver.ts +0 -424
  43. package/src/renderNode.ts +0 -35
  44. package/src/utils/diagnostics.ts +0 -18
  45. package/src/utils/isInputPath.ts +0 -10
  46. package/src/utils/packageJSON.ts +0 -99
package/dist/mocks.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "./chunk--u3MIqq1.js";
2
- import { R as NormalizedPlugin, V as PluginFactoryOptions, a as Config, n as AdapterFactoryOptions, ot as PluginDriver, t as Adapter, ut as Generator } from "./types-CC09VtBt.js";
3
- import { OperationNode, SchemaNode, Visitor } from "@kubb/ast";
2
+ import { Ct as AdapterFactoryOptions, F as KubbDriver, M as Generator, St as Adapter, U as NormalizedPlugin, q as PluginFactoryOptions, r as Config } from "./createKubb-6zii1jo-.js";
3
+ import { InputMeta, OperationNode, SchemaNode, Visitor } from "@kubb/ast";
4
4
 
5
5
  //#region src/mocks.d.ts
6
6
  /**
@@ -11,7 +11,7 @@ declare function createMockedPluginDriver(options?: {
11
11
  name?: string;
12
12
  plugin?: NormalizedPlugin;
13
13
  config?: Config;
14
- }): PluginDriver;
14
+ }): KubbDriver;
15
15
  /**
16
16
  * Creates a minimal `Adapter` mock for unit tests.
17
17
  * `parse` returns an empty `InputNode` by default; override via `options.parse`.
@@ -20,7 +20,6 @@ declare function createMockedPluginDriver(options?: {
20
20
  declare function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(options?: {
21
21
  name?: TOptions['name'];
22
22
  resolvedOptions?: TOptions['resolvedOptions'];
23
- inputNode?: Adapter<TOptions>['inputNode'];
24
23
  parse?: Adapter<TOptions>['parse'];
25
24
  getImports?: Adapter<TOptions>['getImports'];
26
25
  }): Adapter<TOptions>;
@@ -40,7 +39,8 @@ declare function createMockedPlugin<TOptions extends PluginFactoryOptions = Plug
40
39
  type RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {
41
40
  config: Config;
42
41
  adapter: Adapter;
43
- driver: PluginDriver;
42
+ meta?: InputMeta;
43
+ driver: KubbDriver;
44
44
  plugin: NormalizedPlugin<TOptions>;
45
45
  options: TOptions['resolvedOptions'];
46
46
  resolver: TOptions['resolver'];
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-DV3p2Hky.js";
2
+ import { a as FileManager, n as applyHookResult, t as KubbDriver } from "./KubbDriver-CFx2DdhF.js";
3
3
  import { resolve } from "node:path";
4
4
  import { transform } from "@kubb/ast";
5
5
  //#region src/mocks.ts
@@ -26,11 +26,9 @@ function createMockedPluginDriver(options = {}) {
26
26
  * `getImports` returns `[]` by default.
27
27
  */
28
28
  function createMockedAdapter(options = {}) {
29
- const inputNode = options.inputNode ?? null;
30
29
  return {
31
30
  name: options.name ?? "oas",
32
31
  options: options.resolvedOptions ?? {},
33
- inputNode,
34
32
  parse: options.parse ?? (async () => ({
35
33
  kind: "Input",
36
34
  schemas: [],
@@ -60,16 +58,15 @@ function createMockedPluginContext(opts) {
60
58
  return {
61
59
  config: opts.config,
62
60
  root,
63
- getMode: (output) => PluginDriver.getMode(resolve(root, output.path)),
61
+ getMode: (output) => KubbDriver.getMode(resolve(root, output.path)),
64
62
  adapter: opts.adapter,
65
63
  resolver: opts.resolver,
66
64
  plugin: opts.plugin,
67
65
  driver: opts.driver,
68
66
  getResolver: (name) => opts.driver.getResolver(name),
69
- inputNode: {
70
- kind: "Input",
71
- schemas: [],
72
- operations: []
67
+ meta: opts.meta ?? {
68
+ circularNames: [],
69
+ enumNames: []
73
70
  },
74
71
  addFile: async (...files) => opts.driver.fileManager.add(...files),
75
72
  upsertFile: async (...files) => opts.driver.fileManager.upsert(...files),
@@ -93,10 +90,14 @@ async function renderGeneratorSchema(generator, node, opts) {
93
90
  if (!generator.schema) return;
94
91
  const context = createMockedPluginContext(opts);
95
92
  const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node;
96
- await applyHookResult(await generator.schema(transformedNode, {
97
- ...context,
98
- options: opts.options
99
- }), opts.driver, generator.renderer ?? void 0);
93
+ await applyHookResult({
94
+ result: await generator.schema(transformedNode, {
95
+ ...context,
96
+ options: opts.options
97
+ }),
98
+ driver: opts.driver,
99
+ rendererFactory: generator.renderer
100
+ });
100
101
  }
101
102
  /**
102
103
  * Renders a generator's `operation` method in a test context.
@@ -111,10 +112,14 @@ async function renderGeneratorOperation(generator, node, opts) {
111
112
  if (!generator.operation) return;
112
113
  const context = createMockedPluginContext(opts);
113
114
  const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node;
114
- await applyHookResult(await generator.operation(transformedNode, {
115
- ...context,
116
- options: opts.options
117
- }), opts.driver, generator.renderer ?? void 0);
115
+ await applyHookResult({
116
+ result: await generator.operation(transformedNode, {
117
+ ...context,
118
+ options: opts.options
119
+ }),
120
+ driver: opts.driver,
121
+ rendererFactory: generator.renderer
122
+ });
118
123
  }
119
124
  /**
120
125
  * Renders a generator's `operations` method in a test context.
@@ -129,10 +134,14 @@ async function renderGeneratorOperations(generator, nodes, opts) {
129
134
  if (!generator.operations) return;
130
135
  const context = createMockedPluginContext(opts);
131
136
  const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer)) : nodes;
132
- await applyHookResult(await generator.operations(transformedNodes, {
133
- ...context,
134
- options: opts.options
135
- }), opts.driver, generator.renderer ?? void 0);
137
+ await applyHookResult({
138
+ result: await generator.operations(transformedNodes, {
139
+ ...context,
140
+ options: opts.options
141
+ }),
142
+ driver: opts.driver,
143
+ rendererFactory: generator.renderer
144
+ });
136
145
  }
137
146
  //#endregion
138
147
  export { createMockedAdapter, createMockedPlugin, createMockedPluginDriver, renderGeneratorOperation, renderGeneratorOperations, renderGeneratorSchema };
package/dist/mocks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { FileNode, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { FileManager } from './FileManager.ts'\nimport { PluginDriver } from './PluginDriver.ts'\nimport { applyHookResult } from './renderNode.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): PluginDriver {\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager: new FileManager(),\n } as unknown as PluginDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default; override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n inputNode?: Adapter<TOptions>['inputNode']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n const inputNode = options.inputNode ?? null\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n inputNode,\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n driver: PluginDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n getMode: (output: { path: string }) => PluginDriver.getMode(resolve(root, output.path)),\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n inputNode: { kind: 'Input', schemas: [], operations: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n openInStudio: async () => {},\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult(result, opts.driver, generator.renderer ?? undefined)\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult(result, opts.driver, generator.renderer ?? undefined)\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await applyHookResult(result, opts.driver, generator.renderer ?? undefined)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { FileManager } from './FileManager.ts'\nimport { applyHookResult, KubbDriver } from './KubbDriver.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager: new FileManager(),\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default; override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n getMode: (output: { path: string }) => KubbDriver.getMode(resolve(root, output.path)),\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n openInStudio: async () => {},\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,20 +1,13 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-beta.3",
4
- "description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
3
+ "version": "5.0.0-beta.30",
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
- "ast",
7
6
  "code-generator",
8
7
  "codegen",
9
- "core-library",
10
- "file-system",
11
8
  "kubb",
12
- "oas",
13
9
  "openapi",
14
- "plugin-framework",
15
10
  "plugin-system",
16
- "plugins",
17
- "swagger",
18
11
  "typescript"
19
12
  ],
20
13
  "license": "MIT",
@@ -63,17 +56,16 @@
63
56
  "registry": "https://registry.npmjs.org/"
64
57
  },
65
58
  "dependencies": {
66
- "fflate": "^0.8.2",
59
+ "fflate": "^0.8.3",
67
60
  "tinyexec": "^1.1.2",
68
- "@kubb/ast": "5.0.0-beta.3"
61
+ "@kubb/ast": "5.0.0-beta.30"
69
62
  },
70
63
  "devDependencies": {
71
- "p-limit": "^7.3.0",
72
64
  "@internals/utils": "0.0.0",
73
- "@kubb/renderer-jsx": "5.0.0-beta.3"
65
+ "@kubb/renderer-jsx": "5.0.0-beta.30"
74
66
  },
75
67
  "peerDependencies": {
76
- "@kubb/renderer-jsx": "5.0.0-beta.3"
68
+ "@kubb/renderer-jsx": "5.0.0-beta.30"
77
69
  },
78
70
  "size-limit": [
79
71
  {
@@ -85,10 +77,6 @@
85
77
  "engines": {
86
78
  "node": ">=22"
87
79
  },
88
- "inlinedDependencies": {
89
- "p-limit": "7.3.0",
90
- "yocto-queue": "1.2.2"
91
- },
92
80
  "scripts": {
93
81
  "build": "tsdown && size-limit",
94
82
  "clean": "npx rimraf ./dist",
@@ -4,77 +4,96 @@ import { createFile } from '@kubb/ast'
4
4
  function mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {
5
5
  return {
6
6
  ...a,
7
- // Incoming file (b) takes precedence for banner/footer so that barrel files,
8
- // which never carry a banner, can clear banners set by plugin-generated files
9
- // at the same path.
7
+ // Incoming file (b) takes precedence for banner/footer so a barrel file (whose
8
+ // banner/footer the barrel middleware resolves last) wins over a plugin-generated
9
+ // file at the same path.
10
10
  banner: b.banner,
11
11
  footer: b.footer,
12
- sources: [...(a.sources || []), ...(b.sources || [])],
13
- imports: [...(a.imports || []), ...(b.imports || [])],
14
- exports: [...(a.exports || []), ...(b.exports || [])],
12
+ sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,
13
+ imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,
14
+ exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,
15
15
  }
16
16
  }
17
17
 
18
- /**
19
- * Collapses a list of files so that duplicates sharing the same `path` are merged
20
- * in arrival order. Keeps the original order of first occurrence.
21
- */
22
- function mergeFilesByPath(files: ReadonlyArray<FileNode>): Map<string, FileNode> {
23
- const merged = new Map<string, FileNode>()
24
- for (const file of files) {
25
- const existing = merged.get(file.path)
26
- merged.set(file.path, existing ? mergeFile(existing, file) : file)
27
- }
28
- return merged
18
+ function isIndexPath(path: string): boolean {
19
+ return path.endsWith('/index.ts') || path === 'index.ts'
20
+ }
21
+
22
+ // Sort order: shortest path first; within a length bucket, index.ts barrels last.
23
+ function compareFiles(a: FileNode, b: FileNode): number {
24
+ const lenDiff = a.path.length - b.path.length
25
+ if (lenDiff !== 0) return lenDiff
26
+ const aIsIndex = isIndexPath(a.path)
27
+ const bIsIndex = isIndexPath(b.path)
28
+ if (aIsIndex && !bIsIndex) return 1
29
+ if (!aIsIndex && bIsIndex) return -1
30
+ return 0
29
31
  }
30
32
 
31
33
  /**
32
- * In-memory file store for generated files.
33
- *
34
- * Files with the same `path` are merged sources, imports, and exports are concatenated.
35
- * The `files` getter returns all stored files sorted by path length (shortest first).
34
+ * In-memory file store for generated files. Files sharing a `path` are merged
35
+ * (sources/imports/exports concatenated). The `files` getter is sorted by
36
+ * path length (barrel `index.ts` last within a bucket).
36
37
  *
37
38
  * @example
38
39
  * ```ts
39
- * import { FileManager } from '@kubb/core'
40
- *
41
40
  * const manager = new FileManager()
42
41
  * manager.upsert(myFile)
43
- * console.log(manager.files) // all stored files
42
+ * manager.files // sorted view
44
43
  * ```
45
44
  */
46
45
  export class FileManager {
47
46
  readonly #cache = new Map<string, FileNode>()
48
- #filesCache: Array<FileNode> | null = null
47
+ // Cached sorted view; null means stale and rebuilt lazily on next `files` read.
48
+ // Nulled (not mutated) on every write so callers holding a prior reference
49
+ // keep their snapshot — `dispose()` must not silently empty an array the
50
+ // consumer already holds.
51
+ #sorted: Array<FileNode> | null = null
52
+ #onUpsert: ((file: FileNode) => void) | null = null
49
53
 
50
54
  /**
51
- * Adds one or more files. Incoming files with the same path are merged
52
- * (sources/imports/exports concatenated), but existing cache entries are
53
- * replaced use {@link upsert} when you want to merge into the cache too.
55
+ * Registers a callback invoked with the resolved {@link FileNode} on every
56
+ * `add` / `upsert`. Used by the build loop to track newly written files
57
+ * without keeping its own scan-based diff. Single subscriber by design
58
+ * setting again replaces the previous callback. Pass `null` to detach.
54
59
  */
60
+ setOnUpsert(callback: ((file: FileNode) => void) | null): void {
61
+ this.#onUpsert = callback
62
+ }
63
+
55
64
  add(...files: Array<FileNode>): Array<FileNode> {
56
65
  return this.#store(files, false)
57
66
  }
58
67
 
59
- /**
60
- * Adds or merges one or more files.
61
- * If a file with the same path already exists in the cache, its
62
- * sources/imports/exports are merged into the incoming file.
63
- */
64
68
  upsert(...files: Array<FileNode>): Array<FileNode> {
65
69
  return this.#store(files, true)
66
70
  }
67
71
 
68
72
  #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {
69
- const resolvedFiles: Array<FileNode> = []
70
- for (const file of mergeFilesByPath(files).values()) {
71
- const existing = mergeExisting ? this.#cache.get(file.path) : undefined
72
- const resolvedFile = createFile(existing ? mergeFile(existing, file) : file)
73
- this.#cache.set(resolvedFile.path, resolvedFile)
74
- resolvedFiles.push(resolvedFile)
73
+ const batch = files.length > 1 ? this.#dedupe(files) : files
74
+ const resolved: Array<FileNode> = []
75
+
76
+ for (const file of batch) {
77
+ const existing = this.#cache.get(file.path)
78
+ const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)
79
+ this.#cache.set(merged.path, merged)
80
+ resolved.push(merged)
81
+ this.#onUpsert?.(merged)
75
82
  }
76
- this.#filesCache = null
77
- return resolvedFiles
83
+
84
+ if (resolved.length > 0) this.#sorted = null
85
+ return resolved
86
+ }
87
+
88
+ // Merges same-path entries within a batch so the cache update loop stays
89
+ // uniform. Only called for multi-file batches.
90
+ #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {
91
+ const seen = new Map<string, FileNode>()
92
+ for (const file of files) {
93
+ const prev = seen.get(file.path)
94
+ seen.set(file.path, prev ? mergeFile(prev, file) : file)
95
+ }
96
+ return [...seen.values()]
78
97
  }
79
98
 
80
99
  getByPath(path: string): FileNode | null {
@@ -82,34 +101,32 @@ export class FileManager {
82
101
  }
83
102
 
84
103
  deleteByPath(path: string): void {
85
- this.#cache.delete(path)
86
- this.#filesCache = null
104
+ if (!this.#cache.delete(path)) return
105
+ this.#sorted = null
87
106
  }
88
107
 
89
108
  clear(): void {
90
109
  this.#cache.clear()
91
- this.#filesCache = null
110
+ this.#sorted = null
92
111
  }
93
112
 
94
113
  /**
95
- * All stored files, sorted by path length (shorter paths first).
114
+ * Releases all stored files. Called by the core after `kubb:build:end`.
96
115
  */
97
- get files(): Array<FileNode> {
98
- if (this.#filesCache) {
99
- return this.#filesCache
100
- }
116
+ dispose(): void {
117
+ this.clear()
118
+ this.#onUpsert = null
119
+ }
120
+
121
+ [Symbol.dispose](): void {
122
+ this.dispose()
123
+ }
101
124
 
102
- this.#filesCache = [...this.#cache.values()].sort((a, b) => {
103
- const lenDiff = a.path.length - b.path.length
104
- if (lenDiff !== 0) return lenDiff
105
- // Within the same length bucket, index.ts barrel files go last so other
106
- // files are always processed before their barrel file.
107
- const aIsIndex = a.path.endsWith('/index.ts') || a.path === 'index.ts'
108
- const bIsIndex = b.path.endsWith('/index.ts') || b.path === 'index.ts'
109
- if (aIsIndex && !bIsIndex) return 1
110
- if (!aIsIndex && bIsIndex) return -1
111
- return 0
112
- })
113
- return this.#filesCache
125
+ /**
126
+ * All stored files in stable sort order (shortest path first, barrel files
127
+ * last within a length bucket). Returns a cached view — do not mutate.
128
+ */
129
+ get files(): Array<FileNode> {
130
+ return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))
114
131
  }
115
132
  }
@@ -1,7 +1,6 @@
1
1
  import type { CodeNode, FileNode } from '@kubb/ast'
2
2
  import { extractStringsFromNodes } from '@kubb/ast'
3
- import pLimit from 'p-limit'
4
- import { PARALLEL_CONCURRENCY_LIMIT } from './constants.ts'
3
+ import { AsyncEventEmitter } from '@internals/utils'
5
4
  import type { Parser } from './defineParser.ts'
6
5
 
7
6
  type ParseOptions = {
@@ -9,21 +8,29 @@ type ParseOptions = {
9
8
  extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
10
9
  }
11
10
 
12
- type RunOptions = ParseOptions & {
13
- /**
14
- * @default 'sequential'
15
- */
16
- mode?: 'sequential' | 'parallel'
17
- onStart?: (files: Array<FileNode>) => Promise<void> | void
18
- onEnd?: (files: Array<FileNode>) => Promise<void> | void
19
- onUpdate?: (params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }) => Promise<void> | void
11
+ export type FileProcessorEvents = {
12
+ start: [files: Array<FileNode>]
13
+ update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]
14
+ end: [files: Array<FileNode>]
15
+ }
16
+
17
+ export type ParsedFile = {
18
+ file: FileNode
19
+ source: string
20
+ processed: number
21
+ total: number
22
+ percentage: number
20
23
  }
21
24
 
22
25
  function joinSources(file: FileNode): string {
23
- return file.sources
24
- .map((item) => extractStringsFromNodes(item.nodes as Array<CodeNode>))
25
- .filter(Boolean)
26
- .join('\n\n')
26
+ const sources = file.sources
27
+ if (sources.length === 0) return ''
28
+ const parts: Array<string> = []
29
+ for (const source of sources) {
30
+ const s = extractStringsFromNodes(source.nodes as Array<CodeNode>)
31
+ if (s) parts.push(s)
32
+ }
33
+ return parts.join('\n\n')
27
34
  }
28
35
 
29
36
  /**
@@ -33,9 +40,9 @@ function joinSources(file: FileNode): string {
33
40
  * @internal
34
41
  */
35
42
  export class FileProcessor {
36
- readonly #limit = pLimit(PARALLEL_CONCURRENCY_LIMIT)
43
+ readonly events = new AsyncEventEmitter<FileProcessorEvents>()
37
44
 
38
- async parse(file: FileNode, { parsers, extension }: ParseOptions = {}): Promise<string> {
45
+ parse(file: FileNode, { parsers, extension }: ParseOptions = {}): string {
39
46
  const parseExtName = extension?.[file.extname] || undefined
40
47
 
41
48
  if (!parsers || !file.extname) {
@@ -51,36 +58,39 @@ export class FileProcessor {
51
58
  return parser.parse(file, { extname: parseExtName })
52
59
  }
53
60
 
54
- async run(files: Array<FileNode>, { parsers, mode = 'sequential', extension, onStart, onEnd, onUpdate }: RunOptions = {}): Promise<Array<FileNode>> {
55
- await onStart?.(files)
56
-
61
+ *stream(files: ReadonlyArray<FileNode>, options: ParseOptions = {}): Generator<ParsedFile> {
57
62
  const total = files.length
63
+ if (total === 0) return
64
+
58
65
  let processed = 0
66
+ for (const file of files) {
67
+ const source = this.parse(file, options)
68
+ processed++
59
69
 
60
- const processOne = async (file: FileNode) => {
61
- const source = await this.parse(file, { extension, parsers })
62
- const currentProcessed = ++processed
63
- const percentage = (currentProcessed / total) * 100
64
-
65
- await onUpdate?.({
66
- file,
67
- source,
68
- processed: currentProcessed,
69
- percentage,
70
- total,
71
- })
70
+ yield { file, source, processed, total, percentage: (processed / total) * 100 }
72
71
  }
72
+ }
73
73
 
74
- if (mode === 'sequential') {
75
- for (const file of files) {
76
- await processOne(file)
77
- }
78
- } else {
79
- await Promise.all(files.map((file) => this.#limit(() => processOne(file))))
74
+ async run(files: Array<FileNode>, options: ParseOptions = {}): Promise<Array<FileNode>> {
75
+ await this.events.emit('start', files)
76
+
77
+ for (const { file, source, processed, total, percentage } of this.stream(files, options)) {
78
+ await this.events.emit('update', { file, source, processed, percentage, total })
80
79
  }
81
80
 
82
- await onEnd?.(files)
81
+ await this.events.emit('end', files)
83
82
 
84
83
  return files
85
84
  }
85
+
86
+ /**
87
+ * Clears all registered event listeners.
88
+ */
89
+ dispose(): void {
90
+ this.events.removeAll()
91
+ }
92
+
93
+ [Symbol.dispose](): void {
94
+ this.dispose()
95
+ }
86
96
  }