nuxt-openapi-hyperfetch 0.2.8-alpha.1 → 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.
Files changed (35) hide show
  1. package/README.md +84 -6
  2. package/dist/generators/components/connector-generator/generator.js +1 -0
  3. package/dist/generators/components/connector-generator/templates.d.ts +1 -1
  4. package/dist/generators/components/connector-generator/templates.js +175 -44
  5. package/dist/generators/shared/runtime/connector-types.d.ts +104 -0
  6. package/dist/generators/shared/runtime/connector-types.js +10 -0
  7. package/dist/generators/shared/runtime/useFormConnector.js +8 -1
  8. package/dist/generators/shared/runtime/useListConnector.d.ts +5 -3
  9. package/dist/generators/shared/runtime/useListConnector.js +19 -10
  10. package/dist/generators/use-async-data/generator.js +4 -0
  11. package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +8 -2
  12. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
  13. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +9 -3
  14. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
  15. package/dist/generators/use-async-data/templates.js +24 -8
  16. package/dist/generators/use-fetch/generator.js +4 -0
  17. package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +9 -2
  18. package/dist/generators/use-fetch/templates.js +9 -5
  19. package/dist/index.js +2 -1
  20. package/package.json +1 -1
  21. package/src/generators/components/connector-generator/generator.ts +1 -0
  22. package/src/generators/components/connector-generator/templates.ts +211 -44
  23. package/src/generators/shared/runtime/connector-types.ts +142 -0
  24. package/src/generators/shared/runtime/useFormConnector.ts +9 -1
  25. package/src/generators/shared/runtime/useListConnector.ts +22 -10
  26. package/src/generators/use-async-data/generator.ts +8 -0
  27. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +37 -9
  28. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +34 -12
  29. package/src/generators/use-async-data/templates.ts +24 -9
  30. package/src/generators/use-fetch/generator.ts +8 -0
  31. package/src/generators/use-fetch/runtime/useApiRequest.ts +34 -4
  32. package/src/generators/use-fetch/templates.ts +9 -6
  33. package/src/index.ts +2 -1
  34. package/dist/generators/tanstack-query/generator.d.ts +0 -5
  35. package/dist/generators/tanstack-query/generator.js +0 -11
@@ -0,0 +1,142 @@
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
+
11
+ // Minimal structural aliases — compatible with Vue's Ref<T>, ComputedRef<T>, ShallowRef<T>.
12
+ // These are intentionally kept as simple as possible to avoid coupling to Vue's internals.
13
+ type Ref<T> = { value: T };
14
+ type ShallowRef<T> = { value: T };
15
+ type ComputedRef<T> = { readonly value: T };
16
+
17
+ // ─── Column / field defs (mirrors schema-analyzer output) ────────────────────
18
+
19
+ export interface ColumnDef {
20
+ key: string;
21
+ label: string;
22
+ type: string;
23
+ }
24
+
25
+ export interface FormFieldDef {
26
+ key: string;
27
+ label: string;
28
+ type: string;
29
+ required: boolean;
30
+ options?: { label: string; value: string }[];
31
+ placeholder?: string;
32
+ hidden?: boolean;
33
+ }
34
+
35
+ // ─── Pagination ───────────────────────────────────────────────────────────────
36
+
37
+ export interface PaginationState {
38
+ page: number;
39
+ perPage: number;
40
+ total: number;
41
+ totalPages: number;
42
+ goToPage: (page: number) => void;
43
+ nextPage: () => void;
44
+ prevPage: () => void;
45
+ setPerPage: (n: number) => void;
46
+ }
47
+
48
+ // ─── ListConnectorReturn ──────────────────────────────────────────────────────
49
+
50
+ export interface ListConnectorReturn<TRow = unknown> {
51
+ // State
52
+ rows: ComputedRef<TRow[]>;
53
+ columns: ComputedRef<ColumnDef[]>;
54
+ loading: ComputedRef<boolean>;
55
+ error: ComputedRef<unknown>;
56
+
57
+ // Pagination
58
+ pagination: ComputedRef<PaginationState | null>;
59
+ goToPage: (page: number) => void;
60
+ nextPage: () => void;
61
+ prevPage: () => void;
62
+ setPerPage: (n: number) => void;
63
+
64
+ // Selection
65
+ selected: Ref<TRow[]>;
66
+ onRowSelect: (row: TRow) => void;
67
+ clearSelection: () => void;
68
+
69
+ // Actions
70
+ refresh: () => void;
71
+
72
+ // CRUD coordination — public methods
73
+ create: () => void;
74
+ update: (row: TRow) => void;
75
+ remove: (row: TRow) => void;
76
+
77
+ // CRUD coordination — internal triggers (watch in the parent component)
78
+ _createTrigger: Ref<number>;
79
+ _updateTarget: ShallowRef<TRow | null>;
80
+ _deleteTarget: ShallowRef<TRow | null>;
81
+ }
82
+
83
+ // ─── DetailConnectorReturn ────────────────────────────────────────────────────
84
+
85
+ export interface DetailConnectorReturn<TItem = unknown> {
86
+ // State
87
+ item: ComputedRef<TItem | null>;
88
+ loading: ComputedRef<boolean>;
89
+ error: ComputedRef<unknown>;
90
+ fields: ComputedRef<FormFieldDef[]>;
91
+
92
+ // Actions
93
+ load: (id: string | number) => Promise<void>;
94
+ clear: () => void;
95
+
96
+ // Internals (advanced use)
97
+ _composable: unknown;
98
+ _currentId: Ref<string | number | null>;
99
+ }
100
+
101
+ // ─── FormConnectorReturn ──────────────────────────────────────────────────────
102
+
103
+ export interface FormConnectorReturn<TInput = Record<string, unknown>> {
104
+ // State
105
+ model: Ref<Partial<TInput>>;
106
+ errors: Ref<Record<string, string[]>>;
107
+ loading: Ref<boolean>;
108
+ submitError: Ref<unknown>;
109
+ submitted: Ref<boolean>;
110
+ isValid: ComputedRef<boolean>;
111
+ hasErrors: ComputedRef<boolean>;
112
+ fields: ComputedRef<FormFieldDef[]>;
113
+
114
+ // Callbacks (developer-assignable)
115
+ onSuccess: Ref<((data: unknown) => void) | null>;
116
+ onError: Ref<((err: unknown) => void) | null>;
117
+
118
+ // Actions
119
+ submit: () => Promise<void>;
120
+ reset: () => void;
121
+ setValues: (data: Partial<TInput>) => void;
122
+ }
123
+
124
+ // ─── DeleteConnectorReturn ────────────────────────────────────────────────────
125
+
126
+ export interface DeleteConnectorReturn<TItem = unknown> {
127
+ // State
128
+ target: Ref<TItem | null>;
129
+ isOpen: Ref<boolean>;
130
+ loading: Ref<boolean>;
131
+ error: Ref<unknown>;
132
+ hasTarget: ComputedRef<boolean>;
133
+
134
+ // Callbacks (developer-assignable)
135
+ onSuccess: Ref<((item: TItem) => void) | null>;
136
+ onError: Ref<((err: unknown) => void) | null>;
137
+
138
+ // Actions
139
+ setTarget: (item: TItem) => void;
140
+ cancel: () => void;
141
+ confirm: () => Promise<void>;
142
+ }
@@ -19,7 +19,15 @@ import { mergeZodErrors } from './zod-error-merger.js';
19
19
  * @param options { schema, fields, loadWith?, errorConfig? }
20
20
  */
21
21
  export function useFormConnector(composableFn, options = {}) {
22
- const { schema, fields = [], loadWith = null, errorConfig = {} } = options;
22
+ const { schema: baseSchema, schemaOverride, fields = [], loadWith = null, errorConfig = {} } = options;
23
+
24
+ // Resolve the active schema:
25
+ // schemaOverride(base) — extend or refine the generated schema
26
+ // schemaOverride — replace the generated schema entirely
27
+ // baseSchema — the generated schema unchanged (default)
28
+ const schema = schemaOverride
29
+ ? (typeof schemaOverride === 'function' ? schemaOverride(baseSchema) : schemaOverride)
30
+ : baseSchema;
23
31
 
24
32
  // ── Form state ─────────────────────────────────────────────────────────────
25
33
 
@@ -13,14 +13,16 @@
13
13
  import { ref, computed, shallowRef } from 'vue';
14
14
 
15
15
  /**
16
- * @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
17
- * @param options Configuration for the list connector
16
+ * @param factory A zero-argument function that calls and returns the underlying
17
+ * useAsyncData composable, e.g. () => useAsyncDataGetPets(params)
18
+ * The factory is called once during connector setup (inside setup()).
19
+ * @param options Configuration for the list connector
18
20
  */
19
- export function useListConnector(composableFn, options = {}) {
20
- const { paginated = false, columns = [] } = options;
21
+ export function useListConnector(factory, options = {}) {
22
+ const { paginated = false, columns = [], columnLabels = {}, columnLabel = null } = options;
21
23
 
22
24
  // ── Execute the underlying composable ──────────────────────────────────────
23
- const composable = composableFn({ paginated });
25
+ const composable = factory();
24
26
 
25
27
  // ── Derived state ──────────────────────────────────────────────────────────
26
28
 
@@ -40,19 +42,19 @@ export function useListConnector(composableFn, options = {}) {
40
42
  const pagination = computed(() => composable.pagination?.value ?? null);
41
43
 
42
44
  function goToPage(page) {
43
- composable.goToPage?.(page);
45
+ composable.pagination?.value?.goToPage?.(page);
44
46
  }
45
47
 
46
48
  function nextPage() {
47
- composable.nextPage?.();
49
+ composable.pagination?.value?.nextPage?.();
48
50
  }
49
51
 
50
52
  function prevPage() {
51
- composable.prevPage?.();
53
+ composable.pagination?.value?.prevPage?.();
52
54
  }
53
55
 
54
56
  function setPerPage(n) {
55
- composable.setPerPage?.(n);
57
+ composable.pagination?.value?.setPerPage?.(n);
56
58
  }
57
59
 
58
60
  // ── Row selection ──────────────────────────────────────────────────────────
@@ -113,10 +115,20 @@ export function useListConnector(composableFn, options = {}) {
113
115
  _deleteTarget.value = row;
114
116
  }
115
117
 
118
+ // Apply label overrides: columnLabel function takes priority over columnLabels map
119
+ const resolvedColumns = computed(() =>
120
+ columns.map((col) => ({
121
+ ...col,
122
+ label: columnLabel
123
+ ? columnLabel(col.key)
124
+ : (columnLabels[col.key] ?? col.label),
125
+ }))
126
+ );
127
+
116
128
  return {
117
129
  // State
118
130
  rows,
119
- columns: computed(() => columns),
131
+ columns: resolvedColumns,
120
132
  loading,
121
133
  error,
122
134
 
@@ -104,6 +104,14 @@ export async function generateUseAsyncDataComposables(
104
104
  );
105
105
  const sharedHelpersDest = path.join(sharedRuntimeDir, 'apiHelpers.ts');
106
106
  await fs.copyFile(sharedHelpersSource, sharedHelpersDest);
107
+
108
+ // Copy shared pagination.ts
109
+ const sharedPaginationSource = path.resolve(
110
+ __dirname,
111
+ '../../../src/generators/shared/runtime/pagination.ts'
112
+ );
113
+ const sharedPaginationDest = path.join(sharedRuntimeDir, 'pagination.ts');
114
+ await fs.copyFile(sharedPaginationSource, sharedPaginationDest);
107
115
  mainSpinner.stop('Runtime files copied');
108
116
 
109
117
  // 5. Calculate relative import path from composables to APIs
@@ -37,14 +37,39 @@ type MaybeTransformed<T, Options> = Options extends { transform: (...args: any)
37
37
  ? any // With nested paths, type inference is complex, so we use any
38
38
  : T;
39
39
 
40
+ type PickInput = ReadonlyArray<string> | undefined;
41
+
42
+ type HasNestedPath<K extends ReadonlyArray<string>> =
43
+ Extract<K[number], `${string}.${string}`> extends never ? false : true;
44
+
45
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string>
46
+ ? HasNestedPath<K> extends true
47
+ ? any
48
+ : Pick<T, Extract<K[number], keyof T>>
49
+ : T;
50
+
51
+ type InferPick<Options> = Options extends { pick: infer K extends ReadonlyArray<string> }
52
+ ? K
53
+ : undefined;
54
+
55
+ type InferData<T, Options> = Options extends { transform: (...args: any) => infer R }
56
+ ? R
57
+ : PickedData<T, InferPick<Options>>;
58
+
40
59
  /**
41
60
  * Options for useAsyncData API requests with lifecycle callbacks.
42
61
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
43
62
  * Native options like baseURL, method, body, headers, query, lazy, server, immediate, dedupe, etc. are all available.
44
63
  * watch: boolean (true = auto-watch reactive params, false = disable auto-refresh)
45
64
  */
46
- export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> &
47
- Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'watch'> & {
65
+ export type ApiAsyncDataOptions<
66
+ T,
67
+ DataT = T,
68
+ PickT extends PickInput = undefined,
69
+ > = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> &
70
+ Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'watch'> & {
71
+ pick?: PickT;
72
+ transform?: (data: PickedData<T, PickT>) => DataT;
48
73
  /**
49
74
  * Enable automatic refresh when reactive params/url change (default: true).
50
75
  * Set to false to disable auto-refresh entirely.
@@ -61,10 +86,13 @@ export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> &
61
86
  * - Global headers from useApiHeaders or $getApiHeaders
62
87
  * - Watch pattern for reactive parameters
63
88
  */
64
- export function useApiAsyncData<T>(
89
+ export function useApiAsyncData<
90
+ T,
91
+ Options extends ApiAsyncDataOptions<T, any, any> = ApiAsyncDataOptions<T>,
92
+ >(
65
93
  key: string,
66
94
  url: string | (() => string),
67
- options?: ApiAsyncDataOptions<T>
95
+ options?: Options
68
96
  ) {
69
97
  const {
70
98
  method = 'GET',
@@ -295,7 +323,7 @@ export function useApiAsyncData<T>(
295
323
  };
296
324
 
297
325
  // Use Nuxt's useAsyncData with a computed key for proper cache isolation per params
298
- const result = useAsyncData<MaybeTransformed<T, ApiAsyncDataOptions<T>>>(computedKey, fetchFn, {
326
+ const result = useAsyncData<InferData<T, Options>>(computedKey, fetchFn, {
299
327
  immediate,
300
328
  lazy,
301
329
  server,
@@ -320,10 +348,10 @@ export function useApiAsyncData<T>(
320
348
  ...paginationState.value,
321
349
  hasNextPage: hasNextPage.value,
322
350
  hasPrevPage: hasPrevPage.value,
351
+ goToPage,
352
+ nextPage,
353
+ prevPage,
354
+ setPerPage,
323
355
  })),
324
- goToPage,
325
- nextPage,
326
- prevPage,
327
- setPerPage,
328
356
  };
329
357
  }
@@ -48,18 +48,35 @@ type MaybeTransformedRaw<T, Options> = Options extends { transform: (...args: an
48
48
  ? RawResponse<any> // With nested paths, type inference is complex
49
49
  : RawResponse<T>;
50
50
 
51
+ type PickInput = ReadonlyArray<string> | undefined;
52
+
53
+ type HasNestedPath<K extends ReadonlyArray<string>> =
54
+ Extract<K[number], `${string}.${string}`> extends never ? false : true;
55
+
56
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string>
57
+ ? HasNestedPath<K> extends true
58
+ ? any
59
+ : Pick<T, Extract<K[number], keyof T>>
60
+ : T;
61
+
51
62
  /**
52
63
  * Options for useAsyncData Raw API requests.
53
64
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
54
65
  * onSuccess receives data AND the full response (headers, status, statusText).
55
66
  */
56
- export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> &
57
- Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
67
+ export type ApiAsyncDataRawOptions<
68
+ T,
69
+ DataT = T,
70
+ PickT extends PickInput = undefined,
71
+ > = Omit<BaseApiRequestOptions<T>, 'onSuccess' | 'transform' | 'pick'> &
72
+ Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'onSuccess'> & {
73
+ pick?: PickT;
74
+ transform?: (data: PickedData<T, PickT>) => DataT;
58
75
  /**
59
76
  * Called when the request succeeds — receives both data and the full response object.
60
77
  */
61
78
  onSuccess?: (
62
- data: T,
79
+ data: DataT,
63
80
  response: { headers: Headers; status: number; statusText: string; url: string }
64
81
  ) => void | Promise<void>;
65
82
  };
@@ -76,10 +93,15 @@ export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSucces
76
93
  * - Global headers from useApiHeaders or $getApiHeaders
77
94
  * - Watch pattern for reactive parameters
78
95
  */
79
- export function useApiAsyncDataRaw<T>(
96
+ export function useApiAsyncDataRaw<
97
+ T,
98
+ DataT = T,
99
+ PickT extends PickInput = undefined,
100
+ Options extends ApiAsyncDataRawOptions<T, DataT, PickT> = ApiAsyncDataRawOptions<T, DataT, PickT>,
101
+ >(
80
102
  key: string,
81
103
  url: string | (() => string),
82
- options?: ApiAsyncDataRawOptions<T>
104
+ options?: Options
83
105
  ) {
84
106
  const {
85
107
  method = 'GET',
@@ -151,7 +173,7 @@ export function useApiAsyncDataRaw<T>(
151
173
  };
152
174
 
153
175
  // Fetch function for useAsyncData
154
- const fetchFn = async (): Promise<RawResponse<T>> => {
176
+ const fetchFn = async (): Promise<RawResponse<DataT>> => {
155
177
  // Get URL value for merging callbacks
156
178
  const finalUrl = typeof url === 'function' ? url() : url;
157
179
 
@@ -253,7 +275,7 @@ export function useApiAsyncDataRaw<T>(
253
275
  }
254
276
 
255
277
  // Construct the raw response object
256
- const rawResponse: RawResponse<T> = {
278
+ const rawResponse: RawResponse<DataT> = {
257
279
  data,
258
280
  headers: response.headers,
259
281
  status: response.status,
@@ -290,7 +312,7 @@ export function useApiAsyncDataRaw<T>(
290
312
  };
291
313
 
292
314
  // Use Nuxt's useAsyncData with a computed key for proper cache isolation per params
293
- const result = useAsyncData<MaybeTransformedRaw<T, ApiAsyncDataRawOptions<T>>>(computedKey, fetchFn, {
315
+ const result = useAsyncData<MaybeTransformedRaw<T, Options>>(computedKey, fetchFn, {
294
316
  immediate,
295
317
  lazy,
296
318
  server,
@@ -315,10 +337,10 @@ export function useApiAsyncDataRaw<T>(
315
337
  ...paginationState.value,
316
338
  hasNextPage: hasNextPage.value,
317
339
  hasPrevPage: hasPrevPage.value,
340
+ goToPage,
341
+ nextPage,
342
+ prevPage,
343
+ setPerPage,
318
344
  })),
319
- goToPage,
320
- nextPage,
321
- prevPage,
322
- setPerPage,
323
345
  };
324
346
  }
@@ -136,17 +136,18 @@ function generateFunctionBody(
136
136
  ): string {
137
137
  const hasParams = !!method.requestType;
138
138
  const paramsArg = hasParams ? `params: ${method.requestType}` : '';
139
+ const responseType = method.responseType !== 'void' ? method.responseType : 'void';
139
140
 
140
141
  // Determine the options type based on isRaw
141
142
  const optionsType = isRaw
142
- ? `ApiAsyncDataRawOptions<${method.responseType}>`
143
- : `ApiAsyncDataOptions<${method.responseType}>`;
144
- const optionsArg = `options?: ${optionsType}`;
143
+ ? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
144
+ : `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
145
+ const optionsDefaultType = isRaw
146
+ ? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
147
+ : `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
148
+ const optionsArg = `options?: Options`;
145
149
  const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
146
150
 
147
- // Determine the response type generic
148
- const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
149
-
150
151
  // Generate unique key for useAsyncData
151
152
  const composableName =
152
153
  isRaw && method.rawMethodName
@@ -162,6 +163,12 @@ function generateFunctionBody(
162
163
 
163
164
  // Choose the correct wrapper function
164
165
  const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
166
+ const wrapperCall = isRaw
167
+ ? `${wrapperFunction}<${responseType}, DataT, PickT, Options>`
168
+ : `${wrapperFunction}<${responseType}, Options>`;
169
+ const returnType = isRaw
170
+ ? `ReturnType<typeof ${wrapperFunction}<${responseType}, DataT, PickT, Options>>`
171
+ : `ReturnType<typeof ${wrapperFunction}<${responseType}, Options>>`;
165
172
 
166
173
  const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
167
174
 
@@ -169,11 +176,19 @@ function generateFunctionBody(
169
176
  ? ` 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]`
170
177
  : ` const _hasKey = typeof args[0] === 'string'\n const options = _hasKey ? { cacheKey: args[0], ...args[1] } : args[0]`;
171
178
 
172
- return `${description}export function ${composableName}(key: string, ${args})
173
- export function ${composableName}(${args})
179
+ return `${description}export function ${composableName}<
180
+ DataT = ${responseType},
181
+ PickT extends ReadonlyArray<string> | undefined = undefined,
182
+ Options extends ${optionsType} = ${optionsDefaultType}
183
+ >(key: string, ${args}): ${returnType}
184
+ export function ${composableName}<
185
+ DataT = ${responseType},
186
+ PickT extends ReadonlyArray<string> | undefined = undefined,
187
+ Options extends ${optionsType} = ${optionsDefaultType}
188
+ >(${args}): ${returnType}
174
189
  export function ${composableName}(...args: any[]) {
175
190
  ${argsExtraction}${pInit}
176
- return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
191
+ return ${wrapperCall}(${key}, ${url}, ${fetchOptions})
177
192
  }`;
178
193
  }
179
194
 
@@ -91,6 +91,14 @@ export async function generateUseFetchComposables(
91
91
  );
92
92
  const sharedHelpersDest = path.join(sharedRuntimeDir, 'apiHelpers.ts');
93
93
  await fs.copyFile(sharedHelpersSource, sharedHelpersDest);
94
+
95
+ // Copy shared pagination.ts
96
+ const sharedPaginationSource = path.resolve(
97
+ __dirname,
98
+ '../../../src/generators/shared/runtime/pagination.ts'
99
+ );
100
+ const sharedPaginationDest = path.join(sharedRuntimeDir, 'pagination.ts');
101
+ await fs.copyFile(sharedPaginationSource, sharedPaginationDest);
94
102
  mainSpinner.stop('Runtime files copied');
95
103
 
96
104
  // 5. Calculate relative import path from composables to APIs
@@ -37,12 +37,39 @@ type MaybeTransformed<T, Options> = Options extends { transform: (...args: any)
37
37
  ? any // With nested paths, type inference is complex, so we use any
38
38
  : T;
39
39
 
40
+ type PickInput = ReadonlyArray<string> | undefined;
41
+
42
+ type HasNestedPath<K extends ReadonlyArray<string>> =
43
+ Extract<K[number], `${string}.${string}`> extends never ? false : true;
44
+
45
+ type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string>
46
+ ? HasNestedPath<K> extends true
47
+ ? any
48
+ : Pick<T, Extract<K[number], keyof T>>
49
+ : T;
50
+
51
+ type InferPick<Options> = Options extends { pick: infer K extends ReadonlyArray<string> }
52
+ ? K
53
+ : undefined;
54
+
55
+ type InferData<T, Options> = Options extends { transform: (...args: any) => infer R }
56
+ ? R
57
+ : PickedData<T, InferPick<Options>>;
58
+
40
59
  /**
41
60
  * Options for useFetch API requests with lifecycle callbacks.
42
61
  * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
43
62
  * Native options like baseURL, method, body, headers, query, lazy, server, immediate, etc. are all available.
44
63
  */
45
- export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetchOptions<T>, 'transform' | 'pick'>;
64
+ export type ApiRequestOptions<
65
+ T = any,
66
+ DataT = T,
67
+ PickT extends PickInput = undefined,
68
+ > = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> &
69
+ Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick'> & {
70
+ pick?: PickT;
71
+ transform?: (data: PickedData<T, PickT>) => DataT;
72
+ };
46
73
 
47
74
  /**
48
75
  * Enhanced useFetch wrapper with lifecycle callbacks and request interception
@@ -80,7 +107,10 @@ export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetc
80
107
  * });
81
108
  * ```
82
109
  */
83
- export function useApiRequest<T = any, Options extends ApiRequestOptions<T> = ApiRequestOptions<T>>(
110
+ export function useApiRequest<
111
+ T = any,
112
+ Options extends ApiRequestOptions<T, any, any> = ApiRequestOptions<T>,
113
+ >(
84
114
  url: string | (() => string),
85
115
  options?: Options
86
116
  ) {
@@ -224,10 +254,10 @@ export function useApiRequest<T = any, Options extends ApiRequestOptions<T> = Ap
224
254
  }
225
255
 
226
256
  // Make the actual request using Nuxt's useFetch
227
- const result = useFetch<T>(url, modifiedOptions);
257
+ const result = useFetch<T>(url, modifiedOptions as UseFetchOptions<T, InferData<T, Options>>);
228
258
 
229
259
  // Create a ref for transformed data
230
- type TransformedType = MaybeTransformed<T, Options>;
260
+ type TransformedType = InferData<T, Options>;
231
261
  const transformedData = ref<TransformedType | null>(null);
232
262
 
233
263
  // Track if callbacks have been executed to avoid duplicates
@@ -116,12 +116,11 @@ function generateImports(method: MethodInfo, apiImportPath: string): string {
116
116
  function generateFunctionBody(method: MethodInfo, options?: GenerateOptions): string {
117
117
  const hasParams = !!method.requestType;
118
118
  const paramsArg = hasParams ? `params: ${method.requestType}` : '';
119
- const optionsType = `ApiRequestOptions<${method.responseType}>`;
120
- const optionsArg = `options?: ${optionsType}`;
119
+ const responseType = method.responseType !== 'void' ? method.responseType : 'void';
120
+ const optionsType = `ApiRequestOptions<${responseType}, DataT, PickT>`;
121
+ const optionsArg = `options?: Options`;
121
122
  const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
122
123
 
123
- const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
124
-
125
124
  const url = generateUrl(method);
126
125
  const fetchOptions = generateFetchOptions(method, options);
127
126
 
@@ -129,8 +128,12 @@ function generateFunctionBody(method: MethodInfo, options?: GenerateOptions): st
129
128
 
130
129
  const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
131
130
 
132
- return `${description}export const ${method.composableName} = (${args}) => {${pInit}
133
- return useApiRequest${responseTypeGeneric}(${url}, ${fetchOptions})
131
+ return `${description}export const ${method.composableName} = <
132
+ DataT = ${responseType},
133
+ PickT extends ReadonlyArray<string> | undefined = undefined,
134
+ Options extends ${optionsType} = ApiRequestOptions<${responseType}, DataT, PickT>
135
+ >(${args}) => {${pInit}
136
+ return useApiRequest<${responseType}, Options>(${url}, ${fetchOptions})
134
137
  }`;
135
138
  }
136
139
 
package/src/index.ts CHANGED
@@ -87,7 +87,8 @@ program
87
87
  options.backend === 'official' || options.backend === 'heyapi'
88
88
  ? options.backend
89
89
  : undefined,
90
- createUseAsyncDataConnectors: options.connectors,
90
+ // Only propagate if explicitly passed — undefined means "ask the user"
91
+ createUseAsyncDataConnectors: options.connectors === true ? true : undefined,
91
92
  });
92
93
 
93
94
  if (config.verbose) {
@@ -1,5 +0,0 @@
1
- /**
2
- * Placeholder for TanStack Query composables generator
3
- * TODO: Implement TanStack Query generation
4
- */
5
- export declare function generateTanstackQueryComposables(inputDir: string, outputDir: string): void;
@@ -1,11 +0,0 @@
1
- /**
2
- * Placeholder for TanStack Query composables generator
3
- * TODO: Implement TanStack Query generation
4
- */
5
- export function generateTanstackQueryComposables(inputDir, outputDir) {
6
- console.log('\n📦 @tanstack/vue-query generator\n');
7
- console.log(` Input: ${inputDir}`);
8
- console.log(` Output: ${outputDir}`);
9
- console.log('\n⚠️ This generator is not yet implemented.');
10
- console.log(' Coming soon!\n');
11
- }