nuxt-openapi-hyperfetch 0.1.7-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.
Files changed (97) hide show
  1. package/CONTRIBUTING.md +291 -292
  2. package/INSTRUCTIONS.md +327 -327
  3. package/LICENSE +202 -202
  4. package/README.md +231 -227
  5. package/dist/cli/logger.d.ts +26 -0
  6. package/dist/cli/logger.js +36 -0
  7. package/dist/cli/logo.js +5 -5
  8. package/dist/generators/components/connector-generator/generator.d.ts +12 -0
  9. package/dist/generators/components/connector-generator/generator.js +116 -0
  10. package/dist/generators/components/connector-generator/templates.d.ts +18 -0
  11. package/dist/generators/components/connector-generator/templates.js +222 -0
  12. package/dist/generators/components/connector-generator/types.d.ts +32 -0
  13. package/dist/generators/components/connector-generator/types.js +7 -0
  14. package/dist/generators/components/schema-analyzer/index.d.ts +17 -0
  15. package/dist/generators/components/schema-analyzer/index.js +20 -0
  16. package/dist/generators/components/schema-analyzer/intent-detector.d.ts +17 -0
  17. package/dist/generators/components/schema-analyzer/intent-detector.js +143 -0
  18. package/dist/generators/components/schema-analyzer/openapi-reader.d.ts +11 -0
  19. package/dist/generators/components/schema-analyzer/openapi-reader.js +76 -0
  20. package/dist/generators/components/schema-analyzer/resource-grouper.d.ts +6 -0
  21. package/dist/generators/components/schema-analyzer/resource-grouper.js +132 -0
  22. package/dist/generators/components/schema-analyzer/schema-field-mapper.d.ts +35 -0
  23. package/dist/generators/components/schema-analyzer/schema-field-mapper.js +220 -0
  24. package/dist/generators/components/schema-analyzer/types.d.ts +156 -0
  25. package/dist/generators/components/schema-analyzer/types.js +7 -0
  26. package/dist/generators/nuxt-server/generator.d.ts +2 -1
  27. package/dist/generators/nuxt-server/generator.js +21 -21
  28. package/dist/generators/shared/runtime/apiHelpers.d.ts +81 -41
  29. package/dist/generators/shared/runtime/apiHelpers.js +97 -104
  30. package/dist/generators/shared/runtime/pagination.d.ts +168 -0
  31. package/dist/generators/shared/runtime/pagination.js +179 -0
  32. package/dist/generators/shared/runtime/useDeleteConnector.d.ts +16 -0
  33. package/dist/generators/shared/runtime/useDeleteConnector.js +93 -0
  34. package/dist/generators/shared/runtime/useDetailConnector.d.ts +14 -0
  35. package/dist/generators/shared/runtime/useDetailConnector.js +50 -0
  36. package/dist/generators/shared/runtime/useFormConnector.d.ts +19 -0
  37. package/dist/generators/shared/runtime/useFormConnector.js +113 -0
  38. package/dist/generators/shared/runtime/useListConnector.d.ts +25 -0
  39. package/dist/generators/shared/runtime/useListConnector.js +125 -0
  40. package/dist/generators/shared/runtime/zod-error-merger.d.ts +23 -0
  41. package/dist/generators/shared/runtime/zod-error-merger.js +106 -0
  42. package/dist/generators/shared/templates/api-callbacks-plugin.js +54 -11
  43. package/dist/generators/shared/templates/api-pagination-plugin.d.ts +51 -0
  44. package/dist/generators/shared/templates/api-pagination-plugin.js +152 -0
  45. package/dist/generators/use-async-data/generator.d.ts +2 -1
  46. package/dist/generators/use-async-data/generator.js +14 -14
  47. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +114 -13
  48. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +88 -10
  49. package/dist/generators/use-async-data/templates.js +17 -17
  50. package/dist/generators/use-fetch/generator.d.ts +2 -1
  51. package/dist/generators/use-fetch/generator.js +12 -12
  52. package/dist/generators/use-fetch/runtime/useApiRequest.js +149 -40
  53. package/dist/generators/use-fetch/templates.js +14 -14
  54. package/dist/index.js +25 -0
  55. package/dist/module/index.d.ts +4 -0
  56. package/dist/module/index.js +93 -0
  57. package/dist/module/types.d.ts +27 -0
  58. package/dist/module/types.js +1 -0
  59. package/docs/API-REFERENCE.md +886 -887
  60. package/docs/generated-components.md +615 -0
  61. package/docs/headless-composables-ui.md +569 -0
  62. package/eslint.config.js +13 -0
  63. package/package.json +29 -2
  64. package/src/cli/config.ts +140 -140
  65. package/src/cli/logger.ts +124 -66
  66. package/src/cli/logo.ts +25 -25
  67. package/src/cli/types.ts +50 -50
  68. package/src/generators/components/connector-generator/generator.ts +138 -0
  69. package/src/generators/components/connector-generator/templates.ts +254 -0
  70. package/src/generators/components/connector-generator/types.ts +34 -0
  71. package/src/generators/components/schema-analyzer/index.ts +44 -0
  72. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -0
  73. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -0
  74. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -0
  75. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -0
  76. package/src/generators/components/schema-analyzer/types.ts +177 -0
  77. package/src/generators/nuxt-server/generator.ts +272 -270
  78. package/src/generators/shared/runtime/apiHelpers.ts +535 -507
  79. package/src/generators/shared/runtime/pagination.ts +323 -0
  80. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -0
  81. package/src/generators/shared/runtime/useDetailConnector.ts +64 -0
  82. package/src/generators/shared/runtime/useFormConnector.ts +139 -0
  83. package/src/generators/shared/runtime/useListConnector.ts +148 -0
  84. package/src/generators/shared/runtime/zod-error-merger.ts +119 -0
  85. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -352
  86. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -0
  87. package/src/generators/use-async-data/generator.ts +205 -204
  88. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -229
  89. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -245
  90. package/src/generators/use-async-data/templates.ts +257 -257
  91. package/src/generators/use-fetch/generator.ts +170 -169
  92. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -234
  93. package/src/generators/use-fetch/templates.ts +214 -214
  94. package/src/index.ts +303 -265
  95. package/src/module/index.ts +133 -0
  96. package/src/module/types.ts +31 -0
  97. 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
- // Uncomment and customize the callbacks you need
35
- const globalCallbacks = {
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
- // OPTION 3: URL Pattern Matching (OPTIONAL)
39
+ // RULE 1 applies to ALL requests (no patterns/methods filter)
38
40
  // ========================================================================
39
- // Only apply global callbacks to URLs matching these patterns
40
- // Use ** to match any path (including nested), * to match single segment
41
- // If omitted or empty, callbacks apply to ALL requests
42
- // patterns: ['/api/**'], // Only internal APIs
43
- // patterns: ['/api/v1/**', '/api/v2/**'], // Multiple API versions
44
- // patterns: ['**/public/**'], // All public endpoints
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 { p, logSuccess, logError } from '../../cli/logger.js';
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 = p.spinner();
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
- logError(`Error parsing ${fileName}: ${error}`);
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
- p.log.warn('No methods found to generate');
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
- logError(`Error generating ${method.composableName}: ${error}`);
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
- logError(`Error generating ${method.composableName} (Raw): ${error}`);
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
- p.log.warn(`Completed with ${errorCount} error(s)`);
118
+ logger.log.warn(`Completed with ${errorCount} error(s)`);
119
119
  }
120
- logSuccess(`Generated ${successCount} useAsyncData composable(s) in ${outputDir}`);
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
- p.log.warn('Could not format code with Prettier');
153
+ logger.log.warn('Could not format code with Prettier');
154
154
  return code;
155
155
  }
156
156
  }