nuxt-openapi-hyperfetch 0.1.6-alpha.1 → 0.2.7-alpha.1
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/CONTRIBUTING.md +291 -292
- package/INSTRUCTIONS.md +327 -327
- package/LICENSE +202 -202
- package/README.md +231 -227
- package/dist/cli/logger.d.ts +26 -0
- package/dist/cli/logger.js +36 -0
- package/dist/cli/logo.js +6 -6
- package/dist/generators/components/connector-generator/generator.d.ts +12 -0
- package/dist/generators/components/connector-generator/generator.js +116 -0
- package/dist/generators/components/connector-generator/templates.d.ts +18 -0
- package/dist/generators/components/connector-generator/templates.js +222 -0
- package/dist/generators/components/connector-generator/types.d.ts +32 -0
- package/dist/generators/components/connector-generator/types.js +7 -0
- package/dist/generators/components/schema-analyzer/index.d.ts +17 -0
- package/dist/generators/components/schema-analyzer/index.js +20 -0
- package/dist/generators/components/schema-analyzer/intent-detector.d.ts +17 -0
- package/dist/generators/components/schema-analyzer/intent-detector.js +143 -0
- package/dist/generators/components/schema-analyzer/openapi-reader.d.ts +11 -0
- package/dist/generators/components/schema-analyzer/openapi-reader.js +76 -0
- package/dist/generators/components/schema-analyzer/resource-grouper.d.ts +6 -0
- package/dist/generators/components/schema-analyzer/resource-grouper.js +132 -0
- package/dist/generators/components/schema-analyzer/schema-field-mapper.d.ts +35 -0
- package/dist/generators/components/schema-analyzer/schema-field-mapper.js +220 -0
- package/dist/generators/components/schema-analyzer/types.d.ts +156 -0
- package/dist/generators/components/schema-analyzer/types.js +7 -0
- package/dist/generators/nuxt-server/generator.d.ts +2 -1
- package/dist/generators/nuxt-server/generator.js +21 -21
- package/dist/generators/shared/runtime/apiHelpers.d.ts +98 -41
- package/dist/generators/shared/runtime/apiHelpers.js +97 -104
- package/dist/generators/shared/runtime/pagination.d.ts +168 -0
- package/dist/generators/shared/runtime/pagination.js +179 -0
- package/dist/generators/shared/runtime/useDeleteConnector.d.ts +16 -0
- package/dist/generators/shared/runtime/useDeleteConnector.js +93 -0
- package/dist/generators/shared/runtime/useDetailConnector.d.ts +14 -0
- package/dist/generators/shared/runtime/useDetailConnector.js +50 -0
- package/dist/generators/shared/runtime/useFormConnector.d.ts +19 -0
- package/dist/generators/shared/runtime/useFormConnector.js +113 -0
- package/dist/generators/shared/runtime/useListConnector.d.ts +25 -0
- package/dist/generators/shared/runtime/useListConnector.js +125 -0
- package/dist/generators/shared/runtime/zod-error-merger.d.ts +23 -0
- package/dist/generators/shared/runtime/zod-error-merger.js +106 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.js +54 -11
- package/dist/generators/shared/templates/api-pagination-plugin.d.ts +51 -0
- package/dist/generators/shared/templates/api-pagination-plugin.js +152 -0
- package/dist/generators/use-async-data/generator.d.ts +2 -1
- package/dist/generators/use-async-data/generator.js +14 -14
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +130 -17
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +103 -13
- package/dist/generators/use-async-data/templates.js +20 -14
- package/dist/generators/use-fetch/generator.d.ts +2 -1
- package/dist/generators/use-fetch/generator.js +12 -12
- package/dist/generators/use-fetch/runtime/useApiRequest.js +149 -40
- package/dist/generators/use-fetch/templates.js +14 -14
- package/dist/index.js +25 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +93 -0
- package/dist/module/types.d.ts +27 -0
- package/dist/module/types.js +1 -0
- package/docs/API-REFERENCE.md +886 -887
- package/docs/generated-components.md +615 -0
- package/docs/headless-composables-ui.md +569 -0
- package/eslint.config.js +13 -0
- package/package.json +29 -2
- package/src/cli/config.ts +140 -140
- package/src/cli/logger.ts +124 -66
- package/src/cli/logo.ts +25 -25
- package/src/cli/types.ts +50 -50
- package/src/generators/components/connector-generator/generator.ts +138 -0
- package/src/generators/components/connector-generator/templates.ts +254 -0
- package/src/generators/components/connector-generator/types.ts +34 -0
- package/src/generators/components/schema-analyzer/index.ts +44 -0
- package/src/generators/components/schema-analyzer/intent-detector.ts +187 -0
- package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -0
- package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -0
- package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -0
- package/src/generators/components/schema-analyzer/types.ts +177 -0
- package/src/generators/nuxt-server/generator.ts +272 -270
- package/src/generators/shared/runtime/apiHelpers.ts +535 -481
- package/src/generators/shared/runtime/pagination.ts +323 -0
- package/src/generators/shared/runtime/useDeleteConnector.ts +109 -0
- package/src/generators/shared/runtime/useDetailConnector.ts +64 -0
- package/src/generators/shared/runtime/useFormConnector.ts +139 -0
- package/src/generators/shared/runtime/useListConnector.ts +148 -0
- package/src/generators/shared/runtime/zod-error-merger.ts +119 -0
- package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -352
- package/src/generators/shared/templates/api-pagination-plugin.ts +158 -0
- package/src/generators/use-async-data/generator.ts +205 -204
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -214
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -230
- package/src/generators/use-async-data/templates.ts +257 -250
- package/src/generators/use-fetch/generator.ts +170 -169
- package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -234
- package/src/generators/use-fetch/templates.ts +214 -214
- package/src/index.ts +303 -265
- package/src/module/index.ts +133 -0
- package/src/module/types.ts +31 -0
- package/src/generators/tanstack-query/generator.ts +0 -11
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* useListConnector — Runtime connector for list/table endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Wraps a useAsyncData composable that returns an array and exposes:
|
|
6
|
+
* - rows, columns, loading, error state
|
|
7
|
+
* - pagination helpers (if paginated: true)
|
|
8
|
+
* - row selection
|
|
9
|
+
* - modal coordination helpers (openCreate, openUpdate, openDelete)
|
|
10
|
+
*
|
|
11
|
+
* Copied to the user's project alongside the generated connectors.
|
|
12
|
+
*/
|
|
13
|
+
import { ref, computed, shallowRef } from 'vue';
|
|
14
|
+
/**
|
|
15
|
+
* @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
|
|
16
|
+
* @param options Configuration for the list connector
|
|
17
|
+
*/
|
|
18
|
+
export function useListConnector(composableFn, options = {}) {
|
|
19
|
+
const { paginated = false, columns = [] } = options;
|
|
20
|
+
// ── Execute the underlying composable ──────────────────────────────────────
|
|
21
|
+
const composable = composableFn({ paginated });
|
|
22
|
+
// ── Derived state ──────────────────────────────────────────────────────────
|
|
23
|
+
const rows = computed(() => {
|
|
24
|
+
const data = composable.data?.value;
|
|
25
|
+
if (!data)
|
|
26
|
+
return [];
|
|
27
|
+
// Support both direct arrays and { data: [...] } shapes
|
|
28
|
+
if (Array.isArray(data))
|
|
29
|
+
return data;
|
|
30
|
+
if (Array.isArray(data.data))
|
|
31
|
+
return data.data;
|
|
32
|
+
return [];
|
|
33
|
+
});
|
|
34
|
+
const loading = computed(() => composable.pending?.value ?? false);
|
|
35
|
+
const error = computed(() => composable.error?.value ?? null);
|
|
36
|
+
// Pagination — passthrough from the underlying composable when paginated: true
|
|
37
|
+
const pagination = computed(() => composable.pagination?.value ?? null);
|
|
38
|
+
function goToPage(page) {
|
|
39
|
+
composable.goToPage?.(page);
|
|
40
|
+
}
|
|
41
|
+
function nextPage() {
|
|
42
|
+
composable.nextPage?.();
|
|
43
|
+
}
|
|
44
|
+
function prevPage() {
|
|
45
|
+
composable.prevPage?.();
|
|
46
|
+
}
|
|
47
|
+
function setPerPage(n) {
|
|
48
|
+
composable.setPerPage?.(n);
|
|
49
|
+
}
|
|
50
|
+
// ── Row selection ──────────────────────────────────────────────────────────
|
|
51
|
+
const selected = ref([]);
|
|
52
|
+
function onRowSelect(row) {
|
|
53
|
+
const idx = selected.value.indexOf(row);
|
|
54
|
+
if (idx === -1) {
|
|
55
|
+
selected.value = [...selected.value, row];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
selected.value = selected.value.filter((r) => r !== row);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function clearSelection() {
|
|
62
|
+
selected.value = [];
|
|
63
|
+
}
|
|
64
|
+
// ── Refresh ────────────────────────────────────────────────────────────────
|
|
65
|
+
function refresh() {
|
|
66
|
+
void composable.refresh?.();
|
|
67
|
+
}
|
|
68
|
+
// ── CRUD coordination (triggers watched by the parent component) ──────────
|
|
69
|
+
// The parent watches these refs to decide when to open a modal/panel/route.
|
|
70
|
+
// Naming convention:
|
|
71
|
+
// _createTrigger — Ref<number> counter; no data needed (form starts empty)
|
|
72
|
+
// _updateTarget — ShallowRef<Row|null>; the row to pre-fill the edit form
|
|
73
|
+
// _deleteTarget — ShallowRef<Row|null>; the row to confirm deletion for
|
|
74
|
+
const _createTrigger = ref(0);
|
|
75
|
+
const _updateTarget = shallowRef(null);
|
|
76
|
+
const _deleteTarget = shallowRef(null);
|
|
77
|
+
/**
|
|
78
|
+
* Signal the parent that the user wants to create a new item.
|
|
79
|
+
* The parent should watch `_createTrigger` and open a create form/modal.
|
|
80
|
+
*/
|
|
81
|
+
function create() {
|
|
82
|
+
_createTrigger.value++;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Signal the parent that the user wants to edit `row`.
|
|
86
|
+
* The parent should watch `_updateTarget` and open an edit form/modal.
|
|
87
|
+
*/
|
|
88
|
+
function update(row) {
|
|
89
|
+
_updateTarget.value = row;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Signal the parent that the user wants to delete `row`.
|
|
93
|
+
* The parent should watch `_deleteTarget` and open a confirmation modal.
|
|
94
|
+
*/
|
|
95
|
+
function remove(row) {
|
|
96
|
+
_deleteTarget.value = row;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
// State
|
|
100
|
+
rows,
|
|
101
|
+
columns: computed(() => columns),
|
|
102
|
+
loading,
|
|
103
|
+
error,
|
|
104
|
+
// Pagination
|
|
105
|
+
pagination,
|
|
106
|
+
goToPage,
|
|
107
|
+
nextPage,
|
|
108
|
+
prevPage,
|
|
109
|
+
setPerPage,
|
|
110
|
+
// Selection
|
|
111
|
+
selected,
|
|
112
|
+
onRowSelect,
|
|
113
|
+
clearSelection,
|
|
114
|
+
// Actions
|
|
115
|
+
refresh,
|
|
116
|
+
// CRUD coordination — public methods
|
|
117
|
+
create,
|
|
118
|
+
update,
|
|
119
|
+
remove,
|
|
120
|
+
// CRUD coordination — internal triggers (watch these in the parent)
|
|
121
|
+
_createTrigger,
|
|
122
|
+
_updateTarget,
|
|
123
|
+
_deleteTarget,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zod-error-merger — Merge Zod fieldErrors with per-field error message overrides.
|
|
3
|
+
*
|
|
4
|
+
* Priority (highest → lowest):
|
|
5
|
+
* 3. config.fields[fieldName].errors[zodCode] — per-field, per-code override
|
|
6
|
+
* 2. z.setErrorMap() — global translation (set by the developer)
|
|
7
|
+
* 1. Zod defaults — built-in English messages
|
|
8
|
+
*
|
|
9
|
+
* This function handles priority 3 only. Priority 2 is handled automatically by Zod
|
|
10
|
+
* when the developer sets z.setErrorMap() in their plugins/zod-i18n.ts.
|
|
11
|
+
*
|
|
12
|
+
* Copied to the user's project alongside the generated connectors.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Convert Zod's flatten().fieldErrors to Record<string, string>,
|
|
16
|
+
* merging optional per-field message overrides from the component config.
|
|
17
|
+
*
|
|
18
|
+
* @param fieldErrors Output of zodResult.error.flatten().fieldErrors
|
|
19
|
+
* Shape: { fieldName: string[] }
|
|
20
|
+
* @param errorConfig Optional per-field error config from index.ts
|
|
21
|
+
* Shape: { fieldName: { required?: string, min?: string, ... } }
|
|
22
|
+
*/
|
|
23
|
+
export declare function mergeZodErrors(fieldErrors: any, errorConfig?: {}): {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* zod-error-merger — Merge Zod fieldErrors with per-field error message overrides.
|
|
4
|
+
*
|
|
5
|
+
* Priority (highest → lowest):
|
|
6
|
+
* 3. config.fields[fieldName].errors[zodCode] — per-field, per-code override
|
|
7
|
+
* 2. z.setErrorMap() — global translation (set by the developer)
|
|
8
|
+
* 1. Zod defaults — built-in English messages
|
|
9
|
+
*
|
|
10
|
+
* This function handles priority 3 only. Priority 2 is handled automatically by Zod
|
|
11
|
+
* when the developer sets z.setErrorMap() in their plugins/zod-i18n.ts.
|
|
12
|
+
*
|
|
13
|
+
* Copied to the user's project alongside the generated connectors.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Map Zod issue codes to friendlier config key names.
|
|
17
|
+
* The developer uses these keys in config.fields[name].errors:
|
|
18
|
+
*
|
|
19
|
+
* errors: {
|
|
20
|
+
* required: 'Name is required',
|
|
21
|
+
* min: 'At least 1 character',
|
|
22
|
+
* max: 'Max 100 characters',
|
|
23
|
+
* email: 'Invalid email',
|
|
24
|
+
* enum: 'Select a valid option',
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
const ZOD_CODE_TO_CONFIG_KEY = {
|
|
28
|
+
too_small: 'min',
|
|
29
|
+
too_big: 'max',
|
|
30
|
+
invalid_type: 'required', // most common: field is undefined/null
|
|
31
|
+
invalid_enum_value: 'enum',
|
|
32
|
+
invalid_string: 'format',
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Convert Zod's flatten().fieldErrors to Record<string, string>,
|
|
36
|
+
* merging optional per-field message overrides from the component config.
|
|
37
|
+
*
|
|
38
|
+
* @param fieldErrors Output of zodResult.error.flatten().fieldErrors
|
|
39
|
+
* Shape: { fieldName: string[] }
|
|
40
|
+
* @param errorConfig Optional per-field error config from index.ts
|
|
41
|
+
* Shape: { fieldName: { required?: string, min?: string, ... } }
|
|
42
|
+
*/
|
|
43
|
+
export function mergeZodErrors(fieldErrors, errorConfig = {}) {
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [field, messages] of Object.entries(fieldErrors)) {
|
|
46
|
+
if (!messages || messages.length === 0) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// The first message is the most relevant one
|
|
50
|
+
const defaultMessage = messages[0];
|
|
51
|
+
// Check if there's a config override for this field
|
|
52
|
+
const fieldConfig = errorConfig[field];
|
|
53
|
+
if (!fieldConfig) {
|
|
54
|
+
result[field] = defaultMessage;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Try to map the Zod message to a config key.
|
|
58
|
+
// We check for simple substrings in the Zod message to identify the code.
|
|
59
|
+
const configMessage = resolveConfigMessage(defaultMessage, fieldConfig);
|
|
60
|
+
result[field] = configMessage ?? defaultMessage;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Try to find a matching override in fieldConfig based on the Zod message content.
|
|
66
|
+
* Returns the override string, or null if no match found.
|
|
67
|
+
*/
|
|
68
|
+
function resolveConfigMessage(zodMessage, fieldConfig) {
|
|
69
|
+
if (!fieldConfig || typeof fieldConfig !== 'object') {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// Direct key match: developer can use zod code names directly
|
|
73
|
+
// e.g. errors.too_small, errors.invalid_type
|
|
74
|
+
for (const [zodCode, configKey] of Object.entries(ZOD_CODE_TO_CONFIG_KEY)) {
|
|
75
|
+
if (fieldConfig[zodCode]) {
|
|
76
|
+
// Check if Zod message suggests this error type
|
|
77
|
+
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
78
|
+
return fieldConfig[zodCode];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Also support friendly key names: errors.min, errors.required, etc.
|
|
82
|
+
if (fieldConfig[configKey]) {
|
|
83
|
+
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
84
|
+
return fieldConfig[configKey];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Fallback: if developer set errors.required and Zod says "Required"
|
|
89
|
+
if (fieldConfig.required && /required|undefined|null/i.test(zodMessage)) {
|
|
90
|
+
return fieldConfig.required;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Heuristic: does the Zod default message suggest a certain error code?
|
|
96
|
+
*/
|
|
97
|
+
function messageMatchesCode(message, zodCode) {
|
|
98
|
+
const patterns = {
|
|
99
|
+
too_small: /at least|minimum|must contain at least|min/i,
|
|
100
|
+
too_big: /at most|maximum|must contain at most|max/i,
|
|
101
|
+
invalid_type: /required|expected|received undefined|null/i,
|
|
102
|
+
invalid_enum_value: /invalid enum|expected one of/i,
|
|
103
|
+
invalid_string: /invalid|email|url|uuid|datetime/i,
|
|
104
|
+
};
|
|
105
|
+
return patterns[zodCode]?.test(message) ?? false;
|
|
106
|
+
}
|
|
@@ -31,17 +31,61 @@
|
|
|
31
31
|
* patterns: ['/api/**', '/api/v2/*']
|
|
32
32
|
*/
|
|
33
33
|
export default defineNuxtPlugin(() => {
|
|
34
|
-
//
|
|
35
|
-
|
|
34
|
+
// Define your global callback rules.
|
|
35
|
+
// Each rule can optionally target specific URL patterns and/or HTTP methods.
|
|
36
|
+
// Rules are executed in order; backward-compatible with a single object.
|
|
37
|
+
const globalCallbacks = [
|
|
36
38
|
// ========================================================================
|
|
37
|
-
//
|
|
39
|
+
// RULE 1 — applies to ALL requests (no patterns/methods filter)
|
|
38
40
|
// ========================================================================
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
41
|
+
// {
|
|
42
|
+
// onRequest: (context) => {
|
|
43
|
+
// console.log(`[API] ${context.method} ${context.url}`);
|
|
44
|
+
// },
|
|
45
|
+
// onError: (error) => {
|
|
46
|
+
// // Handle 401 globally — return false to suppress local onError
|
|
47
|
+
// if (error.statusCode === 401) {
|
|
48
|
+
// navigateTo('/login');
|
|
49
|
+
// return false;
|
|
50
|
+
// }
|
|
51
|
+
// },
|
|
52
|
+
// },
|
|
53
|
+
// ========================================================================
|
|
54
|
+
// RULE 2 — only for DELETE / POST / PUT (method targeting)
|
|
55
|
+
// ========================================================================
|
|
56
|
+
// {
|
|
57
|
+
// methods: ['DELETE', 'POST', 'PUT'],
|
|
58
|
+
// onSuccess: (data, context) => {
|
|
59
|
+
// const { $toast } = useNuxtApp();
|
|
60
|
+
// $toast?.success('✅ Operation completed');
|
|
61
|
+
// },
|
|
62
|
+
// onError: (error) => {
|
|
63
|
+
// const { $toast } = useNuxtApp();
|
|
64
|
+
// $toast?.error(`❌ ${error.message || 'An error occurred'}`);
|
|
65
|
+
// },
|
|
66
|
+
// },
|
|
67
|
+
// ========================================================================
|
|
68
|
+
// RULE 3 — only for private API routes (URL pattern targeting)
|
|
69
|
+
// ========================================================================
|
|
70
|
+
// {
|
|
71
|
+
// patterns: ['/api/private/**'],
|
|
72
|
+
// onRequest: () => {
|
|
73
|
+
// const token = useCookie('auth-token').value;
|
|
74
|
+
// if (token) return { headers: { Authorization: `Bearer ${token}` } };
|
|
75
|
+
// },
|
|
76
|
+
// },
|
|
77
|
+
// ========================================================================
|
|
78
|
+
// RULE 4 — DELETE on a specific resource (method + pattern combined)
|
|
79
|
+
// ========================================================================
|
|
80
|
+
// {
|
|
81
|
+
// methods: ['DELETE'],
|
|
82
|
+
// patterns: ['/api/users/**'],
|
|
83
|
+
// onSuccess: () => console.log('User deleted'),
|
|
84
|
+
// onError: (error) => console.error('Delete failed', error),
|
|
85
|
+
// },
|
|
86
|
+
// ---- Legacy single-object format is still supported ----
|
|
87
|
+
// You can also pass a single object instead of an array:
|
|
88
|
+
// getGlobalApiCallbacks: () => ({ onError: (e) => console.error(e) })
|
|
45
89
|
// ========================================================================
|
|
46
90
|
// onRequest: Called before every request
|
|
47
91
|
// ========================================================================
|
|
@@ -177,8 +221,7 @@ export default defineNuxtPlugin(() => {
|
|
|
177
221
|
// // Example 4: Clean up global loading state
|
|
178
222
|
// // const { $loading } = useNuxtApp();
|
|
179
223
|
// // $loading?.hide();
|
|
180
|
-
|
|
181
|
-
};
|
|
224
|
+
];
|
|
182
225
|
return {
|
|
183
226
|
provide: {
|
|
184
227
|
getGlobalApiCallbacks: () => globalCallbacks,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global API Pagination Plugin
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ IMPORTANT: This file is NEVER regenerated - your changes are safe!
|
|
5
|
+
*
|
|
6
|
+
* Configure the global pagination convention for all paginated API requests.
|
|
7
|
+
* Each composable can override this config locally via `paginationConfig` option.
|
|
8
|
+
*
|
|
9
|
+
* ── HOW IT WORKS ──────────────────────────────────────────────────────────────
|
|
10
|
+
*
|
|
11
|
+
* When you call a composable with `paginated: true`, the wrapper will:
|
|
12
|
+
* 1. Inject page/perPage params into the request (query, body, or headers)
|
|
13
|
+
* 2. After the response, read total/totalPages/currentPage/perPage from the
|
|
14
|
+
* response headers or JSON body, according to `metaSource`
|
|
15
|
+
* 3. Expose `pagination`, `goToPage()`, `nextPage()`, `prevPage()`, `setPerPage()`
|
|
16
|
+
*
|
|
17
|
+
* ── USAGE ─────────────────────────────────────────────────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* // In your page/component:
|
|
20
|
+
* const { data, pagination, goToPage, nextPage, prevPage, setPerPage } =
|
|
21
|
+
* useGetPets(params, { paginated: true })
|
|
22
|
+
*
|
|
23
|
+
* // pagination is a computed ref:
|
|
24
|
+
* pagination.value.currentPage // current page
|
|
25
|
+
* pagination.value.totalPages // total pages
|
|
26
|
+
* pagination.value.total // total items
|
|
27
|
+
* pagination.value.perPage // items per page
|
|
28
|
+
* pagination.value.hasNextPage // boolean
|
|
29
|
+
* pagination.value.hasPrevPage // boolean
|
|
30
|
+
*
|
|
31
|
+
* // Navigation helpers — automatically trigger re-fetch:
|
|
32
|
+
* goToPage(3)
|
|
33
|
+
* nextPage()
|
|
34
|
+
* prevPage()
|
|
35
|
+
* setPerPage(50)
|
|
36
|
+
*
|
|
37
|
+
* ── PRIORITY ──────────────────────────────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* Per-call paginationConfig > this plugin's global config > built-in defaults
|
|
40
|
+
*
|
|
41
|
+
* Example of per-call override:
|
|
42
|
+
* useGetPets(params, {
|
|
43
|
+
* paginated: true,
|
|
44
|
+
* paginationConfig: {
|
|
45
|
+
* meta: { metaSource: 'headers', fields: { ... } },
|
|
46
|
+
* request: { sendAs: 'query', params: { page: 'p', perPage: 'size' }, defaults: { page: 1, perPage: 10 } }
|
|
47
|
+
* }
|
|
48
|
+
* })
|
|
49
|
+
*/
|
|
50
|
+
declare const _default: any;
|
|
51
|
+
export default _default;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Global API Pagination Plugin
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ IMPORTANT: This file is NEVER regenerated - your changes are safe!
|
|
6
|
+
*
|
|
7
|
+
* Configure the global pagination convention for all paginated API requests.
|
|
8
|
+
* Each composable can override this config locally via `paginationConfig` option.
|
|
9
|
+
*
|
|
10
|
+
* ── HOW IT WORKS ──────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* When you call a composable with `paginated: true`, the wrapper will:
|
|
13
|
+
* 1. Inject page/perPage params into the request (query, body, or headers)
|
|
14
|
+
* 2. After the response, read total/totalPages/currentPage/perPage from the
|
|
15
|
+
* response headers or JSON body, according to `metaSource`
|
|
16
|
+
* 3. Expose `pagination`, `goToPage()`, `nextPage()`, `prevPage()`, `setPerPage()`
|
|
17
|
+
*
|
|
18
|
+
* ── USAGE ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* // In your page/component:
|
|
21
|
+
* const { data, pagination, goToPage, nextPage, prevPage, setPerPage } =
|
|
22
|
+
* useGetPets(params, { paginated: true })
|
|
23
|
+
*
|
|
24
|
+
* // pagination is a computed ref:
|
|
25
|
+
* pagination.value.currentPage // current page
|
|
26
|
+
* pagination.value.totalPages // total pages
|
|
27
|
+
* pagination.value.total // total items
|
|
28
|
+
* pagination.value.perPage // items per page
|
|
29
|
+
* pagination.value.hasNextPage // boolean
|
|
30
|
+
* pagination.value.hasPrevPage // boolean
|
|
31
|
+
*
|
|
32
|
+
* // Navigation helpers — automatically trigger re-fetch:
|
|
33
|
+
* goToPage(3)
|
|
34
|
+
* nextPage()
|
|
35
|
+
* prevPage()
|
|
36
|
+
* setPerPage(50)
|
|
37
|
+
*
|
|
38
|
+
* ── PRIORITY ──────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* Per-call paginationConfig > this plugin's global config > built-in defaults
|
|
41
|
+
*
|
|
42
|
+
* Example of per-call override:
|
|
43
|
+
* useGetPets(params, {
|
|
44
|
+
* paginated: true,
|
|
45
|
+
* paginationConfig: {
|
|
46
|
+
* meta: { metaSource: 'headers', fields: { ... } },
|
|
47
|
+
* request: { sendAs: 'query', params: { page: 'p', perPage: 'size' }, defaults: { page: 1, perPage: 10 } }
|
|
48
|
+
* }
|
|
49
|
+
* })
|
|
50
|
+
*/
|
|
51
|
+
export default defineNuxtPlugin(() => {
|
|
52
|
+
// Uncomment and configure ONE of the examples below that matches your backend.
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// EXAMPLE 1 — Query params + response body (most common REST pattern)
|
|
55
|
+
//
|
|
56
|
+
// Request: GET /pets?page=1&limit=20
|
|
57
|
+
// Response: { data: [...], total: 100, totalPages: 5, currentPage: 1, perPage: 20 }
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// const paginationConfig = {
|
|
60
|
+
// meta: {
|
|
61
|
+
// metaSource: 'body',
|
|
62
|
+
// fields: {
|
|
63
|
+
// total: 'total',
|
|
64
|
+
// totalPages: 'totalPages',
|
|
65
|
+
// currentPage: 'currentPage',
|
|
66
|
+
// perPage: 'perPage',
|
|
67
|
+
// dataKey: 'data', // response.data contains the actual array
|
|
68
|
+
// },
|
|
69
|
+
// },
|
|
70
|
+
// request: {
|
|
71
|
+
// sendAs: 'query',
|
|
72
|
+
// params: { page: 'page', perPage: 'limit' },
|
|
73
|
+
// defaults: { page: 1, perPage: 20 },
|
|
74
|
+
// },
|
|
75
|
+
// };
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// EXAMPLE 2 — Laravel paginate() convention
|
|
78
|
+
//
|
|
79
|
+
// Request: GET /pets?page=1&per_page=15
|
|
80
|
+
// Response: { data: [...], meta: { total: 100, last_page: 7, current_page: 1, per_page: 15 } }
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// const paginationConfig = {
|
|
83
|
+
// meta: {
|
|
84
|
+
// metaSource: 'body',
|
|
85
|
+
// fields: {
|
|
86
|
+
// total: 'meta.total',
|
|
87
|
+
// totalPages: 'meta.last_page',
|
|
88
|
+
// currentPage: 'meta.current_page',
|
|
89
|
+
// perPage: 'meta.per_page',
|
|
90
|
+
// dataKey: 'data',
|
|
91
|
+
// },
|
|
92
|
+
// },
|
|
93
|
+
// request: {
|
|
94
|
+
// sendAs: 'query',
|
|
95
|
+
// params: { page: 'page', perPage: 'per_page' },
|
|
96
|
+
// defaults: { page: 1, perPage: 15 },
|
|
97
|
+
// },
|
|
98
|
+
// };
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// EXAMPLE 3 — HTTP response headers convention
|
|
101
|
+
//
|
|
102
|
+
// Request: GET /pets?page=1&limit=20
|
|
103
|
+
// Response headers: X-Total-Count: 100, X-Total-Pages: 5, X-Page: 1, X-Per-Page: 20
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// const paginationConfig = {
|
|
106
|
+
// meta: {
|
|
107
|
+
// metaSource: 'headers',
|
|
108
|
+
// fields: {
|
|
109
|
+
// total: 'X-Total-Count',
|
|
110
|
+
// totalPages: 'X-Total-Pages',
|
|
111
|
+
// currentPage: 'X-Page',
|
|
112
|
+
// perPage: 'X-Per-Page',
|
|
113
|
+
// // No dataKey needed — response body is the array directly
|
|
114
|
+
// },
|
|
115
|
+
// },
|
|
116
|
+
// request: {
|
|
117
|
+
// sendAs: 'query',
|
|
118
|
+
// params: { page: 'page', perPage: 'limit' },
|
|
119
|
+
// defaults: { page: 1, perPage: 20 },
|
|
120
|
+
// },
|
|
121
|
+
// };
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// EXAMPLE 4 — POST-as-search (body pagination)
|
|
124
|
+
//
|
|
125
|
+
// Request: POST /pets/search body: { filters: {...}, page: 1, pageSize: 20 }
|
|
126
|
+
// Response: { items: [...], total: 100, pages: 5 }
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// const paginationConfig = {
|
|
129
|
+
// meta: {
|
|
130
|
+
// metaSource: 'body',
|
|
131
|
+
// fields: {
|
|
132
|
+
// total: 'total',
|
|
133
|
+
// totalPages: 'pages',
|
|
134
|
+
// currentPage: 'page',
|
|
135
|
+
// perPage: 'pageSize',
|
|
136
|
+
// dataKey: 'items',
|
|
137
|
+
// },
|
|
138
|
+
// },
|
|
139
|
+
// request: {
|
|
140
|
+
// sendAs: 'body',
|
|
141
|
+
// params: { page: 'page', perPage: 'pageSize' },
|
|
142
|
+
// defaults: { page: 1, perPage: 20 },
|
|
143
|
+
// },
|
|
144
|
+
// };
|
|
145
|
+
return {
|
|
146
|
+
provide: {
|
|
147
|
+
// Expose the config so the runtime wrappers can read it.
|
|
148
|
+
// Uncomment this line once you've configured paginationConfig above:
|
|
149
|
+
// getGlobalApiPagination: () => paginationConfig,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type GenerateOptions } from './templates.js';
|
|
2
|
+
import { type Logger } from '../../cli/logger.js';
|
|
2
3
|
/**
|
|
3
4
|
* Main function to generate useAsyncData composables
|
|
4
5
|
*/
|
|
5
|
-
export declare function generateUseAsyncDataComposables(inputDir: string, outputDir: string, options?: GenerateOptions): Promise<void>;
|
|
6
|
+
export declare function generateUseAsyncDataComposables(inputDir: string, outputDir: string, options?: GenerateOptions, logger?: Logger): Promise<void>;
|
|
@@ -5,12 +5,12 @@ import { format } from 'prettier';
|
|
|
5
5
|
import { getApiFiles as getApiFilesOfficial, parseApiFile as parseApiFileOfficial, } from './parser.js';
|
|
6
6
|
import { getApiFiles as getApiFilesHeyApi, parseApiFile as parseApiFileHeyApi, } from '../shared/parsers/heyapi-parser.js';
|
|
7
7
|
import { generateComposableFile, generateRawComposableFile, generateIndexFile, } from './templates.js';
|
|
8
|
-
import {
|
|
8
|
+
import { createClackLogger } from '../../cli/logger.js';
|
|
9
9
|
/**
|
|
10
10
|
* Main function to generate useAsyncData composables
|
|
11
11
|
*/
|
|
12
|
-
export async function generateUseAsyncDataComposables(inputDir, outputDir, options) {
|
|
13
|
-
const mainSpinner =
|
|
12
|
+
export async function generateUseAsyncDataComposables(inputDir, outputDir, options, logger = createClackLogger()) {
|
|
13
|
+
const mainSpinner = logger.spinner();
|
|
14
14
|
// Select parser based on chosen backend
|
|
15
15
|
const getApiFiles = options?.backend === 'heyapi' ? getApiFilesHeyApi : getApiFilesOfficial;
|
|
16
16
|
const parseApiFile = options?.backend === 'heyapi' ? parseApiFileHeyApi : parseApiFileOfficial;
|
|
@@ -31,12 +31,12 @@ export async function generateUseAsyncDataComposables(inputDir, outputDir, optio
|
|
|
31
31
|
allMethods.push(...apiInfo.methods);
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
|
-
|
|
34
|
+
logger.log.error(`Error parsing ${fileName}: ${String(error)}`);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
mainSpinner.stop(`Found ${allMethods.length} methods to generate`);
|
|
38
38
|
if (allMethods.length === 0) {
|
|
39
|
-
|
|
39
|
+
logger.log.warn('No methods found to generate');
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
// 3. Clean and create output directories
|
|
@@ -78,7 +78,7 @@ export async function generateUseAsyncDataComposables(inputDir, outputDir, optio
|
|
|
78
78
|
// Generate normal version
|
|
79
79
|
try {
|
|
80
80
|
const code = generateComposableFile(method, relativePath, options);
|
|
81
|
-
const formattedCode = await formatCode(code);
|
|
81
|
+
const formattedCode = await formatCode(code, logger);
|
|
82
82
|
const composableName = method.composableName.replace(/^useFetch/, 'useAsyncData');
|
|
83
83
|
const fileName = `${composableName}.ts`;
|
|
84
84
|
const filePath = path.join(composablesDir, fileName);
|
|
@@ -87,14 +87,14 @@ export async function generateUseAsyncDataComposables(inputDir, outputDir, optio
|
|
|
87
87
|
successCount++;
|
|
88
88
|
}
|
|
89
89
|
catch (error) {
|
|
90
|
-
|
|
90
|
+
logger.log.error(`Error generating ${method.composableName}: ${String(error)}`);
|
|
91
91
|
errorCount++;
|
|
92
92
|
}
|
|
93
93
|
// Generate Raw version if available
|
|
94
94
|
if (method.hasRawMethod && method.rawMethodName) {
|
|
95
95
|
try {
|
|
96
96
|
const code = generateRawComposableFile(method, relativePath, options);
|
|
97
|
-
const formattedCode = await formatCode(code);
|
|
97
|
+
const formattedCode = await formatCode(code, logger);
|
|
98
98
|
const composableName = `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`;
|
|
99
99
|
const fileName = `${composableName}.ts`;
|
|
100
100
|
const filePath = path.join(composablesDir, fileName);
|
|
@@ -103,21 +103,21 @@ export async function generateUseAsyncDataComposables(inputDir, outputDir, optio
|
|
|
103
103
|
successCount++;
|
|
104
104
|
}
|
|
105
105
|
catch (error) {
|
|
106
|
-
|
|
106
|
+
logger.log.error(`Error generating ${method.composableName} (Raw): ${String(error)}`);
|
|
107
107
|
errorCount++;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
// 7. Generate index.ts
|
|
112
112
|
const indexCode = generateIndexFile(generatedComposableNames);
|
|
113
|
-
const formattedIndex = await formatCode(indexCode);
|
|
113
|
+
const formattedIndex = await formatCode(indexCode, logger);
|
|
114
114
|
await fs.writeFile(path.join(outputDir, 'index.ts'), formattedIndex, 'utf-8');
|
|
115
115
|
mainSpinner.stop(`Generated ${successCount} composables`);
|
|
116
116
|
// 8. Summary
|
|
117
117
|
if (errorCount > 0) {
|
|
118
|
-
|
|
118
|
+
logger.log.warn(`Completed with ${errorCount} error(s)`);
|
|
119
119
|
}
|
|
120
|
-
|
|
120
|
+
logger.log.success(`Generated ${successCount} useAsyncData composable(s) in ${outputDir}`);
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
123
|
* Calculate relative import path from composables to APIs
|
|
@@ -138,7 +138,7 @@ function calculateRelativeImportPath(composablesDir, inputDir) {
|
|
|
138
138
|
/**
|
|
139
139
|
* Format code with Prettier
|
|
140
140
|
*/
|
|
141
|
-
async function formatCode(code) {
|
|
141
|
+
async function formatCode(code, logger) {
|
|
142
142
|
try {
|
|
143
143
|
return await format(code, {
|
|
144
144
|
parser: 'typescript',
|
|
@@ -150,7 +150,7 @@ async function formatCode(code) {
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
catch {
|
|
153
|
-
|
|
153
|
+
logger.log.warn('Could not format code with Prettier');
|
|
154
154
|
return code;
|
|
155
155
|
}
|
|
156
156
|
}
|