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.
- package/README.md +84 -6
- package/dist/generators/components/connector-generator/generator.js +1 -0
- package/dist/generators/components/connector-generator/templates.d.ts +1 -1
- package/dist/generators/components/connector-generator/templates.js +175 -44
- package/dist/generators/shared/runtime/connector-types.d.ts +104 -0
- package/dist/generators/shared/runtime/connector-types.js +10 -0
- package/dist/generators/shared/runtime/useFormConnector.js +8 -1
- package/dist/generators/shared/runtime/useListConnector.d.ts +5 -3
- package/dist/generators/shared/runtime/useListConnector.js +19 -10
- package/dist/generators/use-async-data/generator.js +4 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +8 -2
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +9 -3
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
- package/dist/generators/use-async-data/templates.js +24 -8
- package/dist/generators/use-fetch/generator.js +4 -0
- package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +9 -2
- package/dist/generators/use-fetch/templates.js +9 -5
- package/dist/index.js +2 -1
- package/package.json +1 -1
- package/src/generators/components/connector-generator/generator.ts +1 -0
- package/src/generators/components/connector-generator/templates.ts +211 -44
- package/src/generators/shared/runtime/connector-types.ts +142 -0
- package/src/generators/shared/runtime/useFormConnector.ts +9 -1
- package/src/generators/shared/runtime/useListConnector.ts +22 -10
- package/src/generators/use-async-data/generator.ts +8 -0
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +37 -9
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +34 -12
- package/src/generators/use-async-data/templates.ts +24 -9
- package/src/generators/use-fetch/generator.ts +8 -0
- package/src/generators/use-fetch/runtime/useApiRequest.ts +34 -4
- package/src/generators/use-fetch/templates.ts +9 -6
- package/src/index.ts +2 -1
- package/dist/generators/tanstack-query/generator.d.ts +0 -5
- 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
|
|
17
|
-
*
|
|
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(
|
|
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 =
|
|
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:
|
|
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<
|
|
47
|
-
|
|
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<
|
|
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?:
|
|
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<
|
|
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<
|
|
57
|
-
|
|
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:
|
|
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<
|
|
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?:
|
|
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<
|
|
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<
|
|
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,
|
|
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<${
|
|
143
|
-
: `ApiAsyncDataOptions<${
|
|
144
|
-
const
|
|
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}
|
|
173
|
-
|
|
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 ${
|
|
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<
|
|
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<
|
|
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 =
|
|
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
|
|
120
|
-
const
|
|
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} =
|
|
133
|
-
|
|
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
|
-
|
|
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,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
|
-
}
|