nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.3.0-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/.editorconfig +26 -26
- package/.prettierignore +17 -17
- package/CONTRIBUTING.md +291 -291
- package/INSTRUCTIONS.md +327 -327
- package/LICENSE +202 -202
- package/README.md +309 -231
- package/dist/cli/config.d.ts +9 -2
- package/dist/cli/config.js +1 -1
- package/dist/cli/logo.js +5 -5
- package/dist/cli/messages.d.ts +1 -0
- package/dist/cli/messages.js +2 -0
- package/dist/cli/prompts.d.ts +5 -0
- package/dist/cli/prompts.js +12 -0
- package/dist/cli/types.d.ts +1 -1
- package/dist/generators/components/connector-generator/templates.js +68 -19
- package/dist/generators/shared/runtime/useFormConnector.js +8 -1
- package/dist/generators/shared/runtime/useListConnector.js +13 -6
- package/dist/generators/use-async-data/generator.js +4 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
- package/dist/generators/use-async-data/templates.js +17 -17
- package/dist/generators/use-fetch/generator.js +4 -0
- package/dist/generators/use-fetch/templates.js +14 -14
- package/dist/index.js +40 -27
- package/dist/module/index.js +19 -0
- package/dist/module/types.d.ts +7 -0
- package/docs/API-REFERENCE.md +886 -886
- package/docs/generated-components.md +615 -615
- package/docs/headless-composables-ui.md +569 -569
- package/eslint.config.js +85 -85
- package/package.json +1 -1
- package/src/cli/config.ts +147 -140
- package/src/cli/logger.ts +124 -124
- package/src/cli/logo.ts +25 -25
- package/src/cli/messages.ts +4 -0
- package/src/cli/prompts.ts +14 -1
- package/src/cli/types.ts +50 -50
- package/src/generators/components/connector-generator/generator.ts +138 -138
- package/src/generators/components/connector-generator/templates.ts +307 -254
- package/src/generators/components/connector-generator/types.ts +34 -34
- package/src/generators/components/schema-analyzer/index.ts +44 -44
- package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
- package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
- package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
- package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
- package/src/generators/components/schema-analyzer/types.ts +177 -177
- package/src/generators/nuxt-server/generator.ts +272 -272
- package/src/generators/shared/runtime/apiHelpers.ts +535 -535
- package/src/generators/shared/runtime/pagination.ts +323 -323
- package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
- package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
- package/src/generators/shared/runtime/useFormConnector.ts +147 -139
- package/src/generators/shared/runtime/useListConnector.ts +158 -148
- package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
- package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
- package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
- package/src/generators/use-async-data/generator.ts +213 -205
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
- package/src/generators/use-async-data/templates.ts +257 -257
- package/src/generators/use-fetch/generator.ts +178 -170
- package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
- package/src/generators/use-fetch/templates.ts +214 -214
- package/src/index.ts +306 -303
- package/src/module/index.ts +158 -133
- package/src/module/types.ts +39 -31
- package/dist/generators/tanstack-query/generator.d.ts +0 -5
- package/dist/generators/tanstack-query/generator.js +0 -11
|
@@ -1,148 +1,158 @@
|
|
|
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
|
-
/**
|
|
16
|
-
* @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
|
|
17
|
-
* @param options Configuration for the list connector
|
|
18
|
-
*/
|
|
19
|
-
export function useListConnector(composableFn, options = {}) {
|
|
20
|
-
const { paginated = false, columns = [] } = options;
|
|
21
|
-
|
|
22
|
-
// ── Execute the underlying composable ──────────────────────────────────────
|
|
23
|
-
const composable = composableFn({ paginated });
|
|
24
|
-
|
|
25
|
-
// ── Derived state ──────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
const rows = computed(() => {
|
|
28
|
-
const data = composable.data?.value;
|
|
29
|
-
if (!data) return [];
|
|
30
|
-
// Support both direct arrays and { data: [...] } shapes
|
|
31
|
-
if (Array.isArray(data)) return data;
|
|
32
|
-
if (Array.isArray(data.data)) return data.data;
|
|
33
|
-
return [];
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const loading = computed(() => composable.pending?.value ?? false);
|
|
37
|
-
const error = computed(() => composable.error?.value ?? null);
|
|
38
|
-
|
|
39
|
-
// Pagination — passthrough from the underlying composable when paginated: true
|
|
40
|
-
const pagination = computed(() => composable.pagination?.value ?? null);
|
|
41
|
-
|
|
42
|
-
function goToPage(page) {
|
|
43
|
-
composable.goToPage?.(page);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function nextPage() {
|
|
47
|
-
composable.nextPage?.();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function prevPage() {
|
|
51
|
-
composable.prevPage?.();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function setPerPage(n) {
|
|
55
|
-
composable.setPerPage?.(n);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ── Row selection ──────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
const selected = ref([]);
|
|
61
|
-
|
|
62
|
-
function onRowSelect(row) {
|
|
63
|
-
const idx = selected.value.indexOf(row);
|
|
64
|
-
if (idx === -1) {
|
|
65
|
-
selected.value = [...selected.value, row];
|
|
66
|
-
} else {
|
|
67
|
-
selected.value = selected.value.filter((r) => r !== row);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function clearSelection() {
|
|
72
|
-
selected.value = [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── Refresh ────────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
function refresh() {
|
|
78
|
-
void composable.refresh?.();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ── CRUD coordination (triggers watched by the parent component) ──────────
|
|
82
|
-
// The parent watches these refs to decide when to open a modal/panel/route.
|
|
83
|
-
// Naming convention:
|
|
84
|
-
// _createTrigger — Ref<number> counter; no data needed (form starts empty)
|
|
85
|
-
// _updateTarget — ShallowRef<Row|null>; the row to pre-fill the edit form
|
|
86
|
-
// _deleteTarget — ShallowRef<Row|null>; the row to confirm deletion for
|
|
87
|
-
|
|
88
|
-
const _createTrigger = ref(0);
|
|
89
|
-
const _updateTarget = shallowRef(null);
|
|
90
|
-
const _deleteTarget = shallowRef(null);
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Signal the parent that the user wants to create a new item.
|
|
94
|
-
* The parent should watch `_createTrigger` and open a create form/modal.
|
|
95
|
-
*/
|
|
96
|
-
function create() {
|
|
97
|
-
_createTrigger.value++;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Signal the parent that the user wants to edit `row`.
|
|
102
|
-
* The parent should watch `_updateTarget` and open an edit form/modal.
|
|
103
|
-
*/
|
|
104
|
-
function update(row) {
|
|
105
|
-
_updateTarget.value = row;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Signal the parent that the user wants to delete `row`.
|
|
110
|
-
* The parent should watch `_deleteTarget` and open a confirmation modal.
|
|
111
|
-
*/
|
|
112
|
-
function remove(row) {
|
|
113
|
-
_deleteTarget.value = row;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
/**
|
|
16
|
+
* @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPets
|
|
17
|
+
* @param options Configuration for the list connector
|
|
18
|
+
*/
|
|
19
|
+
export function useListConnector(composableFn, options = {}) {
|
|
20
|
+
const { paginated = false, columns = [], columnLabels = {}, columnLabel = null } = options;
|
|
21
|
+
|
|
22
|
+
// ── Execute the underlying composable ──────────────────────────────────────
|
|
23
|
+
const composable = composableFn({ paginated });
|
|
24
|
+
|
|
25
|
+
// ── Derived state ──────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const rows = computed(() => {
|
|
28
|
+
const data = composable.data?.value;
|
|
29
|
+
if (!data) return [];
|
|
30
|
+
// Support both direct arrays and { data: [...] } shapes
|
|
31
|
+
if (Array.isArray(data)) return data;
|
|
32
|
+
if (Array.isArray(data.data)) return data.data;
|
|
33
|
+
return [];
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const loading = computed(() => composable.pending?.value ?? false);
|
|
37
|
+
const error = computed(() => composable.error?.value ?? null);
|
|
38
|
+
|
|
39
|
+
// Pagination — passthrough from the underlying composable when paginated: true
|
|
40
|
+
const pagination = computed(() => composable.pagination?.value ?? null);
|
|
41
|
+
|
|
42
|
+
function goToPage(page) {
|
|
43
|
+
composable.pagination?.value?.goToPage?.(page);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nextPage() {
|
|
47
|
+
composable.pagination?.value?.nextPage?.();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function prevPage() {
|
|
51
|
+
composable.pagination?.value?.prevPage?.();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setPerPage(n) {
|
|
55
|
+
composable.pagination?.value?.setPerPage?.(n);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Row selection ──────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const selected = ref([]);
|
|
61
|
+
|
|
62
|
+
function onRowSelect(row) {
|
|
63
|
+
const idx = selected.value.indexOf(row);
|
|
64
|
+
if (idx === -1) {
|
|
65
|
+
selected.value = [...selected.value, row];
|
|
66
|
+
} else {
|
|
67
|
+
selected.value = selected.value.filter((r) => r !== row);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function clearSelection() {
|
|
72
|
+
selected.value = [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Refresh ────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function refresh() {
|
|
78
|
+
void composable.refresh?.();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── CRUD coordination (triggers watched by the parent component) ──────────
|
|
82
|
+
// The parent watches these refs to decide when to open a modal/panel/route.
|
|
83
|
+
// Naming convention:
|
|
84
|
+
// _createTrigger — Ref<number> counter; no data needed (form starts empty)
|
|
85
|
+
// _updateTarget — ShallowRef<Row|null>; the row to pre-fill the edit form
|
|
86
|
+
// _deleteTarget — ShallowRef<Row|null>; the row to confirm deletion for
|
|
87
|
+
|
|
88
|
+
const _createTrigger = ref(0);
|
|
89
|
+
const _updateTarget = shallowRef(null);
|
|
90
|
+
const _deleteTarget = shallowRef(null);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Signal the parent that the user wants to create a new item.
|
|
94
|
+
* The parent should watch `_createTrigger` and open a create form/modal.
|
|
95
|
+
*/
|
|
96
|
+
function create() {
|
|
97
|
+
_createTrigger.value++;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Signal the parent that the user wants to edit `row`.
|
|
102
|
+
* The parent should watch `_updateTarget` and open an edit form/modal.
|
|
103
|
+
*/
|
|
104
|
+
function update(row) {
|
|
105
|
+
_updateTarget.value = row;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Signal the parent that the user wants to delete `row`.
|
|
110
|
+
* The parent should watch `_deleteTarget` and open a confirmation modal.
|
|
111
|
+
*/
|
|
112
|
+
function remove(row) {
|
|
113
|
+
_deleteTarget.value = row;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Apply label overrides: columnLabel function takes priority over columnLabels map
|
|
117
|
+
const resolvedColumns = computed(() =>
|
|
118
|
+
columns.map((col) => ({
|
|
119
|
+
...col,
|
|
120
|
+
label: columnLabel
|
|
121
|
+
? columnLabel(col.key)
|
|
122
|
+
: (columnLabels[col.key] ?? col.label),
|
|
123
|
+
}))
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
// State
|
|
128
|
+
rows,
|
|
129
|
+
columns: resolvedColumns,
|
|
130
|
+
loading,
|
|
131
|
+
error,
|
|
132
|
+
|
|
133
|
+
// Pagination
|
|
134
|
+
pagination,
|
|
135
|
+
goToPage,
|
|
136
|
+
nextPage,
|
|
137
|
+
prevPage,
|
|
138
|
+
setPerPage,
|
|
139
|
+
|
|
140
|
+
// Selection
|
|
141
|
+
selected,
|
|
142
|
+
onRowSelect,
|
|
143
|
+
clearSelection,
|
|
144
|
+
|
|
145
|
+
// Actions
|
|
146
|
+
refresh,
|
|
147
|
+
|
|
148
|
+
// CRUD coordination — public methods
|
|
149
|
+
create,
|
|
150
|
+
update,
|
|
151
|
+
remove,
|
|
152
|
+
|
|
153
|
+
// CRUD coordination — internal triggers (watch these in the parent)
|
|
154
|
+
_createTrigger,
|
|
155
|
+
_updateTarget,
|
|
156
|
+
_deleteTarget,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -1,119 +1,119 @@
|
|
|
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
|
-
/**
|
|
17
|
-
* Map Zod issue codes to friendlier config key names.
|
|
18
|
-
* The developer uses these keys in config.fields[name].errors:
|
|
19
|
-
*
|
|
20
|
-
* errors: {
|
|
21
|
-
* required: 'Name is required',
|
|
22
|
-
* min: 'At least 1 character',
|
|
23
|
-
* max: 'Max 100 characters',
|
|
24
|
-
* email: 'Invalid email',
|
|
25
|
-
* enum: 'Select a valid option',
|
|
26
|
-
* }
|
|
27
|
-
*/
|
|
28
|
-
const ZOD_CODE_TO_CONFIG_KEY = {
|
|
29
|
-
too_small: 'min',
|
|
30
|
-
too_big: 'max',
|
|
31
|
-
invalid_type: 'required', // most common: field is undefined/null
|
|
32
|
-
invalid_enum_value: 'enum',
|
|
33
|
-
invalid_string: 'format',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Convert Zod's flatten().fieldErrors to Record<string, string>,
|
|
38
|
-
* merging optional per-field message overrides from the component config.
|
|
39
|
-
*
|
|
40
|
-
* @param fieldErrors Output of zodResult.error.flatten().fieldErrors
|
|
41
|
-
* Shape: { fieldName: string[] }
|
|
42
|
-
* @param errorConfig Optional per-field error config from index.ts
|
|
43
|
-
* Shape: { fieldName: { required?: string, min?: string, ... } }
|
|
44
|
-
*/
|
|
45
|
-
export function mergeZodErrors(fieldErrors, errorConfig = {}) {
|
|
46
|
-
const result = {};
|
|
47
|
-
|
|
48
|
-
for (const [field, messages] of Object.entries(fieldErrors)) {
|
|
49
|
-
if (!messages || messages.length === 0) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// The first message is the most relevant one
|
|
54
|
-
const defaultMessage = messages[0];
|
|
55
|
-
|
|
56
|
-
// Check if there's a config override for this field
|
|
57
|
-
const fieldConfig = errorConfig[field];
|
|
58
|
-
if (!fieldConfig) {
|
|
59
|
-
result[field] = defaultMessage;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Try to map the Zod message to a config key.
|
|
64
|
-
// We check for simple substrings in the Zod message to identify the code.
|
|
65
|
-
const configMessage = resolveConfigMessage(defaultMessage, fieldConfig);
|
|
66
|
-
result[field] = configMessage ?? defaultMessage;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Try to find a matching override in fieldConfig based on the Zod message content.
|
|
74
|
-
* Returns the override string, or null if no match found.
|
|
75
|
-
*/
|
|
76
|
-
function resolveConfigMessage(zodMessage, fieldConfig) {
|
|
77
|
-
if (!fieldConfig || typeof fieldConfig !== 'object') {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Direct key match: developer can use zod code names directly
|
|
82
|
-
// e.g. errors.too_small, errors.invalid_type
|
|
83
|
-
for (const [zodCode, configKey] of Object.entries(ZOD_CODE_TO_CONFIG_KEY)) {
|
|
84
|
-
if (fieldConfig[zodCode]) {
|
|
85
|
-
// Check if Zod message suggests this error type
|
|
86
|
-
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
87
|
-
return fieldConfig[zodCode];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// Also support friendly key names: errors.min, errors.required, etc.
|
|
91
|
-
if (fieldConfig[configKey]) {
|
|
92
|
-
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
93
|
-
return fieldConfig[configKey];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Fallback: if developer set errors.required and Zod says "Required"
|
|
99
|
-
if (fieldConfig.required && /required|undefined|null/i.test(zodMessage)) {
|
|
100
|
-
return fieldConfig.required;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Heuristic: does the Zod default message suggest a certain error code?
|
|
108
|
-
*/
|
|
109
|
-
function messageMatchesCode(message, zodCode) {
|
|
110
|
-
const patterns = {
|
|
111
|
-
too_small: /at least|minimum|must contain at least|min/i,
|
|
112
|
-
too_big: /at most|maximum|must contain at most|max/i,
|
|
113
|
-
invalid_type: /required|expected|received undefined|null/i,
|
|
114
|
-
invalid_enum_value: /invalid enum|expected one of/i,
|
|
115
|
-
invalid_string: /invalid|email|url|uuid|datetime/i,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return patterns[zodCode]?.test(message) ?? false;
|
|
119
|
-
}
|
|
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
|
+
/**
|
|
17
|
+
* Map Zod issue codes to friendlier config key names.
|
|
18
|
+
* The developer uses these keys in config.fields[name].errors:
|
|
19
|
+
*
|
|
20
|
+
* errors: {
|
|
21
|
+
* required: 'Name is required',
|
|
22
|
+
* min: 'At least 1 character',
|
|
23
|
+
* max: 'Max 100 characters',
|
|
24
|
+
* email: 'Invalid email',
|
|
25
|
+
* enum: 'Select a valid option',
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
const ZOD_CODE_TO_CONFIG_KEY = {
|
|
29
|
+
too_small: 'min',
|
|
30
|
+
too_big: 'max',
|
|
31
|
+
invalid_type: 'required', // most common: field is undefined/null
|
|
32
|
+
invalid_enum_value: 'enum',
|
|
33
|
+
invalid_string: 'format',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert Zod's flatten().fieldErrors to Record<string, string>,
|
|
38
|
+
* merging optional per-field message overrides from the component config.
|
|
39
|
+
*
|
|
40
|
+
* @param fieldErrors Output of zodResult.error.flatten().fieldErrors
|
|
41
|
+
* Shape: { fieldName: string[] }
|
|
42
|
+
* @param errorConfig Optional per-field error config from index.ts
|
|
43
|
+
* Shape: { fieldName: { required?: string, min?: string, ... } }
|
|
44
|
+
*/
|
|
45
|
+
export function mergeZodErrors(fieldErrors, errorConfig = {}) {
|
|
46
|
+
const result = {};
|
|
47
|
+
|
|
48
|
+
for (const [field, messages] of Object.entries(fieldErrors)) {
|
|
49
|
+
if (!messages || messages.length === 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The first message is the most relevant one
|
|
54
|
+
const defaultMessage = messages[0];
|
|
55
|
+
|
|
56
|
+
// Check if there's a config override for this field
|
|
57
|
+
const fieldConfig = errorConfig[field];
|
|
58
|
+
if (!fieldConfig) {
|
|
59
|
+
result[field] = defaultMessage;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Try to map the Zod message to a config key.
|
|
64
|
+
// We check for simple substrings in the Zod message to identify the code.
|
|
65
|
+
const configMessage = resolveConfigMessage(defaultMessage, fieldConfig);
|
|
66
|
+
result[field] = configMessage ?? defaultMessage;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Try to find a matching override in fieldConfig based on the Zod message content.
|
|
74
|
+
* Returns the override string, or null if no match found.
|
|
75
|
+
*/
|
|
76
|
+
function resolveConfigMessage(zodMessage, fieldConfig) {
|
|
77
|
+
if (!fieldConfig || typeof fieldConfig !== 'object') {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Direct key match: developer can use zod code names directly
|
|
82
|
+
// e.g. errors.too_small, errors.invalid_type
|
|
83
|
+
for (const [zodCode, configKey] of Object.entries(ZOD_CODE_TO_CONFIG_KEY)) {
|
|
84
|
+
if (fieldConfig[zodCode]) {
|
|
85
|
+
// Check if Zod message suggests this error type
|
|
86
|
+
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
87
|
+
return fieldConfig[zodCode];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Also support friendly key names: errors.min, errors.required, etc.
|
|
91
|
+
if (fieldConfig[configKey]) {
|
|
92
|
+
if (messageMatchesCode(zodMessage, zodCode)) {
|
|
93
|
+
return fieldConfig[configKey];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fallback: if developer set errors.required and Zod says "Required"
|
|
99
|
+
if (fieldConfig.required && /required|undefined|null/i.test(zodMessage)) {
|
|
100
|
+
return fieldConfig.required;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Heuristic: does the Zod default message suggest a certain error code?
|
|
108
|
+
*/
|
|
109
|
+
function messageMatchesCode(message, zodCode) {
|
|
110
|
+
const patterns = {
|
|
111
|
+
too_small: /at least|minimum|must contain at least|min/i,
|
|
112
|
+
too_big: /at most|maximum|must contain at most|max/i,
|
|
113
|
+
invalid_type: /required|expected|received undefined|null/i,
|
|
114
|
+
invalid_enum_value: /invalid enum|expected one of/i,
|
|
115
|
+
invalid_string: /invalid|email|url|uuid|datetime/i,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return patterns[zodCode]?.test(message) ?? false;
|
|
119
|
+
}
|