nuxt-openapi-hyperfetch 0.3.0-beta → 0.3.1-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.
@@ -7,6 +7,7 @@ import { generateConnectorFile, connectorFileName, generateConnectorIndexFile, }
7
7
  import { createClackLogger } from '../../../cli/logger.js';
8
8
  // Runtime files that must be copied to the user's project
9
9
  const RUNTIME_FILES = [
10
+ 'connector-types.ts',
10
11
  'useListConnector.ts',
11
12
  'useDetailConnector.ts',
12
13
  'useFormConnector.ts',
@@ -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): string;
9
+ export declare function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string, sdkRelDir?: string): string;
10
10
  /**
11
11
  * Derive the output filename for a connector.
12
12
  * 'usePetsConnector' → 'use-pets-connector.ts'
@@ -12,7 +12,6 @@ function generateFileHeader() {
12
12
  */
13
13
 
14
14
  /* eslint-disable */
15
- // @ts-nocheck
16
15
  `;
17
16
  }
18
17
  // ─── Naming helpers ───────────────────────────────────────────────────────────
@@ -34,16 +33,33 @@ function toFileName(composableName) {
34
33
  /**
35
34
  * Build all `import` lines for a resource connector.
36
35
  */
37
- function buildImports(resource, composablesRelDir) {
36
+ function buildImports(resource, composablesRelDir, sdkRelDir) {
38
37
  const lines = [];
39
38
  // zod
40
39
  lines.push(`import { z } from 'zod';`);
41
40
  lines.push('');
42
- // runtime helpers (Nuxt alias set up by the Nuxt module)
43
- const runtimeHelpers = [];
41
+ // connector-typesstructural interfaces for return types
42
+ const connectorTypeImports = ['ListConnectorReturn'];
43
+ if (resource.detailEndpoint) {
44
+ connectorTypeImports.push('DetailConnectorReturn');
45
+ }
46
+ if (resource.createEndpoint || resource.updateEndpoint) {
47
+ connectorTypeImports.push('FormConnectorReturn');
48
+ }
49
+ if (resource.deleteEndpoint) {
50
+ connectorTypeImports.push('DeleteConnectorReturn');
51
+ }
52
+ lines.push(`import type { ${connectorTypeImports.join(', ')} } from '#nxh/runtime/connector-types';`);
53
+ lines.push('');
54
+ // SDK request/response types (for the params overload signature)
44
55
  if (resource.listEndpoint) {
45
- runtimeHelpers.push('useListConnector');
56
+ const requestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
57
+ lines.push(`import type { ${requestTypeName} } from '${sdkRelDir}';`);
58
+ lines.push('');
46
59
  }
60
+ // runtime helpers (Nuxt alias — set up by the Nuxt module)
61
+ // useListConnector is always imported to support the optional factory pattern
62
+ const runtimeHelpers = ['useListConnector'];
47
63
  if (resource.detailEndpoint) {
48
64
  runtimeHelpers.push('useDetailConnector');
49
65
  }
@@ -121,6 +137,61 @@ function buildColumns(resource) {
121
137
  .join(',\n');
122
138
  return `const ${varName} = [\n${entries},\n];`;
123
139
  }
140
+ /**
141
+ * Build the TypeScript options interface for a connector.
142
+ * Only includes fields relevant to the endpoints present on the resource.
143
+ */
144
+ function buildOptionsInterface(resource) {
145
+ const typeName = `${pascalCase(resource.composableName)}Options`;
146
+ const hasColumns = resource.columns && resource.columns.length > 0;
147
+ const fields = [];
148
+ if (resource.listEndpoint && hasColumns) {
149
+ fields.push(` columnLabels?: Record<string, string>;`);
150
+ fields.push(` columnLabel?: (key: string) => string;`);
151
+ }
152
+ if (resource.createEndpoint && resource.zodSchemas.create) {
153
+ fields.push(` createSchema?: z.ZodTypeAny | ((base: z.ZodTypeAny) => z.ZodTypeAny);`);
154
+ }
155
+ if (resource.updateEndpoint && resource.zodSchemas.update) {
156
+ fields.push(` updateSchema?: z.ZodTypeAny | ((base: z.ZodTypeAny) => z.ZodTypeAny);`);
157
+ }
158
+ if (fields.length === 0) {
159
+ return `type ${typeName} = Record<string, never>;`;
160
+ }
161
+ return [`interface ${typeName} {`, ...fields, `}`].join('\n');
162
+ }
163
+ /**
164
+ * Build the TypeScript return type for a connector.
165
+ */
166
+ function buildReturnType(resource) {
167
+ const pascal = pascalCase(resource.name);
168
+ const typeName = `${pascalCase(resource.composableName)}Return`;
169
+ const fields = [];
170
+ // table is always present in the return type:
171
+ // - if listEndpoint exists → ListConnectorReturn<T> (always defined)
172
+ // - if no listEndpoint → ListConnectorReturn<unknown> | undefined (only when factory passed)
173
+ if (resource.listEndpoint) {
174
+ fields.push(` table: ListConnectorReturn<${pascal}>;`);
175
+ }
176
+ else {
177
+ fields.push(` table: ListConnectorReturn<unknown> | undefined;`);
178
+ }
179
+ if (resource.detailEndpoint) {
180
+ fields.push(` detail: DetailConnectorReturn<${pascal}>;`);
181
+ }
182
+ if (resource.createEndpoint) {
183
+ const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
184
+ fields.push(` createForm: FormConnectorReturn<${inputType}>;`);
185
+ }
186
+ if (resource.updateEndpoint) {
187
+ const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
188
+ fields.push(` updateForm: FormConnectorReturn<${inputType}>;`);
189
+ }
190
+ if (resource.deleteEndpoint) {
191
+ fields.push(` deleteAction: DeleteConnectorReturn<${pascal}>;`);
192
+ }
193
+ return [`type ${typeName} = {`, ...fields, `};`].join('\n');
194
+ }
124
195
  /**
125
196
  * Build the body of the exported connector function.
126
197
  */
@@ -133,6 +204,9 @@ function buildFunctionBody(resource) {
133
204
  .replace(/^./, (c) => c.toLowerCase());
134
205
  const columnsVar = `${camel}Columns`;
135
206
  const subConnectors = [];
207
+ // Derived type names — must match buildOptionsInterface / buildReturnType
208
+ const optionsTypeName = `${pascalCase(resource.composableName)}Options`;
209
+ const returnTypeName = `${pascalCase(resource.composableName)}Return`;
136
210
  // Destructure options param — only what's relevant for this resource
137
211
  const optionKeys = [];
138
212
  if (resource.listEndpoint && hasColumns) {
@@ -145,63 +219,57 @@ function buildFunctionBody(resource) {
145
219
  optionKeys.push('updateSchema');
146
220
  }
147
221
  const optionsDestructure = optionKeys.length > 0 ? ` const { ${optionKeys.join(', ')} } = options;\n` : '';
222
+ // ── List / table sub-connector ─────────────────────────────────────────────
148
223
  if (resource.listEndpoint) {
149
224
  const fn = toAsyncDataName(resource.listEndpoint.operationId);
150
- // paginated: true tells useListConnector to expose pagination helpers
151
- // (goToPage, nextPage, prevPage, setPerPage, pagination ref).
152
- // We set it whenever the spec declares a list endpoint that has a response schema,
153
- // which is a reliable proxy for "this API returns structured data worth paginating".
225
+ const listRequestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
154
226
  const paginatedFlag = resource.listEndpoint.responseSchema ? 'paginated: true' : '';
155
227
  const columnsArg = hasColumns ? `columns: ${columnsVar}` : '';
156
228
  const labelArgs = hasColumns ? 'columnLabels, columnLabel' : '';
157
229
  const allArgs = [paginatedFlag, columnsArg, labelArgs].filter(Boolean).join(', ');
158
230
  const opts = allArgs ? `{ ${allArgs} }` : '{}';
159
- subConnectors.push(` const table = useListConnector(${fn}, ${opts});`);
231
+ // Factory: if the first arg is a function the user provided their own composable;
232
+ // otherwise build a default factory from the plain params object.
233
+ subConnectors.push(` const isFactory = typeof paramsOrSource === 'function';`, ` const listFactory = isFactory`, ` ? (paramsOrSource as () => unknown)`, ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName});`, ` const table = useListConnector(listFactory, ${opts}) as unknown as ListConnectorReturn<${pascal}>;`);
234
+ }
235
+ else {
236
+ // No list endpoint — support optional factory for developer-provided list
237
+ subConnectors.push(` const table = paramsOrSource`, ` ? (useListConnector(paramsOrSource as () => unknown, {}) as unknown as ListConnectorReturn<unknown>)`, ` : undefined;`);
160
238
  }
161
239
  if (resource.detailEndpoint) {
162
240
  const fn = toAsyncDataName(resource.detailEndpoint.operationId);
163
- subConnectors.push(` const detail = useDetailConnector(${fn});`);
241
+ subConnectors.push(` const detail = useDetailConnector(${fn}) as unknown as DetailConnectorReturn<${pascal}>;`);
164
242
  }
165
243
  if (resource.createEndpoint) {
166
244
  const fn = toAsyncDataName(resource.createEndpoint.operationId);
245
+ const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
167
246
  const schemaArg = resource.zodSchemas.create
168
247
  ? `{ schema: ${pascal}CreateSchema, schemaOverride: createSchema }`
169
248
  : '{}';
170
- subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg});`);
249
+ subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
171
250
  }
172
251
  if (resource.updateEndpoint) {
173
252
  const fn = toAsyncDataName(resource.updateEndpoint.operationId);
174
253
  const hasDetail = !!resource.detailEndpoint;
175
- // Build the options argument for useFormConnector:
176
- // schema → Zod schema for client-side validation before submission
177
- // loadWith → reference to the detail connector so the form auto-fills
178
- // when detail.item changes (user clicks "Edit" on a row)
179
- //
180
- // Four combinations are possible depending on what the spec provides:
254
+ const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
181
255
  let schemaArg = '{}';
182
256
  if (resource.zodSchemas.update && hasDetail) {
183
- // Best case: validate AND pre-fill from detail
184
257
  schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema, loadWith: detail }`;
185
258
  }
186
259
  else if (resource.zodSchemas.update) {
187
- // Validate, but no detail endpoint to pre-fill from
188
260
  schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema }`;
189
261
  }
190
262
  else if (hasDetail) {
191
- // No Zod schema (no request body in spec), but still pre-fill from detail
192
263
  schemaArg = `{ loadWith: detail }`;
193
264
  }
194
- subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg});`);
265
+ subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
195
266
  }
196
267
  if (resource.deleteEndpoint) {
197
268
  const fn = toAsyncDataName(resource.deleteEndpoint.operationId);
198
- subConnectors.push(` const deleteAction = useDeleteConnector(${fn});`);
199
- }
200
- // Return object — only include what was built
201
- const returnKeys = [];
202
- if (resource.listEndpoint) {
203
- returnKeys.push('table');
269
+ subConnectors.push(` const deleteAction = useDeleteConnector(${fn}) as unknown as DeleteConnectorReturn<${pascal}>;`);
204
270
  }
271
+ // Return object — always includes table (undefined when no list + no factory)
272
+ const returnKeys = ['table'];
205
273
  if (resource.detailEndpoint) {
206
274
  returnKeys.push('detail');
207
275
  }
@@ -214,16 +282,27 @@ function buildFunctionBody(resource) {
214
282
  if (resource.deleteEndpoint) {
215
283
  returnKeys.push('deleteAction');
216
284
  }
217
- const returnStatement = ` return { ${returnKeys.join(', ')} };`;
218
- return [
219
- `export function ${resource.composableName}(options = {}) {`,
220
- optionsDestructure.trimEnd(),
221
- ...subConnectors,
222
- returnStatement,
223
- `}`,
224
- ]
225
- .filter((s) => s !== '')
226
- .join('\n');
285
+ const returnStatement = ` return { ${returnKeys.join(', ')} } as ${returnTypeName};`;
286
+ // ── Function signature ─────────────────────────────────────────────────────
287
+ // Resources WITH a list endpoint: two overloads (factory | params).
288
+ // Resources WITHOUT a list endpoint: single signature with optional factory.
289
+ const lines = [];
290
+ if (resource.listEndpoint) {
291
+ const listRequestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
292
+ lines.push(`export function ${resource.composableName}(source: () => unknown, options?: ${optionsTypeName}): ${returnTypeName};`, `export function ${resource.composableName}(params?: ${listRequestTypeName}, options?: ${optionsTypeName}): ${returnTypeName};`, `export function ${resource.composableName}(paramsOrSource?: ${listRequestTypeName} | (() => unknown), options: ${optionsTypeName} = {}): ${returnTypeName} {`);
293
+ }
294
+ else {
295
+ lines.push(`export function ${resource.composableName}(source?: () => unknown, options: ${optionsTypeName} = {}): ${returnTypeName} {`);
296
+ // Alias so the body can use paramsOrSource uniformly
297
+ lines.push(` const paramsOrSource = source;`);
298
+ }
299
+ if (optionsDestructure.trim()) {
300
+ lines.push(optionsDestructure.trimEnd());
301
+ }
302
+ lines.push(...subConnectors);
303
+ lines.push(returnStatement);
304
+ lines.push(`}`);
305
+ return lines.join('\n');
227
306
  }
228
307
  // ─── Public API ───────────────────────────────────────────────────────────────
229
308
  /**
@@ -233,15 +312,16 @@ function buildFunctionBody(resource) {
233
312
  * @param composablesRelDir Relative path from the connector dir to the
234
313
  * useAsyncData composables dir (e.g. '../use-async-data')
235
314
  */
236
- export function generateConnectorFile(resource, composablesRelDir) {
315
+ export function generateConnectorFile(resource, composablesRelDir, sdkRelDir = '../..') {
237
316
  const header = generateFileHeader();
238
- const imports = buildImports(resource, composablesRelDir);
317
+ const imports = buildImports(resource, composablesRelDir, sdkRelDir);
239
318
  const schemas = buildZodSchemas(resource);
240
319
  const columns = buildColumns(resource);
320
+ const optionsInterface = buildOptionsInterface(resource);
321
+ const returnType = buildReturnType(resource);
241
322
  const fn = buildFunctionBody(resource);
242
- // Assemble file: header + imports + (optional) Zod blocks + columns const + function body.
243
- // Each section ends with its own trailing newline; join with \n adds one blank
244
- // line between sections, which matches Prettier's output for this structure.
323
+ // Assemble file: header + imports + (optional) Zod blocks + columns const +
324
+ // options interface + return type + function body.
245
325
  const parts = [header, imports];
246
326
  if (schemas.trim()) {
247
327
  parts.push(schemas);
@@ -249,6 +329,8 @@ export function generateConnectorFile(resource, composablesRelDir) {
249
329
  if (columns.trim()) {
250
330
  parts.push(columns);
251
331
  }
332
+ parts.push(optionsInterface);
333
+ parts.push(returnType);
252
334
  parts.push(fn);
253
335
  return parts.join('\n') + '\n';
254
336
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * connector-types.ts — Structural return type interfaces for the 4 runtime connectors.
3
+ *
4
+ * Uses locally-defined minimal type aliases for Ref/ComputedRef/ShallowRef so this
5
+ * file compiles in the CLI context (where Vue is not installed) and remains
6
+ * structurally compatible with Vue's actual types in the user's Nuxt project.
7
+ *
8
+ * Copied to the user's project alongside the generated connectors and runtime helpers.
9
+ */
10
+ type Ref<T> = {
11
+ value: T;
12
+ };
13
+ type ShallowRef<T> = {
14
+ value: T;
15
+ };
16
+ type ComputedRef<T> = {
17
+ readonly value: T;
18
+ };
19
+ export interface ColumnDef {
20
+ key: string;
21
+ label: string;
22
+ type: string;
23
+ }
24
+ export interface FormFieldDef {
25
+ key: string;
26
+ label: string;
27
+ type: string;
28
+ required: boolean;
29
+ options?: {
30
+ label: string;
31
+ value: string;
32
+ }[];
33
+ placeholder?: string;
34
+ hidden?: boolean;
35
+ }
36
+ export interface PaginationState {
37
+ page: number;
38
+ perPage: number;
39
+ total: number;
40
+ totalPages: number;
41
+ goToPage: (page: number) => void;
42
+ nextPage: () => void;
43
+ prevPage: () => void;
44
+ setPerPage: (n: number) => void;
45
+ }
46
+ export interface ListConnectorReturn<TRow = unknown> {
47
+ rows: ComputedRef<TRow[]>;
48
+ columns: ComputedRef<ColumnDef[]>;
49
+ loading: ComputedRef<boolean>;
50
+ error: ComputedRef<unknown>;
51
+ pagination: ComputedRef<PaginationState | null>;
52
+ goToPage: (page: number) => void;
53
+ nextPage: () => void;
54
+ prevPage: () => void;
55
+ setPerPage: (n: number) => void;
56
+ selected: Ref<TRow[]>;
57
+ onRowSelect: (row: TRow) => void;
58
+ clearSelection: () => void;
59
+ refresh: () => void;
60
+ create: () => void;
61
+ update: (row: TRow) => void;
62
+ remove: (row: TRow) => void;
63
+ _createTrigger: Ref<number>;
64
+ _updateTarget: ShallowRef<TRow | null>;
65
+ _deleteTarget: ShallowRef<TRow | null>;
66
+ }
67
+ export interface DetailConnectorReturn<TItem = unknown> {
68
+ item: ComputedRef<TItem | null>;
69
+ loading: ComputedRef<boolean>;
70
+ error: ComputedRef<unknown>;
71
+ fields: ComputedRef<FormFieldDef[]>;
72
+ load: (id: string | number) => Promise<void>;
73
+ clear: () => void;
74
+ _composable: unknown;
75
+ _currentId: Ref<string | number | null>;
76
+ }
77
+ export interface FormConnectorReturn<TInput = Record<string, unknown>> {
78
+ model: Ref<Partial<TInput>>;
79
+ errors: Ref<Record<string, string[]>>;
80
+ loading: Ref<boolean>;
81
+ submitError: Ref<unknown>;
82
+ submitted: Ref<boolean>;
83
+ isValid: ComputedRef<boolean>;
84
+ hasErrors: ComputedRef<boolean>;
85
+ fields: ComputedRef<FormFieldDef[]>;
86
+ onSuccess: Ref<((data: unknown) => void) | null>;
87
+ onError: Ref<((err: unknown) => void) | null>;
88
+ submit: () => Promise<void>;
89
+ reset: () => void;
90
+ setValues: (data: Partial<TInput>) => void;
91
+ }
92
+ export interface DeleteConnectorReturn<TItem = unknown> {
93
+ target: Ref<TItem | null>;
94
+ isOpen: Ref<boolean>;
95
+ loading: Ref<boolean>;
96
+ error: Ref<unknown>;
97
+ hasTarget: ComputedRef<boolean>;
98
+ onSuccess: Ref<((item: TItem) => void) | null>;
99
+ onError: Ref<((err: unknown) => void) | null>;
100
+ setTarget: (item: TItem) => void;
101
+ cancel: () => void;
102
+ confirm: () => Promise<void>;
103
+ }
104
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * connector-types.ts — Structural return type interfaces for the 4 runtime connectors.
3
+ *
4
+ * Uses locally-defined minimal type aliases for Ref/ComputedRef/ShallowRef so this
5
+ * file compiles in the CLI context (where Vue is not installed) and remains
6
+ * structurally compatible with Vue's actual types in the user's Nuxt project.
7
+ *
8
+ * Copied to the user's project alongside the generated connectors and runtime helpers.
9
+ */
10
+ export {};
@@ -1,8 +1,10 @@
1
1
  /**
2
- * @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
3
- * @param options Configuration for the list connector
2
+ * @param factory A zero-argument function that calls and returns the underlying
3
+ * useAsyncData composable, e.g. () => useAsyncDataGetPets(params)
4
+ * The factory is called once during connector setup (inside setup()).
5
+ * @param options Configuration for the list connector
4
6
  */
5
- export declare function useListConnector(composableFn: any, options?: {}): {
7
+ export declare function useListConnector(factory: any, options?: {}): {
6
8
  rows: any;
7
9
  columns: any;
8
10
  loading: any;
@@ -12,13 +12,15 @@
12
12
  */
13
13
  import { ref, computed, shallowRef } from 'vue';
14
14
  /**
15
- * @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
16
- * @param options Configuration for the list connector
15
+ * @param factory A zero-argument function that calls and returns the underlying
16
+ * useAsyncData composable, e.g. () => useAsyncDataGetPets(params)
17
+ * The factory is called once during connector setup (inside setup()).
18
+ * @param options Configuration for the list connector
17
19
  */
18
- export function useListConnector(composableFn, options = {}) {
20
+ export function useListConnector(factory, options = {}) {
19
21
  const { paginated = false, columns = [], columnLabels = {}, columnLabel = null } = options;
20
22
  // ── Execute the underlying composable ──────────────────────────────────────
21
- const composable = composableFn({ paginated });
23
+ const composable = factory();
22
24
  // ── Derived state ──────────────────────────────────────────────────────────
23
25
  const rows = computed(() => {
24
26
  const data = composable.data?.value;
@@ -1,12 +1,17 @@
1
1
  import type { UseFetchOptions } from '#app';
2
2
  import { type ApiRequestOptions as BaseApiRequestOptions } from '../../shared/runtime/apiHelpers.js';
3
+ type PickInput = ReadonlyArray<string> | undefined;
4
+ type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
5
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
3
6
  /**
4
7
  * Options for useAsyncData API requests with lifecycle callbacks.
5
8
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
6
9
  * Native options like baseURL, method, body, headers, query, lazy, server, immediate, dedupe, etc. are all available.
7
10
  * watch: boolean (true = auto-watch reactive params, false = disable auto-refresh)
8
11
  */
9
- export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> & Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'watch'> & {
12
+ export type ApiAsyncDataOptions<T, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'watch'> & {
13
+ pick?: PickT;
14
+ transform?: (data: PickedData<T, PickT>) => DataT;
10
15
  /**
11
16
  * Enable automatic refresh when reactive params/url change (default: true).
12
17
  * Set to false to disable auto-refresh entirely.
@@ -22,4 +27,5 @@ export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> & Omit<UseFetchOpt
22
27
  * - Global headers from useApiHeaders or $getApiHeaders
23
28
  * - Watch pattern for reactive parameters
24
29
  */
25
- export declare function useApiAsyncData<T>(key: string, url: string | (() => string), options?: ApiAsyncDataOptions<T>): any;
30
+ export declare function useApiAsyncData<T, Options extends ApiAsyncDataOptions<T, any, any> = ApiAsyncDataOptions<T>>(key: string, url: string | (() => string), options?: Options): any;
31
+ export {};
@@ -10,16 +10,21 @@ export interface RawResponse<T> {
10
10
  status: number;
11
11
  statusText: string;
12
12
  }
13
+ type PickInput = ReadonlyArray<string> | undefined;
14
+ type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
15
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
13
16
  /**
14
17
  * Options for useAsyncData Raw API requests.
15
18
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
16
19
  * onSuccess receives data AND the full response (headers, status, statusText).
17
20
  */
18
- export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> & Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
21
+ export type ApiAsyncDataRawOptions<T, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'onSuccess' | 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'onSuccess'> & {
22
+ pick?: PickT;
23
+ transform?: (data: PickedData<T, PickT>) => DataT;
19
24
  /**
20
25
  * Called when the request succeeds — receives both data and the full response object.
21
26
  */
22
- onSuccess?: (data: T, response: {
27
+ onSuccess?: (data: DataT, response: {
23
28
  headers: Headers;
24
29
  status: number;
25
30
  statusText: string;
@@ -38,4 +43,5 @@ export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSucces
38
43
  * - Global headers from useApiHeaders or $getApiHeaders
39
44
  * - Watch pattern for reactive parameters
40
45
  */
41
- export declare function useApiAsyncDataRaw<T>(key: string, url: string | (() => string), options?: ApiAsyncDataRawOptions<T>): any;
46
+ export declare function useApiAsyncDataRaw<T, DataT = T, PickT extends PickInput = undefined, Options extends ApiAsyncDataRawOptions<T, DataT, PickT> = ApiAsyncDataRawOptions<T, DataT, PickT>>(key: string, url: string | (() => string), options?: Options): any;
47
+ export {};
@@ -98,14 +98,16 @@ function generateImports(method, apiImportPath, isRaw) {
98
98
  function generateFunctionBody(method, isRaw, generateOptions) {
99
99
  const hasParams = !!method.requestType;
100
100
  const paramsArg = hasParams ? `params: ${method.requestType}` : '';
101
+ const responseType = method.responseType !== 'void' ? method.responseType : 'void';
101
102
  // Determine the options type based on isRaw
102
103
  const optionsType = isRaw
103
- ? `ApiAsyncDataRawOptions<${method.responseType}>`
104
- : `ApiAsyncDataOptions<${method.responseType}>`;
105
- const optionsArg = `options?: ${optionsType}`;
104
+ ? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
105
+ : `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
106
+ const optionsDefaultType = isRaw
107
+ ? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
108
+ : `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
109
+ const optionsArg = `options?: Options`;
106
110
  const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
107
- // Determine the response type generic
108
- const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
109
111
  // Generate unique key for useAsyncData
110
112
  const composableName = isRaw && method.rawMethodName
111
113
  ? `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`
@@ -116,15 +118,29 @@ function generateFunctionBody(method, isRaw, generateOptions) {
116
118
  const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
117
119
  // Choose the correct wrapper function
118
120
  const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
121
+ const wrapperCall = isRaw
122
+ ? `${wrapperFunction}<${responseType}, DataT, PickT, Options>`
123
+ : `${wrapperFunction}<${responseType}, Options>`;
124
+ const returnType = isRaw
125
+ ? `ReturnType<typeof ${wrapperFunction}<${responseType}, DataT, PickT, Options>>`
126
+ : `ReturnType<typeof ${wrapperFunction}<${responseType}, Options>>`;
119
127
  const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
120
128
  const argsExtraction = hasParams
121
129
  ? ` const _hasKey = typeof args[0] === 'string'\n const params = _hasKey ? args[1] : args[0]\n const options = _hasKey ? { cacheKey: args[0], ...args[2] } : args[1]`
122
130
  : ` const _hasKey = typeof args[0] === 'string'\n const options = _hasKey ? { cacheKey: args[0], ...args[1] } : args[0]`;
123
- return `${description}export function ${composableName}(key: string, ${args})
124
- export function ${composableName}(${args})
131
+ return `${description}export function ${composableName}<
132
+ DataT = ${responseType},
133
+ PickT extends ReadonlyArray<string> | undefined = undefined,
134
+ Options extends ${optionsType} = ${optionsDefaultType}
135
+ >(key: string, ${args}): ${returnType}
136
+ export function ${composableName}<
137
+ DataT = ${responseType},
138
+ PickT extends ReadonlyArray<string> | undefined = undefined,
139
+ Options extends ${optionsType} = ${optionsDefaultType}
140
+ >(${args}): ${returnType}
125
141
  export function ${composableName}(...args: any[]) {
126
142
  ${argsExtraction}${pInit}
127
- return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
143
+ return ${wrapperCall}(${key}, ${url}, ${fetchOptions})
128
144
  }`;
129
145
  }
130
146
  /**
@@ -1,11 +1,17 @@
1
1
  import type { UseFetchOptions } from '#app';
2
2
  import { type ApiRequestOptions as BaseApiRequestOptions } from '../../shared/runtime/apiHelpers.js';
3
+ type PickInput = ReadonlyArray<string> | undefined;
4
+ type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
5
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
3
6
  /**
4
7
  * Options for useFetch API requests with lifecycle callbacks.
5
8
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
6
9
  * Native options like baseURL, method, body, headers, query, lazy, server, immediate, etc. are all available.
7
10
  */
8
- export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetchOptions<T>, 'transform' | 'pick'>;
11
+ export type ApiRequestOptions<T = any, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick'> & {
12
+ pick?: PickT;
13
+ transform?: (data: PickedData<T, PickT>) => DataT;
14
+ };
9
15
  /**
10
16
  * Enhanced useFetch wrapper with lifecycle callbacks and request interception
11
17
  *
@@ -42,4 +48,5 @@ export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetc
42
48
  * });
43
49
  * ```
44
50
  */
45
- export declare function useApiRequest<T = any, Options extends ApiRequestOptions<T> = ApiRequestOptions<T>>(url: string | (() => string), options?: Options): any;
51
+ export declare function useApiRequest<T = any, Options extends ApiRequestOptions<T, any, any> = ApiRequestOptions<T>>(url: string | (() => string), options?: Options): any;
52
+ export {};
@@ -87,16 +87,20 @@ function generateImports(method, apiImportPath) {
87
87
  function generateFunctionBody(method, options) {
88
88
  const hasParams = !!method.requestType;
89
89
  const paramsArg = hasParams ? `params: ${method.requestType}` : '';
90
- const optionsType = `ApiRequestOptions<${method.responseType}>`;
91
- const optionsArg = `options?: ${optionsType}`;
90
+ const responseType = method.responseType !== 'void' ? method.responseType : 'void';
91
+ const optionsType = `ApiRequestOptions<${responseType}, DataT, PickT>`;
92
+ const optionsArg = `options?: Options`;
92
93
  const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
93
- const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
94
94
  const url = generateUrl(method);
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} = <
99
+ DataT = ${responseType},
100
+ PickT extends ReadonlyArray<string> | undefined = undefined,
101
+ Options extends ${optionsType} = ApiRequestOptions<${responseType}, DataT, PickT>
102
+ >(${args}) => {${pInit}
103
+ return useApiRequest<${responseType}, Options>(${url}, ${fetchOptions})
100
104
  }`;
101
105
  }
102
106
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-openapi-hyperfetch",
3
- "version": "0.3.0-beta",
3
+ "version": "0.3.1-beta",
4
4
  "description": "Nuxt useFetch, useAsyncData and Nuxt server OpenAPI generator",
5
5
  "type": "module",
6
6
  "author": "",
@@ -13,6 +13,7 @@ import { type Logger, createClackLogger } from '../../../cli/logger.js';
13
13
 
14
14
  // Runtime files that must be copied to the user's project
15
15
  const RUNTIME_FILES = [
16
+ 'connector-types.ts',
16
17
  'useListConnector.ts',
17
18
  'useDetailConnector.ts',
18
19
  'useFormConnector.ts',