nuxt-openapi-hyperfetch 0.3.1-beta → 0.3.3-beta

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.
@@ -62,7 +62,7 @@ export async function generateConnectors(options, logger = createClackLogger())
62
62
  const generatedNames = [];
63
63
  for (const resource of resourceMap.values()) {
64
64
  try {
65
- const code = generateConnectorFile(resource, composablesRelDir);
65
+ const code = generateConnectorFile(resource, composablesRelDir, '../..', runtimeRelDir);
66
66
  const formatted = await formatCode(code, logger);
67
67
  const fileName = connectorFileName(resource.composableName);
68
68
  const filePath = path.join(outputDir, fileName);
@@ -6,7 +6,7 @@ import type { ResourceInfo } from '../schema-analyzer/types.js';
6
6
  * @param composablesRelDir Relative path from the connector dir to the
7
7
  * useAsyncData composables dir (e.g. '../use-async-data')
8
8
  */
9
- export declare function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string, sdkRelDir?: string): string;
9
+ export declare function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string, sdkRelDir?: string, runtimeRelDir?: string): string;
10
10
  /**
11
11
  * Derive the output filename for a connector.
12
12
  * 'usePetsConnector' → 'use-pets-connector.ts'
@@ -33,7 +33,7 @@ function toFileName(composableName) {
33
33
  /**
34
34
  * Build all `import` lines for a resource connector.
35
35
  */
36
- function buildImports(resource, composablesRelDir, sdkRelDir) {
36
+ function buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir) {
37
37
  const lines = [];
38
38
  // zod
39
39
  lines.push(`import { z } from 'zod';`);
@@ -49,7 +49,7 @@ function buildImports(resource, composablesRelDir, sdkRelDir) {
49
49
  if (resource.deleteEndpoint) {
50
50
  connectorTypeImports.push('DeleteConnectorReturn');
51
51
  }
52
- lines.push(`import type { ${connectorTypeImports.join(', ')} } from '#nxh/runtime/connector-types';`);
52
+ lines.push(`import type { ${connectorTypeImports.join(', ')} } from '${runtimeRelDir}/connector-types';`);
53
53
  lines.push('');
54
54
  // SDK request/response types (for the params overload signature)
55
55
  if (resource.listEndpoint) {
@@ -57,7 +57,7 @@ function buildImports(resource, composablesRelDir, sdkRelDir) {
57
57
  lines.push(`import type { ${requestTypeName} } from '${sdkRelDir}';`);
58
58
  lines.push('');
59
59
  }
60
- // runtime helpers (Nuxt alias set up by the Nuxt module)
60
+ // runtime helpers relative path works in both CLI and Nuxt module contexts
61
61
  // useListConnector is always imported to support the optional factory pattern
62
62
  const runtimeHelpers = ['useListConnector'];
63
63
  if (resource.detailEndpoint) {
@@ -70,7 +70,7 @@ function buildImports(resource, composablesRelDir, sdkRelDir) {
70
70
  runtimeHelpers.push('useDeleteConnector');
71
71
  }
72
72
  for (const helper of runtimeHelpers) {
73
- lines.push(`import { ${helper} } from '#nxh/runtime/${helper}';`);
73
+ lines.push(`import { ${helper} } from '${runtimeRelDir}/${helper}';`);
74
74
  }
75
75
  lines.push('');
76
76
  // generated useAsyncData composables
@@ -312,9 +312,9 @@ function buildFunctionBody(resource) {
312
312
  * @param composablesRelDir Relative path from the connector dir to the
313
313
  * useAsyncData composables dir (e.g. '../use-async-data')
314
314
  */
315
- export function generateConnectorFile(resource, composablesRelDir, sdkRelDir = '../..') {
315
+ export function generateConnectorFile(resource, composablesRelDir, sdkRelDir = '../..', runtimeRelDir = '../../runtime') {
316
316
  const header = generateFileHeader();
317
- const imports = buildImports(resource, composablesRelDir, sdkRelDir);
317
+ const imports = buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir);
318
318
  const schemas = buildZodSchemas(resource);
319
319
  const columns = buildColumns(resource);
320
320
  const optionsInterface = buildOptionsInterface(resource);
@@ -36,6 +36,13 @@ function getSuccessResponseSchema(operation) {
36
36
  function isArraySchema(schema) {
37
37
  return schema.type === 'array' || schema.items !== undefined;
38
38
  }
39
+ /** True when schema is a primitive scalar (string, number, integer, boolean) — not a resource */
40
+ function isPrimitiveSchema(schema) {
41
+ return (schema.type === 'string' ||
42
+ schema.type === 'number' ||
43
+ schema.type === 'integer' ||
44
+ schema.type === 'boolean');
45
+ }
39
46
  // ─── Request body schema ──────────────────────────────────────────────────────
40
47
  function getRequestBodySchema(operation) {
41
48
  if (!operation.requestBody?.content) {
@@ -84,6 +91,11 @@ export function detectIntent(method, path, operation) {
84
91
  if (isArraySchema(responseSchema)) {
85
92
  return 'list';
86
93
  }
94
+ // Primitive response (string, number, boolean) → not a CRUD resource
95
+ // e.g. GET /user/login returns a string token — not a list or detail
96
+ if (isPrimitiveSchema(responseSchema)) {
97
+ return 'unknown';
98
+ }
87
99
  // Object response — distinguish list vs detail by path structure:
88
100
  // GET /pets/{id} → has path param → detail (single item fetch)
89
101
  // GET /pets → no path param → list (likely paginated envelope: { data: [], total: n })
@@ -132,7 +132,7 @@ function baseZodExpr(prop) {
132
132
  case 'array':
133
133
  return arrayZodExpr(prop);
134
134
  case 'object':
135
- return 'z.record(z.unknown())';
135
+ return objectZodExpr(prop);
136
136
  default:
137
137
  // $ref already resolved, unknown type → permissive
138
138
  return 'z.unknown()';
@@ -181,6 +181,27 @@ function numberZodExpr(prop) {
181
181
  }
182
182
  return expr;
183
183
  }
184
+ function objectZodExpr(prop) {
185
+ const { additionalProperties } = prop;
186
+ // additionalProperties: false or undefined → plain object
187
+ if (!additionalProperties || additionalProperties === true) {
188
+ return 'z.record(z.unknown())';
189
+ }
190
+ // additionalProperties has a known primitive type → typed record
191
+ const valueExpr = additionalPropsZodExpr(additionalProperties);
192
+ return `z.record(${valueExpr})`;
193
+ }
194
+ function additionalPropsZodExpr(schema) {
195
+ switch (schema.type) {
196
+ case 'string': return stringZodExpr(schema);
197
+ case 'integer': return integerZodExpr(schema);
198
+ case 'number': return numberZodExpr(schema);
199
+ case 'boolean': return 'z.boolean()';
200
+ case 'array': return arrayZodExpr(schema);
201
+ case 'object': return objectZodExpr(schema);
202
+ default: return 'z.unknown()';
203
+ }
204
+ }
184
205
  function arrayZodExpr(prop) {
185
206
  const itemExpr = prop.items ? baseZodExpr(prop.items) : 'z.unknown()';
186
207
  let expr = `z.array(${itemExpr})`;
package/dist/index.js CHANGED
@@ -196,6 +196,19 @@ program
196
196
  }
197
197
  }
198
198
  // Generate headless connectors if requested (requires useAsyncData)
199
+ if (generateConnectorsFlag) {
200
+ // Check zod is available in the user's project before attempting generation
201
+ try {
202
+ // Use a variable to prevent TypeScript from resolving the module at compile time
203
+ const zodId = 'zod';
204
+ await import(zodId);
205
+ }
206
+ catch {
207
+ p.log.warn('Skipping connectors: "zod" is not installed in this project.\n' +
208
+ ' Run: npm install zod');
209
+ generateConnectorsFlag = false;
210
+ }
211
+ }
199
212
  if (generateConnectorsFlag) {
200
213
  const spinner = p.spinner();
201
214
  spinner.start('Generating headless UI connectors...');
@@ -70,15 +70,12 @@ export default defineNuxtModule({
70
70
  if (options.createUseAsyncDataConnectors &&
71
71
  selectedGenerators.includes('useAsyncData')) {
72
72
  const connectorsOutputDir = path.join(composablesOutputDir, 'connectors');
73
- const runtimeDir = path.join(resolvedOutput, 'runtime');
74
73
  await generateConnectors({
75
74
  inputSpec: resolvedInput,
76
75
  outputDir: connectorsOutputDir,
77
76
  composablesRelDir: '../use-async-data',
78
77
  runtimeRelDir: '../../runtime',
79
78
  }, logger);
80
- // Register #nxh alias so generated connector imports resolve
81
- nuxt.options.alias['#nxh'] = runtimeDir;
82
79
  }
83
80
  };
84
81
  // --- Hooks: dev build / production build ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-openapi-hyperfetch",
3
- "version": "0.3.1-beta",
3
+ "version": "0.3.3-beta",
4
4
  "description": "Nuxt useFetch, useAsyncData and Nuxt server OpenAPI generator",
5
5
  "type": "module",
6
6
  "author": "",
@@ -78,7 +78,7 @@ export async function generateConnectors(
78
78
 
79
79
  for (const resource of resourceMap.values()) {
80
80
  try {
81
- const code = generateConnectorFile(resource, composablesRelDir);
81
+ const code = generateConnectorFile(resource, composablesRelDir, '../..', runtimeRelDir);
82
82
  const formatted = await formatCode(code, logger);
83
83
  const fileName = connectorFileName(resource.composableName);
84
84
  const filePath = path.join(outputDir, fileName);
@@ -41,7 +41,7 @@ function toFileName(composableName: string): string {
41
41
  /**
42
42
  * Build all `import` lines for a resource connector.
43
43
  */
44
- function buildImports(resource: ResourceInfo, composablesRelDir: string, sdkRelDir: string): string {
44
+ function buildImports(resource: ResourceInfo, composablesRelDir: string, sdkRelDir: string, runtimeRelDir: string): string {
45
45
  const lines: string[] = [];
46
46
 
47
47
  // zod
@@ -59,7 +59,7 @@ function buildImports(resource: ResourceInfo, composablesRelDir: string, sdkRelD
59
59
  if (resource.deleteEndpoint) {
60
60
  connectorTypeImports.push('DeleteConnectorReturn');
61
61
  }
62
- lines.push(`import type { ${connectorTypeImports.join(', ')} } from '#nxh/runtime/connector-types';`);
62
+ lines.push(`import type { ${connectorTypeImports.join(', ')} } from '${runtimeRelDir}/connector-types';`);
63
63
  lines.push('');
64
64
 
65
65
  // SDK request/response types (for the params overload signature)
@@ -69,7 +69,7 @@ function buildImports(resource: ResourceInfo, composablesRelDir: string, sdkRelD
69
69
  lines.push('');
70
70
  }
71
71
 
72
- // runtime helpers (Nuxt alias set up by the Nuxt module)
72
+ // runtime helpers relative path works in both CLI and Nuxt module contexts
73
73
  // useListConnector is always imported to support the optional factory pattern
74
74
  const runtimeHelpers: string[] = ['useListConnector'];
75
75
  if (resource.detailEndpoint) {
@@ -83,7 +83,7 @@ function buildImports(resource: ResourceInfo, composablesRelDir: string, sdkRelD
83
83
  }
84
84
 
85
85
  for (const helper of runtimeHelpers) {
86
- lines.push(`import { ${helper} } from '#nxh/runtime/${helper}';`);
86
+ lines.push(`import { ${helper} } from '${runtimeRelDir}/${helper}';`);
87
87
  }
88
88
  lines.push('');
89
89
 
@@ -375,10 +375,11 @@ function buildFunctionBody(resource: ResourceInfo): string {
375
375
  export function generateConnectorFile(
376
376
  resource: ResourceInfo,
377
377
  composablesRelDir: string,
378
- sdkRelDir = '../..'
378
+ sdkRelDir = '../..',
379
+ runtimeRelDir = '../../runtime'
379
380
  ): string {
380
381
  const header = generateFileHeader();
381
- const imports = buildImports(resource, composablesRelDir, sdkRelDir);
382
+ const imports = buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir);
382
383
  const schemas = buildZodSchemas(resource);
383
384
  const columns = buildColumns(resource);
384
385
  const optionsInterface = buildOptionsInterface(resource);
@@ -49,6 +49,16 @@ function isArraySchema(schema: OpenApiPropertySchema): boolean {
49
49
  return schema.type === 'array' || schema.items !== undefined;
50
50
  }
51
51
 
52
+ /** True when schema is a primitive scalar (string, number, integer, boolean) — not a resource */
53
+ function isPrimitiveSchema(schema: OpenApiPropertySchema): boolean {
54
+ return (
55
+ schema.type === 'string' ||
56
+ schema.type === 'number' ||
57
+ schema.type === 'integer' ||
58
+ schema.type === 'boolean'
59
+ );
60
+ }
61
+
52
62
  // ─── Request body schema ──────────────────────────────────────────────────────
53
63
 
54
64
  function getRequestBodySchema(operation: OpenApiOperation): OpenApiPropertySchema | undefined {
@@ -113,6 +123,12 @@ export function detectIntent(
113
123
  return 'list';
114
124
  }
115
125
 
126
+ // Primitive response (string, number, boolean) → not a CRUD resource
127
+ // e.g. GET /user/login returns a string token — not a list or detail
128
+ if (isPrimitiveSchema(responseSchema)) {
129
+ return 'unknown';
130
+ }
131
+
116
132
  // Object response — distinguish list vs detail by path structure:
117
133
  // GET /pets/{id} → has path param → detail (single item fetch)
118
134
  // GET /pets → no path param → list (likely paginated envelope: { data: [], total: n })
@@ -167,7 +167,7 @@ function baseZodExpr(prop: OpenApiPropertySchema): string {
167
167
  return arrayZodExpr(prop);
168
168
 
169
169
  case 'object':
170
- return 'z.record(z.unknown())';
170
+ return objectZodExpr(prop);
171
171
 
172
172
  default:
173
173
  // $ref already resolved, unknown type → permissive
@@ -224,6 +224,31 @@ function numberZodExpr(prop: OpenApiPropertySchema): string {
224
224
  return expr;
225
225
  }
226
226
 
227
+ function objectZodExpr(prop: OpenApiPropertySchema): string {
228
+ const { additionalProperties } = prop;
229
+
230
+ // additionalProperties: false or undefined → plain object
231
+ if (!additionalProperties || additionalProperties === true) {
232
+ return 'z.record(z.unknown())';
233
+ }
234
+
235
+ // additionalProperties has a known primitive type → typed record
236
+ const valueExpr = additionalPropsZodExpr(additionalProperties);
237
+ return `z.record(${valueExpr})`;
238
+ }
239
+
240
+ function additionalPropsZodExpr(schema: OpenApiPropertySchema): string {
241
+ switch (schema.type) {
242
+ case 'string': return stringZodExpr(schema);
243
+ case 'integer': return integerZodExpr(schema);
244
+ case 'number': return numberZodExpr(schema);
245
+ case 'boolean': return 'z.boolean()';
246
+ case 'array': return arrayZodExpr(schema);
247
+ case 'object': return objectZodExpr(schema);
248
+ default: return 'z.unknown()';
249
+ }
250
+ }
251
+
227
252
  function arrayZodExpr(prop: OpenApiPropertySchema): string {
228
253
  const itemExpr = prop.items ? baseZodExpr(prop.items) : 'z.unknown()';
229
254
  let expr = `z.array(${itemExpr})`;
package/src/index.ts CHANGED
@@ -253,6 +253,21 @@ program
253
253
  }
254
254
 
255
255
  // Generate headless connectors if requested (requires useAsyncData)
256
+ if (generateConnectorsFlag) {
257
+ // Check zod is available in the user's project before attempting generation
258
+ try {
259
+ // Use a variable to prevent TypeScript from resolving the module at compile time
260
+ const zodId = 'zod';
261
+ await import(zodId);
262
+ } catch {
263
+ p.log.warn(
264
+ 'Skipping connectors: "zod" is not installed in this project.\n' +
265
+ ' Run: npm install zod'
266
+ );
267
+ generateConnectorsFlag = false;
268
+ }
269
+ }
270
+
256
271
  if (generateConnectorsFlag) {
257
272
  const spinner = p.spinner();
258
273
  spinner.start('Generating headless UI connectors...');
@@ -108,7 +108,6 @@ export default defineNuxtModule<ModuleOptions>({
108
108
  selectedGenerators.includes('useAsyncData')
109
109
  ) {
110
110
  const connectorsOutputDir = path.join(composablesOutputDir, 'connectors');
111
- const runtimeDir = path.join(resolvedOutput, 'runtime');
112
111
  await generateConnectors(
113
112
  {
114
113
  inputSpec: resolvedInput,
@@ -118,8 +117,6 @@ export default defineNuxtModule<ModuleOptions>({
118
117
  },
119
118
  logger
120
119
  );
121
- // Register #nxh alias so generated connector imports resolve
122
- nuxt.options.alias['#nxh'] = runtimeDir;
123
120
  }
124
121
  };
125
122