nuxt-openapi-hyperfetch 0.1.6-alpha.1 → 0.2.7-alpha.1

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 (97) hide show
  1. package/CONTRIBUTING.md +291 -292
  2. package/INSTRUCTIONS.md +327 -327
  3. package/LICENSE +202 -202
  4. package/README.md +231 -227
  5. package/dist/cli/logger.d.ts +26 -0
  6. package/dist/cli/logger.js +36 -0
  7. package/dist/cli/logo.js +6 -6
  8. package/dist/generators/components/connector-generator/generator.d.ts +12 -0
  9. package/dist/generators/components/connector-generator/generator.js +116 -0
  10. package/dist/generators/components/connector-generator/templates.d.ts +18 -0
  11. package/dist/generators/components/connector-generator/templates.js +222 -0
  12. package/dist/generators/components/connector-generator/types.d.ts +32 -0
  13. package/dist/generators/components/connector-generator/types.js +7 -0
  14. package/dist/generators/components/schema-analyzer/index.d.ts +17 -0
  15. package/dist/generators/components/schema-analyzer/index.js +20 -0
  16. package/dist/generators/components/schema-analyzer/intent-detector.d.ts +17 -0
  17. package/dist/generators/components/schema-analyzer/intent-detector.js +143 -0
  18. package/dist/generators/components/schema-analyzer/openapi-reader.d.ts +11 -0
  19. package/dist/generators/components/schema-analyzer/openapi-reader.js +76 -0
  20. package/dist/generators/components/schema-analyzer/resource-grouper.d.ts +6 -0
  21. package/dist/generators/components/schema-analyzer/resource-grouper.js +132 -0
  22. package/dist/generators/components/schema-analyzer/schema-field-mapper.d.ts +35 -0
  23. package/dist/generators/components/schema-analyzer/schema-field-mapper.js +220 -0
  24. package/dist/generators/components/schema-analyzer/types.d.ts +156 -0
  25. package/dist/generators/components/schema-analyzer/types.js +7 -0
  26. package/dist/generators/nuxt-server/generator.d.ts +2 -1
  27. package/dist/generators/nuxt-server/generator.js +21 -21
  28. package/dist/generators/shared/runtime/apiHelpers.d.ts +98 -41
  29. package/dist/generators/shared/runtime/apiHelpers.js +97 -104
  30. package/dist/generators/shared/runtime/pagination.d.ts +168 -0
  31. package/dist/generators/shared/runtime/pagination.js +179 -0
  32. package/dist/generators/shared/runtime/useDeleteConnector.d.ts +16 -0
  33. package/dist/generators/shared/runtime/useDeleteConnector.js +93 -0
  34. package/dist/generators/shared/runtime/useDetailConnector.d.ts +14 -0
  35. package/dist/generators/shared/runtime/useDetailConnector.js +50 -0
  36. package/dist/generators/shared/runtime/useFormConnector.d.ts +19 -0
  37. package/dist/generators/shared/runtime/useFormConnector.js +113 -0
  38. package/dist/generators/shared/runtime/useListConnector.d.ts +25 -0
  39. package/dist/generators/shared/runtime/useListConnector.js +125 -0
  40. package/dist/generators/shared/runtime/zod-error-merger.d.ts +23 -0
  41. package/dist/generators/shared/runtime/zod-error-merger.js +106 -0
  42. package/dist/generators/shared/templates/api-callbacks-plugin.js +54 -11
  43. package/dist/generators/shared/templates/api-pagination-plugin.d.ts +51 -0
  44. package/dist/generators/shared/templates/api-pagination-plugin.js +152 -0
  45. package/dist/generators/use-async-data/generator.d.ts +2 -1
  46. package/dist/generators/use-async-data/generator.js +14 -14
  47. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +130 -17
  48. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +103 -13
  49. package/dist/generators/use-async-data/templates.js +20 -14
  50. package/dist/generators/use-fetch/generator.d.ts +2 -1
  51. package/dist/generators/use-fetch/generator.js +12 -12
  52. package/dist/generators/use-fetch/runtime/useApiRequest.js +149 -40
  53. package/dist/generators/use-fetch/templates.js +14 -14
  54. package/dist/index.js +25 -0
  55. package/dist/module/index.d.ts +4 -0
  56. package/dist/module/index.js +93 -0
  57. package/dist/module/types.d.ts +27 -0
  58. package/dist/module/types.js +1 -0
  59. package/docs/API-REFERENCE.md +886 -887
  60. package/docs/generated-components.md +615 -0
  61. package/docs/headless-composables-ui.md +569 -0
  62. package/eslint.config.js +13 -0
  63. package/package.json +29 -2
  64. package/src/cli/config.ts +140 -140
  65. package/src/cli/logger.ts +124 -66
  66. package/src/cli/logo.ts +25 -25
  67. package/src/cli/types.ts +50 -50
  68. package/src/generators/components/connector-generator/generator.ts +138 -0
  69. package/src/generators/components/connector-generator/templates.ts +254 -0
  70. package/src/generators/components/connector-generator/types.ts +34 -0
  71. package/src/generators/components/schema-analyzer/index.ts +44 -0
  72. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -0
  73. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -0
  74. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -0
  75. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -0
  76. package/src/generators/components/schema-analyzer/types.ts +177 -0
  77. package/src/generators/nuxt-server/generator.ts +272 -270
  78. package/src/generators/shared/runtime/apiHelpers.ts +535 -481
  79. package/src/generators/shared/runtime/pagination.ts +323 -0
  80. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -0
  81. package/src/generators/shared/runtime/useDetailConnector.ts +64 -0
  82. package/src/generators/shared/runtime/useFormConnector.ts +139 -0
  83. package/src/generators/shared/runtime/useListConnector.ts +148 -0
  84. package/src/generators/shared/runtime/zod-error-merger.ts +119 -0
  85. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -352
  86. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -0
  87. package/src/generators/use-async-data/generator.ts +205 -204
  88. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -214
  89. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -230
  90. package/src/generators/use-async-data/templates.ts +257 -250
  91. package/src/generators/use-fetch/generator.ts +170 -169
  92. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -234
  93. package/src/generators/use-fetch/templates.ts +214 -214
  94. package/src/index.ts +303 -265
  95. package/src/module/index.ts +133 -0
  96. package/src/module/types.ts +31 -0
  97. package/src/generators/tanstack-query/generator.ts +0 -11
@@ -3,8 +3,9 @@
3
3
  * Nuxt Runtime Helper - This file is copied to the generated output
4
4
  * It requires Nuxt 3 to be installed in the target project
5
5
  */
6
- import { watch } from 'vue';
6
+ import { watch, ref, computed } from 'vue';
7
7
  import { getGlobalHeaders, getGlobalBaseUrl, applyPick, applyRequestModifications, mergeCallbacks, } from '../../shared/runtime/apiHelpers.js';
8
+ import { getGlobalApiPagination, buildPaginationRequest, extractPaginationMetaFromBody, extractPaginationMetaFromHeaders, unwrapDataKey, } from '../../shared/runtime/pagination.js';
8
9
  /**
9
10
  * Enhanced useFetch wrapper with lifecycle callbacks and request interception
10
11
  *
@@ -42,18 +43,26 @@ import { getGlobalHeaders, getGlobalBaseUrl, applyPick, applyRequestModification
42
43
  * ```
43
44
  */
44
45
  export function useApiRequest(url, options) {
45
- const { onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, transform, pick, ...fetchOptions } = options || {};
46
- // Prepare request context for onRequest interceptor
46
+ const { onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, transform, pick, paginated, initialPage, initialPerPage, paginationConfig, ...fetchOptions } = options || {};
47
+ // Resolve URL value for callbacks and pattern matching
47
48
  const urlValue = typeof url === 'function' ? url() : url;
48
- const requestContext = {
49
- url: urlValue,
50
- method: fetchOptions.method || 'GET',
51
- body: fetchOptions.body,
52
- headers: fetchOptions.headers,
53
- query: fetchOptions.query,
54
- };
49
+ // ---------------------------------------------------------------------------
50
+ // Pagination setup
51
+ // ---------------------------------------------------------------------------
52
+ const activePaginationConfig = paginationConfig ?? (paginated ? getGlobalApiPagination() : null);
53
+ const page = ref(initialPage ?? activePaginationConfig?.request.defaults.page ?? 1);
54
+ const perPage = ref(initialPerPage ?? activePaginationConfig?.request.defaults.perPage ?? 20);
55
+ // Reactive pagination state (populated after response)
56
+ const paginationState = ref({
57
+ currentPage: page.value,
58
+ totalPages: 0,
59
+ total: 0,
60
+ perPage: perPage.value,
61
+ });
62
+ // ---------------------------------------------------------------------------
55
63
  // Merge local and global callbacks
56
- const mergedCallbacks = mergeCallbacks(urlValue, { onRequest, onSuccess, onError, onFinish }, skipGlobalCallbacks);
64
+ // ---------------------------------------------------------------------------
65
+ const mergedCallbacks = mergeCallbacks(urlValue, String(fetchOptions.method || 'GET'), { onRequest, onSuccess, onError, onFinish }, skipGlobalCallbacks);
57
66
  // Apply global headers configuration (from composable or plugin)
58
67
  const modifiedOptions = { ...fetchOptions };
59
68
  const globalHeaders = getGlobalHeaders();
@@ -70,30 +79,82 @@ export function useApiRequest(url, options) {
70
79
  if (!modifiedOptions.baseURL) {
71
80
  console.warn('[nuxt-openapi-hyperfetch] No baseURL configured. Set runtimeConfig.public.apiBaseUrl in nuxt.config.ts or pass baseURL in options.');
72
81
  }
73
- // Execute merged onRequest interceptor and apply modifications
82
+ // ---------------------------------------------------------------------------
83
+ // Inject pagination request params before every request
84
+ // ---------------------------------------------------------------------------
85
+ if (paginated && activePaginationConfig) {
86
+ const existingOnRequestForPagination = modifiedOptions.onRequest;
87
+ modifiedOptions.onRequest = async (ctx) => {
88
+ // Inject page/perPage according to sendAs strategy
89
+ const paginationPayload = buildPaginationRequest(page.value, perPage.value, activePaginationConfig);
90
+ if (paginationPayload.query) {
91
+ ctx.options.query = { ...ctx.options.query, ...paginationPayload.query };
92
+ }
93
+ if (paginationPayload.body) {
94
+ ctx.options.body = {
95
+ ...(ctx.options.body && typeof ctx.options.body === 'object' ? ctx.options.body : {}),
96
+ ...paginationPayload.body,
97
+ };
98
+ }
99
+ if (paginationPayload.headers) {
100
+ ctx.options.headers = { ...ctx.options.headers, ...paginationPayload.headers };
101
+ }
102
+ if (existingOnRequestForPagination) {
103
+ await existingOnRequestForPagination(ctx);
104
+ }
105
+ };
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Extract pagination metadata from headers after response (metaSource: 'headers')
109
+ // ---------------------------------------------------------------------------
110
+ if (paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'headers') {
111
+ const existingOnResponse = modifiedOptions.onResponse;
112
+ modifiedOptions.onResponse = async ({ response }) => {
113
+ const meta = extractPaginationMetaFromHeaders(response.headers, activePaginationConfig);
114
+ if (meta.total !== undefined)
115
+ paginationState.value.total = meta.total;
116
+ if (meta.totalPages !== undefined)
117
+ paginationState.value.totalPages = meta.totalPages;
118
+ if (meta.currentPage !== undefined)
119
+ paginationState.value.currentPage = meta.currentPage;
120
+ if (meta.perPage !== undefined)
121
+ paginationState.value.perPage = meta.perPage;
122
+ if (existingOnResponse) {
123
+ await existingOnResponse({ response });
124
+ }
125
+ };
126
+ }
127
+ // If page/perPage refs change, re-trigger useFetch (add them to watch array)
128
+ if (paginated) {
129
+ modifiedOptions.watch = [...(modifiedOptions.watch ?? []), page, perPage];
130
+ }
131
+ // Pass onRequest to useFetch's native interceptor so async callbacks are
132
+ // properly awaited by ofetch before the request is sent.
74
133
  if (mergedCallbacks.onRequest) {
75
- try {
76
- const result = mergedCallbacks.onRequest(requestContext);
77
- // Handle async onRequest
78
- if (result && typeof result === 'object' && 'then' in result) {
79
- result
80
- .then((modifications) => {
81
- if (modifications) {
82
- applyRequestModifications(modifiedOptions, modifications);
83
- }
84
- })
85
- .catch((error) => {
86
- console.error('Error in merged onRequest callback:', error);
87
- });
88
- }
89
- // Handle sync onRequest with return value
90
- else if (result && typeof result === 'object') {
91
- applyRequestModifications(modifiedOptions, result);
134
+ const existingOnRequest = modifiedOptions.onRequest;
135
+ modifiedOptions.onRequest = async ({ options: fetchOpts }) => {
136
+ // Build context from the live options at request time
137
+ const requestContext = {
138
+ url: urlValue,
139
+ method: String(fetchOpts.method || modifiedOptions.method || 'GET'),
140
+ body: fetchOpts.body,
141
+ headers: fetchOpts.headers,
142
+ query: fetchOpts.query,
143
+ };
144
+ try {
145
+ const modifications = await mergedCallbacks.onRequest(requestContext);
146
+ if (modifications && typeof modifications === 'object') {
147
+ applyRequestModifications(fetchOpts, modifications);
148
+ }
92
149
  }
93
- }
94
- catch (error) {
95
- console.error('Error in merged onRequest callback:', error);
96
- }
150
+ catch (error) {
151
+ console.error('Error in merged onRequest callback:', error);
152
+ }
153
+ // Chain any pre-existing onRequest interceptor
154
+ if (existingOnRequest) {
155
+ await existingOnRequest({ options: fetchOpts });
156
+ }
157
+ };
97
158
  }
98
159
  // Make the actual request using Nuxt's useFetch
99
160
  const result = useFetch(url, modifiedOptions);
@@ -106,7 +167,22 @@ export function useApiRequest(url, options) {
106
167
  const [prevData, prevError, prevPending] = prev ?? [undefined, undefined, undefined];
107
168
  // Apply transformations when data arrives
108
169
  if (data && data !== prevData) {
109
- let processedData = data;
170
+ // Unwrap dataKey for paginated body-based responses BEFORE pick/transform
171
+ let processedData = paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'body'
172
+ ? unwrapDataKey(data, activePaginationConfig)
173
+ : data;
174
+ // Extract body-based pagination meta from raw response
175
+ if (paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'body') {
176
+ const meta = extractPaginationMetaFromBody(data, activePaginationConfig);
177
+ if (meta.total !== undefined)
178
+ paginationState.value.total = meta.total;
179
+ if (meta.totalPages !== undefined)
180
+ paginationState.value.totalPages = meta.totalPages;
181
+ if (meta.currentPage !== undefined)
182
+ paginationState.value.currentPage = meta.currentPage;
183
+ if (meta.perPage !== undefined)
184
+ paginationState.value.perPage = meta.perPage;
185
+ }
110
186
  // Step 1: Apply pick if specified
111
187
  if (pick) {
112
188
  processedData = applyPick(processedData, pick);
@@ -123,7 +199,8 @@ export function useApiRequest(url, options) {
123
199
  // Update transformed data ref
124
200
  transformedData.value = processedData;
125
201
  // onSuccess - when data arrives and no error (using merged callback)
126
- if (!error && !successExecuted && mergedCallbacks.onSuccess) {
202
+ // NOTE: data !== null/undefined check instead of truthy to handle 0, false, ''
203
+ if (data != null && !error && !successExecuted && mergedCallbacks.onSuccess) {
127
204
  successExecuted = true;
128
205
  try {
129
206
  await mergedCallbacks.onSuccess(processedData);
@@ -146,9 +223,9 @@ export function useApiRequest(url, options) {
146
223
  // onFinish - when request completes (was pending, now not) (using merged callback)
147
224
  if (prevPending && !pending && mergedCallbacks.onFinish) {
148
225
  const finishContext = {
149
- data: transformedData.value || undefined,
150
- error: error || undefined,
151
- success: !!transformedData.value && !error,
226
+ data: transformedData.value ?? undefined,
227
+ error: error ?? undefined,
228
+ success: data != null && !error,
152
229
  };
153
230
  try {
154
231
  await mergedCallbacks.onFinish(finishContext);
@@ -158,9 +235,41 @@ export function useApiRequest(url, options) {
158
235
  }
159
236
  }
160
237
  }, { immediate: true });
161
- // Return result with transformed data
162
- return {
238
+ // Return result with transformed data and optional pagination
239
+ const baseResult = {
163
240
  ...result,
164
241
  data: transformedData,
165
242
  };
243
+ if (!paginated)
244
+ return baseResult;
245
+ // Pagination computed helpers
246
+ const hasNextPage = computed(() => paginationState.value.currentPage < paginationState.value.totalPages);
247
+ const hasPrevPage = computed(() => paginationState.value.currentPage > 1);
248
+ const goToPage = (n) => {
249
+ page.value = n;
250
+ successExecuted = false;
251
+ errorExecuted = false;
252
+ };
253
+ const nextPage = () => { if (hasNextPage.value)
254
+ goToPage(page.value + 1); };
255
+ const prevPage = () => { if (hasPrevPage.value)
256
+ goToPage(page.value - 1); };
257
+ const setPerPage = (n) => {
258
+ perPage.value = n;
259
+ page.value = 1;
260
+ successExecuted = false;
261
+ errorExecuted = false;
262
+ };
263
+ return {
264
+ ...baseResult,
265
+ pagination: computed(() => ({
266
+ ...paginationState.value,
267
+ hasNextPage: hasNextPage.value,
268
+ hasPrevPage: hasPrevPage.value,
269
+ })),
270
+ goToPage,
271
+ nextPage,
272
+ prevPage,
273
+ setPerPage,
274
+ };
166
275
  }
@@ -2,18 +2,18 @@
2
2
  * Generate file header with auto-generation warning
3
3
  */
4
4
  function generateFileHeader() {
5
- return `/**
6
- * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
7
- *
8
- * This file was automatically generated by nuxt-openapi-generator.
9
- * Any manual changes will be overwritten on the next generation.
10
- *
11
- * @generated by nuxt-openapi-generator
12
- * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
13
- */
14
-
15
- /* eslint-disable */
16
- // @ts-nocheck
5
+ return `/**
6
+ * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
7
+ *
8
+ * This file was automatically generated by nuxt-openapi-generator.
9
+ * Any manual changes will be overwritten on the next generation.
10
+ *
11
+ * @generated by nuxt-openapi-generator
12
+ * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
13
+ */
14
+
15
+ /* eslint-disable */
16
+ // @ts-nocheck
17
17
  `;
18
18
  }
19
19
  /**
@@ -95,8 +95,8 @@ function generateFunctionBody(method, options) {
95
95
  const fetchOptions = generateFetchOptions(method, options);
96
96
  const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
97
97
  const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
98
- return `${description}export const ${method.composableName} = (${args}) => {${pInit}
99
- return useApiRequest${responseTypeGeneric}(${url}, ${fetchOptions})
98
+ return `${description}export const ${method.composableName} = (${args}) => {${pInit}
99
+ return useApiRequest${responseTypeGeneric}(${url}, ${fetchOptions})
100
100
  }`;
101
101
  }
102
102
  /**
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { generateOpenApiFiles, generateHeyApiFiles, checkJavaInstalled } from '.
5
5
  import { generateUseFetchComposables } from './generators/use-fetch/generator.js';
6
6
  import { generateUseAsyncDataComposables } from './generators/use-async-data/generator.js';
7
7
  import { generateNuxtServerRoutes } from './generators/nuxt-server/generator.js';
8
+ import { generateConnectors } from './generators/components/connector-generator/generator.js';
8
9
  import { promptInitialInputs, promptInputPath, promptComposablesSelection, promptServerRoutePath, promptBffConfig, promptGeneratorBackend, } from './cli/prompts.js';
9
10
  import { MESSAGES } from './cli/messages.js';
10
11
  import { displayLogo } from './cli/logo.js';
@@ -210,4 +211,28 @@ program
210
211
  process.exit(1);
211
212
  }
212
213
  });
214
+ program
215
+ .command('connectors')
216
+ .description('Generate headless connector composables from an OpenAPI spec')
217
+ .requiredOption('-i, --input <path>', 'Path to OpenAPI YAML or JSON spec')
218
+ .requiredOption('-o, --output <path>', 'Output directory for connector composables')
219
+ .option('--composables-dir <relPath>', 'Relative path from output dir to useAsyncData composables (default: ../use-async-data)')
220
+ .option('--runtime-dir <relPath>', 'Relative path from output dir where runtime helpers are copied (default: ../runtime)')
221
+ .action(async (options) => {
222
+ try {
223
+ displayLogo();
224
+ p.intro('Generating connector composables…');
225
+ await generateConnectors({
226
+ inputSpec: options.input,
227
+ outputDir: options.output,
228
+ composablesRelDir: options.composablesDir,
229
+ runtimeRelDir: options.runtimeDir,
230
+ });
231
+ p.outro('Done!');
232
+ }
233
+ catch (error) {
234
+ p.log.error(`Error: ${String(error)}`);
235
+ process.exit(1);
236
+ }
237
+ });
213
238
  program.parse();
@@ -0,0 +1,4 @@
1
+ import type { ModuleOptions } from './types.js';
2
+ declare const _default: import("@nuxt/schema").NuxtModule<ModuleOptions, ModuleOptions, false>;
3
+ export default _default;
4
+ export type { ModuleOptions };
@@ -0,0 +1,93 @@
1
+ import { defineNuxtModule, addImportsDir } from '@nuxt/kit';
2
+ import { execSync } from 'child_process';
3
+ import * as path from 'path';
4
+ import { checkJavaInstalled } from '../generate.js';
5
+ import { generateUseFetchComposables } from '../generators/use-fetch/generator.js';
6
+ import { generateUseAsyncDataComposables } from '../generators/use-async-data/generator.js';
7
+ import { generateNuxtServerRoutes } from '../generators/nuxt-server/generator.js';
8
+ import { createConsoleLogger } from '../cli/logger.js';
9
+ export default defineNuxtModule({
10
+ meta: {
11
+ name: 'nuxt-openapi-hyperfetch',
12
+ configKey: 'openApiHyperFetch',
13
+ },
14
+ defaults: {
15
+ output: './composables/api',
16
+ generators: ['useFetch', 'useAsyncData'],
17
+ backend: 'heyapi',
18
+ enableDevBuild: true,
19
+ enableProductionBuild: true,
20
+ enableAutoGeneration: false,
21
+ enableAutoImport: true,
22
+ },
23
+ setup(options, nuxt) {
24
+ // --- Guard: input is required ---
25
+ if (!options.input) {
26
+ console.warn('[nuxt-openapi-hyperfetch] No input configured — skipping generation.\n' +
27
+ "Add `openApiHyperFetch: { input: './swagger.yaml' }` to your nuxt.config.ts");
28
+ return;
29
+ }
30
+ const resolvedInput = path.resolve(nuxt.options.rootDir, options.input);
31
+ const resolvedOutput = path.resolve(nuxt.options.rootDir, options.output);
32
+ const composablesOutputDir = path.join(resolvedOutput, 'composables');
33
+ const selectedGenerators = options.generators ?? ['useFetch', 'useAsyncData'];
34
+ const backend = options.backend ?? 'heyapi';
35
+ const logger = createConsoleLogger();
36
+ // --- Core generation function ---
37
+ const runGeneration = async () => {
38
+ logger.log.info('Generating OpenAPI composables...');
39
+ // 1. Generate OpenAPI SDK files
40
+ if (backend === 'official') {
41
+ if (!checkJavaInstalled()) {
42
+ throw new Error('[nuxt-openapi-hyperfetch] Java not found. The official backend requires Java 11+.\n' +
43
+ 'Install from: https://adoptium.net or set backend: "heyapi" in nuxt.config.ts');
44
+ }
45
+ execSync(`npx @openapitools/openapi-generator-cli generate -i "${resolvedInput}" -g typescript-fetch -o "${resolvedOutput}"`, { stdio: 'inherit' });
46
+ }
47
+ else {
48
+ const { createClient } = await import('@hey-api/openapi-ts');
49
+ await createClient({
50
+ input: resolvedInput,
51
+ output: resolvedOutput,
52
+ plugins: ['@hey-api/typescript', '@hey-api/sdk'],
53
+ });
54
+ }
55
+ // 2. Run selected composable generators
56
+ const genOptions = { backend };
57
+ if (selectedGenerators.includes('useFetch')) {
58
+ await generateUseFetchComposables(resolvedOutput, path.join(composablesOutputDir, 'use-fetch'), genOptions, logger);
59
+ }
60
+ if (selectedGenerators.includes('useAsyncData')) {
61
+ await generateUseAsyncDataComposables(resolvedOutput, path.join(composablesOutputDir, 'use-async-data'), genOptions, logger);
62
+ }
63
+ if (selectedGenerators.includes('nuxtServer')) {
64
+ const serverRoutePath = path.resolve(nuxt.options.rootDir, options.serverRoutePath ?? 'server/routes/api');
65
+ await generateNuxtServerRoutes(resolvedOutput, serverRoutePath, { enableBff: options.enableBff, backend }, logger);
66
+ }
67
+ };
68
+ // --- Hooks: dev build / production build ---
69
+ const isDev = nuxt.options.dev;
70
+ if ((isDev && options.enableDevBuild) || (!isDev && options.enableProductionBuild)) {
71
+ nuxt.hook('build:before', runGeneration);
72
+ }
73
+ // --- Hook: auto-regeneration on input file change (dev only) ---
74
+ if (options.enableAutoGeneration) {
75
+ nuxt.hook('builder:watch', async (event, watchedPath) => {
76
+ const absWatchedPath = path.resolve(nuxt.options.rootDir, watchedPath);
77
+ if (absWatchedPath === resolvedInput && (event === 'change' || event === 'add')) {
78
+ logger.log.info(`Detected change in ${watchedPath}, regenerating composables...`);
79
+ await runGeneration();
80
+ }
81
+ });
82
+ }
83
+ // --- Auto-import: register composable directories ---
84
+ if (options.enableAutoImport !== false) {
85
+ if (selectedGenerators.includes('useFetch')) {
86
+ addImportsDir(path.join(composablesOutputDir, 'use-fetch', 'composables'));
87
+ }
88
+ if (selectedGenerators.includes('useAsyncData')) {
89
+ addImportsDir(path.join(composablesOutputDir, 'use-async-data', 'composables'));
90
+ }
91
+ }
92
+ },
93
+ });
@@ -0,0 +1,27 @@
1
+ import type { GeneratorConfig } from '../cli/config.js';
2
+ /**
3
+ * Configuration options for the nuxt-openapi-hyperfetch Nuxt module.
4
+ * Extends the CLI GeneratorConfig so the same fields work in both nxh.config.js and nuxt.config.ts.
5
+ */
6
+ export interface ModuleOptions extends GeneratorConfig {
7
+ /**
8
+ * Generate composables before the dev server starts.
9
+ * @default true
10
+ */
11
+ enableDevBuild?: boolean;
12
+ /**
13
+ * Generate composables before the production build.
14
+ * @default true
15
+ */
16
+ enableProductionBuild?: boolean;
17
+ /**
18
+ * Watch the input file and regenerate composables on change (dev mode only).
19
+ * @default false
20
+ */
21
+ enableAutoGeneration?: boolean;
22
+ /**
23
+ * Automatically import generated useFetch/useAsyncData composables project-wide.
24
+ * @default true
25
+ */
26
+ enableAutoImport?: boolean;
27
+ }
@@ -0,0 +1 @@
1
+ export {};