nuxt-openapi-hyperfetch 1.0.2 → 1.0.4

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.
@@ -0,0 +1,207 @@
1
+ import { camelCase, pascalCase } from 'change-case';
2
+ import { mapColumnsFromSchema, mapFieldsFromSchema, buildZodSchema, } from '../components/schema-analyzer/index.js';
3
+ function toConnectorName(tag) {
4
+ const pascal = pascalCase(tag);
5
+ const plural = /(?:s|x|z|ch|sh)$/i.test(pascal) ? `${pascal}es` : `${pascal}s`;
6
+ return `use${plural}Connector`;
7
+ }
8
+ function cloneResource(resource) {
9
+ return {
10
+ ...resource,
11
+ endpoints: [...resource.endpoints],
12
+ columns: [...resource.columns],
13
+ formFields: {
14
+ ...(resource.formFields.create ? { create: [...resource.formFields.create] } : {}),
15
+ ...(resource.formFields.update ? { update: [...resource.formFields.update] } : {}),
16
+ },
17
+ zodSchemas: { ...resource.zodSchemas },
18
+ };
19
+ }
20
+ function getRefNameFromSchema(schema) {
21
+ if (!schema) {
22
+ return undefined;
23
+ }
24
+ const directRef = schema['x-ref-name'];
25
+ if (typeof directRef === 'string') {
26
+ return directRef;
27
+ }
28
+ const items = schema.items;
29
+ if (items && typeof items === 'object') {
30
+ const itemRef = items['x-ref-name'];
31
+ if (typeof itemRef === 'string') {
32
+ return itemRef;
33
+ }
34
+ }
35
+ return undefined;
36
+ }
37
+ function rebuildDerived(resource) {
38
+ const schemaForColumns = resource.listEndpoint?.responseSchema ?? resource.detailEndpoint?.responseSchema;
39
+ resource.columns = schemaForColumns ? mapColumnsFromSchema(schemaForColumns) : [];
40
+ resource.formFields = {
41
+ ...(resource.createEndpoint?.requestBodySchema
42
+ ? { create: mapFieldsFromSchema(resource.createEndpoint.requestBodySchema) }
43
+ : {}),
44
+ ...(resource.updateEndpoint?.requestBodySchema
45
+ ? { update: mapFieldsFromSchema(resource.updateEndpoint.requestBodySchema) }
46
+ : {}),
47
+ };
48
+ resource.zodSchemas = {
49
+ ...(resource.createEndpoint?.requestBodySchema
50
+ ? { create: buildZodSchema(resource.createEndpoint.requestBodySchema) }
51
+ : {}),
52
+ ...(resource.updateEndpoint?.requestBodySchema
53
+ ? { update: buildZodSchema(resource.updateEndpoint.requestBodySchema) }
54
+ : {}),
55
+ };
56
+ resource.itemTypeName =
57
+ getRefNameFromSchema(resource.detailEndpoint?.responseSchema) ??
58
+ getRefNameFromSchema(resource.listEndpoint?.responseSchema) ??
59
+ undefined;
60
+ }
61
+ function expectedMethodsForOperation(operationName) {
62
+ switch (operationName) {
63
+ case 'getAll':
64
+ case 'get':
65
+ return ['GET'];
66
+ case 'create':
67
+ return ['POST'];
68
+ case 'update':
69
+ return ['PUT', 'PATCH'];
70
+ case 'delete':
71
+ return ['DELETE'];
72
+ }
73
+ }
74
+ function selectByIntent(operationName, candidates) {
75
+ if (operationName === 'getAll') {
76
+ const preferred = candidates.filter((c) => c.intent === 'list');
77
+ return preferred.length > 0 ? preferred : candidates;
78
+ }
79
+ if (operationName === 'get') {
80
+ const preferred = candidates.filter((c) => c.intent === 'detail');
81
+ return preferred.length > 0 ? preferred : candidates;
82
+ }
83
+ if (operationName === 'update') {
84
+ const put = candidates.filter((c) => c.method === 'PUT');
85
+ return put.length > 0 ? put : candidates;
86
+ }
87
+ return candidates;
88
+ }
89
+ function buildEndpointIndex(resourceMap) {
90
+ const byOperationId = new Map();
91
+ const byPath = new Map();
92
+ for (const resource of resourceMap.values()) {
93
+ for (const endpoint of resource.endpoints) {
94
+ byOperationId.set(endpoint.operationId, endpoint);
95
+ const list = byPath.get(endpoint.path) ?? [];
96
+ list.push(endpoint);
97
+ byPath.set(endpoint.path, list);
98
+ }
99
+ }
100
+ return { byOperationId, byPath };
101
+ }
102
+ function resolveEndpoint(operationName, operationConfig, endpointIndex, resourceName) {
103
+ if (operationConfig.operationId && operationConfig.path) {
104
+ throw new Error(`[connectors] Resource "${resourceName}" operation "${operationName}" cannot define both operationId and path`);
105
+ }
106
+ if (!operationConfig.operationId && !operationConfig.path) {
107
+ throw new Error(`[connectors] Resource "${resourceName}" operation "${operationName}" must define operationId or path`);
108
+ }
109
+ if (operationConfig.operationId) {
110
+ const endpoint = endpointIndex.byOperationId.get(operationConfig.operationId);
111
+ if (!endpoint) {
112
+ throw new Error(`[connectors] Resource "${resourceName}" operation "${operationName}" references unknown operationId "${operationConfig.operationId}"`);
113
+ }
114
+ return endpoint;
115
+ }
116
+ const candidates = endpointIndex.byPath.get(operationConfig.path) ?? [];
117
+ if (candidates.length === 0) {
118
+ throw new Error(`[connectors] Resource "${resourceName}" operation "${operationName}" references unknown path "${operationConfig.path}"`);
119
+ }
120
+ const validMethods = expectedMethodsForOperation(operationName);
121
+ const byMethod = candidates.filter((c) => validMethods.includes(c.method));
122
+ if (byMethod.length === 0) {
123
+ throw new Error(`[connectors] Resource "${resourceName}" operation "${operationName}" path "${operationConfig.path}" has no compatible method (${validMethods.join('/')})`);
124
+ }
125
+ const prioritized = selectByIntent(operationName, byMethod);
126
+ return prioritized[0];
127
+ }
128
+ function setOperationEndpoint(resource, operationName, endpoint) {
129
+ switch (operationName) {
130
+ case 'getAll':
131
+ resource.listEndpoint = endpoint;
132
+ break;
133
+ case 'get':
134
+ resource.detailEndpoint = endpoint;
135
+ break;
136
+ case 'create':
137
+ resource.createEndpoint = endpoint;
138
+ break;
139
+ case 'update':
140
+ resource.updateEndpoint = endpoint;
141
+ break;
142
+ case 'delete':
143
+ resource.deleteEndpoint = endpoint;
144
+ break;
145
+ }
146
+ if (!resource.endpoints.some((ep) => ep.operationId === endpoint.operationId)) {
147
+ resource.endpoints.push(endpoint);
148
+ }
149
+ }
150
+ function createResourceSkeleton(resourceKey) {
151
+ return {
152
+ name: pascalCase(resourceKey),
153
+ tag: resourceKey,
154
+ composableName: toConnectorName(resourceKey),
155
+ endpoints: [],
156
+ columns: [],
157
+ formFields: {},
158
+ zodSchemas: {},
159
+ };
160
+ }
161
+ function applyResourceOverrides(target, resourceName, config, endpointIndex) {
162
+ const operations = config.operations ?? {};
163
+ for (const [operationName, operationConfig] of Object.entries(operations)) {
164
+ if (!operationConfig) {
165
+ continue;
166
+ }
167
+ const endpoint = resolveEndpoint(operationName, operationConfig, endpointIndex, resourceName);
168
+ setOperationEndpoint(target, operationName, endpoint);
169
+ }
170
+ rebuildDerived(target);
171
+ return target;
172
+ }
173
+ function normalizeConfig(config) {
174
+ return {
175
+ strategy: config?.strategy ?? 'hybrid',
176
+ ...config,
177
+ };
178
+ }
179
+ export function resolveConnectorResourceMap(baseResourceMap, connectorsConfig) {
180
+ if (!connectorsConfig) {
181
+ return baseResourceMap;
182
+ }
183
+ const normalized = normalizeConfig(connectorsConfig);
184
+ const resourceConfigs = normalized.resources ?? {};
185
+ const endpointIndex = buildEndpointIndex(baseResourceMap);
186
+ if (normalized.strategy === 'manual') {
187
+ const manualMap = new Map();
188
+ for (const [resourceName, resourceConfig] of Object.entries(resourceConfigs)) {
189
+ const resourceKey = camelCase(resourceName);
190
+ const baseResource = baseResourceMap.get(resourceKey);
191
+ const target = baseResource
192
+ ? cloneResource(baseResource)
193
+ : createResourceSkeleton(resourceName);
194
+ applyResourceOverrides(target, resourceName, resourceConfig, endpointIndex);
195
+ manualMap.set(resourceKey, target);
196
+ }
197
+ return manualMap;
198
+ }
199
+ const hybridMap = new Map([...baseResourceMap.entries()].map(([key, value]) => [key, cloneResource(value)]));
200
+ for (const [resourceName, resourceConfig] of Object.entries(resourceConfigs)) {
201
+ const resourceKey = camelCase(resourceName);
202
+ const target = hybridMap.get(resourceKey) ?? createResourceSkeleton(resourceName);
203
+ applyResourceOverrides(target, resourceName, resourceConfig, endpointIndex);
204
+ hybridMap.set(resourceKey, target);
205
+ }
206
+ return hybridMap;
207
+ }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import { format } from 'prettier';
5
5
  import { analyzeSpec } from '../components/schema-analyzer/index.js';
6
6
  import { generateConnectorFile, connectorFileName, generateConnectorIndexFile, } from './templates.js';
7
+ import { resolveConnectorResourceMap } from './config-resolver.js';
7
8
  import { createClackLogger } from '../../cli/logger.js';
8
9
  // Runtime files that must be copied to the user's project
9
10
  const RUNTIME_FILES = [
@@ -44,7 +45,8 @@ export async function generateConnectors(options, logger = createClackLogger())
44
45
  const runtimeRelDir = options.runtimeRelDir ?? '../runtime';
45
46
  // ── 1. Analyze spec ───────────────────────────────────────────────────────
46
47
  spinner.start('Analyzing OpenAPI spec');
47
- const resourceMap = analyzeSpec(options.inputSpec);
48
+ const baseResourceMap = analyzeSpec(options.inputSpec);
49
+ const resourceMap = resolveConnectorResourceMap(baseResourceMap, options.connectorsConfig);
48
50
  spinner.stop(`Found ${resourceMap.size} resource(s)`);
49
51
  if (resourceMap.size === 0) {
50
52
  logger.log.warn('No resources found in spec — nothing to generate');
@@ -150,6 +150,7 @@ function buildOptionsInterface(resource) {
150
150
  if (resource.listEndpoint && hasColumns) {
151
151
  fields.push(` columnLabels?: Record<string, string>;`);
152
152
  fields.push(` columnLabel?: (key: string) => string;`);
153
+ fields.push(` getAllRequestOptions?: Record<string, unknown>;`);
153
154
  }
154
155
  if (resource.createEndpoint && resource.zodSchemas.create) {
155
156
  const pascal = pascalCase(resource.name);
@@ -218,7 +219,10 @@ function buildFunctionBody(resource) {
218
219
  // Options destructure
219
220
  const optionKeys = [];
220
221
  if (resource.listEndpoint && hasColumns) {
221
- optionKeys.push('columnLabels', 'columnLabel');
222
+ optionKeys.push('columnLabels', 'columnLabel', 'getAllRequestOptions');
223
+ }
224
+ else if (resource.listEndpoint) {
225
+ optionKeys.push('getAllRequestOptions');
222
226
  }
223
227
  if (resource.createEndpoint && resource.zodSchemas.create) {
224
228
  optionKeys.push('createSchema');
@@ -257,7 +261,7 @@ function buildFunctionBody(resource) {
257
261
  const labelArgs = hasColumns ? 'columnLabels, columnLabel' : '';
258
262
  const allArgs = [columnsArg, labelArgs].filter(Boolean).join(', ');
259
263
  const opts = allArgs ? `{ ${allArgs} }` : '{}';
260
- lines.push(` const isFactory = typeof paramsOrSource === 'function';`, ` const listFactory = isFactory`, ` ? (paramsOrSource as () => unknown)`, ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName});`, ` const getAll = useGetAllConnector(listFactory, ${opts}) as unknown as GetAllConnectorReturn<${pascal}>;`);
264
+ lines.push(` const isFactory = typeof paramsOrSource === 'function';`, ` const listFactory = isFactory`, ` ? (paramsOrSource as () => unknown)`, ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName}, { paginated: true, ...(getAllRequestOptions ?? {}) });`, ` const getAll = useGetAllConnector(listFactory, ${opts}) as unknown as GetAllConnectorReturn<${pascal}>;`);
261
265
  }
262
266
  else {
263
267
  lines.push(` const getAll = useGetAllConnector(() => ({}), {}) as unknown as GetAllConnectorReturn<${pascal}>;`);
@@ -4,6 +4,7 @@
4
4
  * The Connector Generator reads the ResourceMap produced by the Schema Analyzer
5
5
  * and writes one `use{Resource}Connector.ts` file per resource using $fetch for mutations.
6
6
  */
7
+ import type { ConnectorsConfig } from '../../config/types.js';
7
8
  export interface ConnectorGeneratorOptions {
8
9
  /** Absolute or relative path to the OpenAPI YAML/JSON spec */
9
10
  inputSpec: string;
@@ -24,6 +25,8 @@ export interface ConnectorGeneratorOptions {
24
25
  * useRuntimeConfig().public.apiBaseUrl at runtime.
25
26
  */
26
27
  baseUrl?: string;
28
+ /** Advanced connectors configuration (manual/hybrid, custom resources, overloads). */
29
+ connectorsConfig?: ConnectorsConfig;
27
30
  }
28
31
  export interface ConnectorFileInfo {
29
32
  /** PascalCase resource name. E.g. 'Pet' */
package/dist/index.js CHANGED
@@ -9,7 +9,8 @@ import { generateConnectors } from './generators/connectors/generator.js';
9
9
  import { promptInitialInputs, promptInputPath, promptComposablesSelection, promptServerRoutePath, promptBffConfig, promptGeneratorBackend, promptConnectors, } from './cli/prompts.js';
10
10
  import { MESSAGES } from './cli/messages.js';
11
11
  import { displayLogo } from './cli/logo.js';
12
- import { loadConfig, mergeConfig, parseTags, parseGenerators } from './cli/config.js';
12
+ import { loadConfig, mergeConfig, parseTags, parseGenerators, normalizeGenerators, } from './cli/config.js';
13
+ import { hasConnectorsConfig } from './config/connectors.js';
13
14
  const program = new Command();
14
15
  program.name('nxh').description('Nuxt OpenAPI Hyperfetch generator').version('1.0.0');
15
16
  program
@@ -25,7 +26,7 @@ program
25
26
  .option('--dry-run', 'Preview changes without writing files', false)
26
27
  .option('-v, --verbose', 'Enable verbose logging', false)
27
28
  .option('--watch', 'Watch mode - regenerate on file changes', false)
28
- .option('--generators <types>', 'Generators to use: useFetch,useAsyncData,nuxtServer')
29
+ .option('--generators <types>', 'Generators to use: useFetch,useAsyncData,nuxtServer,connectors')
29
30
  .option('--connectors', 'Generate headless UI connectors on top of useAsyncData', false)
30
31
  .option('--server-route-path <path>', 'Server route path (for nuxtServer mode)')
31
32
  .option('--enable-bff', 'Enable BFF pattern (for nuxtServer mode)', false)
@@ -86,16 +87,21 @@ program
86
87
  }
87
88
  // 1. Determine composables to generate FIRST
88
89
  let composables;
90
+ let generateConnectorsFlag = false;
91
+ const connectorsConfigured = hasConnectorsConfig(config.connectors);
89
92
  if (config.generators) {
90
- // filter out 'connectors' — handled separately below
91
- composables = config.generators.filter((g) => g !== 'connectors');
93
+ const normalized = normalizeGenerators(config.generators, config.createUseAsyncDataConnectors);
94
+ composables = normalized.composables;
95
+ generateConnectorsFlag = normalized.generateConnectors;
92
96
  if (config.verbose) {
93
97
  console.log(`Using generators from config: ${composables.join(', ')}`);
94
98
  }
95
99
  }
96
100
  else {
97
101
  const result = await promptComposablesSelection();
98
- composables = result.composables;
102
+ const normalized = normalizeGenerators(result.composables, config.createUseAsyncDataConnectors);
103
+ composables = normalized.composables;
104
+ generateConnectorsFlag = normalized.generateConnectors;
99
105
  }
100
106
  if (composables.length === 0) {
101
107
  p.outro(MESSAGES.outro.noComposables);
@@ -117,14 +123,24 @@ program
117
123
  inputPath = await promptInputPath(config.input);
118
124
  outputPath = config.output ?? './swagger';
119
125
  }
120
- // 3. Ask whether to generate headless connectors (only if useAsyncData selected)
121
- let generateConnectorsFlag = false;
126
+ // 3. Ask whether to generate headless connectors (only if still unresolved)
122
127
  if (composables.includes('useAsyncData')) {
123
- if (config.createUseAsyncDataConnectors !== undefined) {
128
+ const hasExplicitConnectorsInGenerators = config.generators?.includes('connectors') ?? false;
129
+ const shouldPromptConnectors = !generateConnectorsFlag &&
130
+ config.createUseAsyncDataConnectors === undefined &&
131
+ !hasExplicitConnectorsInGenerators &&
132
+ !connectorsConfigured;
133
+ if (shouldPromptConnectors) {
134
+ generateConnectorsFlag = await promptConnectors();
135
+ }
136
+ else if (connectorsConfigured) {
137
+ generateConnectorsFlag = true;
138
+ }
139
+ else if (config.createUseAsyncDataConnectors !== undefined) {
124
140
  generateConnectorsFlag = config.createUseAsyncDataConnectors;
125
141
  }
126
- else {
127
- generateConnectorsFlag = await promptConnectors();
142
+ else if (hasExplicitConnectorsInGenerators) {
143
+ generateConnectorsFlag = true;
128
144
  }
129
145
  }
130
146
  // 4. Ask for server route path if nuxtServer is selected
@@ -203,6 +219,7 @@ program
203
219
  outputDir: `${composablesOutputDir}/connectors`,
204
220
  composablesRelDir: '../use-async-data/composables',
205
221
  runtimeRelDir: '../../runtime',
222
+ connectorsConfig: config.connectors,
206
223
  });
207
224
  spinner.stop('✓ Generated headless UI connectors');
208
225
  }
@@ -7,6 +7,8 @@ import { generateUseAsyncDataComposables } from '../generators/use-async-data/ge
7
7
  import { generateNuxtServerRoutes } from '../generators/nuxt-server/generator.js';
8
8
  import { generateConnectors } from '../generators/connectors/generator.js';
9
9
  import { createConsoleLogger } from '../cli/logger.js';
10
+ import { normalizeGenerators } from '../cli/config.js';
11
+ import { isConnectorsRequested } from '../config/connectors.js';
10
12
  export default defineNuxtModule({
11
13
  meta: {
12
14
  name: 'nuxt-openapi-hyperfetch',
@@ -32,7 +34,10 @@ export default defineNuxtModule({
32
34
  const resolvedInput = path.resolve(nuxt.options.rootDir, options.input);
33
35
  const resolvedOutput = path.resolve(nuxt.options.rootDir, options.output);
34
36
  const composablesOutputDir = path.join(resolvedOutput, 'composables');
35
- const selectedGenerators = options.generators ?? ['useFetch', 'useAsyncData'];
37
+ const normalized = normalizeGenerators(options.generators ?? ['useFetch', 'useAsyncData'], options.createUseAsyncDataConnectors);
38
+ const selectedGenerators = normalized.composables;
39
+ const generateConnectorsFlag = normalized.generateConnectors;
40
+ const connectorsRequested = isConnectorsRequested(options);
36
41
  const backend = options.backend ?? 'heyapi';
37
42
  const logger = createConsoleLogger();
38
43
  // --- Core generation function ---
@@ -67,13 +72,15 @@ export default defineNuxtModule({
67
72
  await generateNuxtServerRoutes(resolvedOutput, serverRoutePath, { enableBff: options.enableBff, backend }, logger);
68
73
  }
69
74
  // 3. Generate headless connectors if requested (requires useAsyncData)
70
- if (options.createUseAsyncDataConnectors && selectedGenerators.includes('useAsyncData')) {
75
+ if ((generateConnectorsFlag || connectorsRequested) &&
76
+ selectedGenerators.includes('useAsyncData')) {
71
77
  const connectorsOutputDir = path.join(composablesOutputDir, 'connectors');
72
78
  await generateConnectors({
73
79
  inputSpec: resolvedInput,
74
80
  outputDir: connectorsOutputDir,
75
81
  composablesRelDir: '../use-async-data/composables',
76
82
  runtimeRelDir: '../../runtime',
83
+ connectorsConfig: options.connectors,
77
84
  }, logger);
78
85
  }
79
86
  };
@@ -100,7 +107,8 @@ export default defineNuxtModule({
100
107
  if (selectedGenerators.includes('useAsyncData')) {
101
108
  addImportsDir(path.join(composablesOutputDir, 'use-async-data', 'composables'));
102
109
  }
103
- if (options.createUseAsyncDataConnectors && selectedGenerators.includes('useAsyncData')) {
110
+ if ((generateConnectorsFlag || connectorsRequested) &&
111
+ selectedGenerators.includes('useAsyncData')) {
104
112
  addImportsDir(path.join(composablesOutputDir, 'connectors'));
105
113
  }
106
114
  }
@@ -1,4 +1,4 @@
1
- import type { GeneratorConfig } from '../cli/config.js';
1
+ import type { GeneratorConfig } from '../config/types.js';
2
2
  /**
3
3
  * Configuration options for the nuxt-openapi-hyperfetch Nuxt module.
4
4
  * Extends the CLI GeneratorConfig so the same fields work in both nxh.config.js and nuxt.config.ts.
@@ -25,9 +25,9 @@ export interface ModuleOptions extends GeneratorConfig {
25
25
  */
26
26
  enableAutoImport?: boolean;
27
27
  /**
28
- * Generate headless UI connector composables on top of useAsyncData.
29
- * Connectors provide ready-made logic for tables, pagination, forms and delete actions.
30
- * Requires useAsyncData to also be in generators.
28
+ * Backward-compatible connectors flag.
29
+ * Prefer `generators: ['connectors']` so module and CLI behave the same.
30
+ * When true, connectors are generated and useAsyncData is added automatically.
31
31
  * @default false
32
32
  */
33
33
  createUseAsyncDataConnectors?: boolean;
@@ -15,10 +15,10 @@ OpenAPI/Swagger → TypeScript Client → Nuxt Composables
15
15
 
16
16
  The tool supports two backends for **Stage 1** (OpenAPI → TypeScript Client). You choose at runtime:
17
17
 
18
- | Backend | Command | Requires | Output format |
19
- |---|---|---|---|
20
- | **OpenAPI Generator** (official) | `@openapitools/openapi-generator-cli` | Java 11+ | `apis/PetApi.ts`, `models/` |
21
- | **Hey API** | `@hey-api/openapi-ts` | Node.js only | `sdk.gen.ts`, `types.gen.ts` |
18
+ | Backend | Command | Requires | Output format |
19
+ | -------------------------------- | ------------------------------------- | ------------ | ---------------------------- |
20
+ | **OpenAPI Generator** (official) | `@openapitools/openapi-generator-cli` | Java 11+ | `apis/PetApi.ts`, `models/` |
21
+ | **Hey API** | `@hey-api/openapi-ts` | Node.js only | `sdk.gen.ts`, `types.gen.ts` |
22
22
 
23
23
  Both produce the same final Nuxt composables. The choice affects only Stage 1 output and the parser used in Stage 2.
24
24
 
@@ -98,6 +98,16 @@ useFetchAddPet → useApiRequest → useFetch (Nuxt native)
98
98
  | Plugin templates | ❌ | ✅ (once) | ✅ (safe) |
99
99
  | CLI tool itself | ❌ | ❌ | ✅ (you edit) |
100
100
 
101
+ ### 4. Generator Selection Semantics (CLI + Nuxt module)
102
+
103
+ Generator selection is unified between `nxh.config.*` and `nuxt.config.ts`.
104
+
105
+ - `generators: ['connectors']` runs `useAsyncData` first, then `connectors`
106
+ - `generators: ['useAsyncData']` runs only `useAsyncData`
107
+ - `generators: ['useAsyncData', 'connectors']` runs both
108
+
109
+ `createUseAsyncDataConnectors` remains available for backward compatibility, but `generators: ['connectors']` is the preferred declarative option.
110
+
101
111
  ## Key Files (Read These First)
102
112
 
103
113
  ### 1. **CLI Entry** - `src/index.ts`
@@ -231,6 +241,36 @@ nuxt-generator/
231
241
 
232
242
  ## Most Common Tasks
233
243
 
244
+ ### Configure Connectors (manual/hybrid)
245
+
246
+ Use `connectors` in `nxh.config.ts` or `openApiHyperFetch` in `nuxt.config.ts`.
247
+
248
+ ```ts
249
+ connectors: {
250
+ strategy: 'hybrid',
251
+ resources: {
252
+ pets: {
253
+ operations: {
254
+ getAll: { operationId: 'findPetsByStatus' },
255
+ get: { path: '/pet/{petId}' }
256
+ }
257
+ },
258
+ featuredPets: {
259
+ operations: {
260
+ getAll: { operationId: 'findPetsByTags' }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ Rules:
268
+
269
+ - Each operation accepts `operationId` or `path` (never both)
270
+ - Partial override is allowed (you can define only `get` and `getAll`)
271
+ - `manual` generates only configured resources
272
+ - `hybrid` merges inferred resources with configured overrides/custom resources
273
+
234
274
  ### Debugging a Parser Error
235
275
 
236
276
  1. Check error message for method name
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-openapi-hyperfetch",
3
- "version": "1.0.2",
4
- "description": "Nuxt useFetch, useAsyncData, Nuxt server & Headless UI connectors OpenAPI generator",
3
+ "version": "1.0.4",
4
+ "description": "⚡ OpenAPI to Nuxt code generator for useFetch, useAsyncData, server routes, and headless CRUD connectors.",
5
5
  "type": "module",
6
6
  "author": "",
7
7
  "license": "Apache-2.0",
@@ -86,9 +86,28 @@
86
86
  "fs-extra": "^11.3.4",
87
87
  "globby": "^16.1.1",
88
88
  "gradient-string": "^3.0.0",
89
+ "jiti": "^2.6.1",
89
90
  "js-yaml": "^4.1.1",
90
91
  "prettier": "^3.8.1",
91
92
  "ts-morph": "^27.0.2"
92
93
  },
93
- "keywords": ["nuxt", "openapi", "swagger", "codegen", "composables", "useFetch", "useAsyncData", "typescript", "zod", "connector"]
94
+ "keywords": [
95
+ "openapi",
96
+ "generator",
97
+ "nuxt",
98
+ "rest",
99
+ "composables",
100
+ "swagger",
101
+ "fetch",
102
+ "$fetch",
103
+ "useFetch",
104
+ "useAsyncData",
105
+ "api",
106
+ "open",
107
+ "codegen",
108
+ "server",
109
+ "node",
110
+ "headless",
111
+ "sdk"
112
+ ]
94
113
  }