nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.2.8-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/.editorconfig +26 -26
- package/.prettierignore +17 -17
- package/CONTRIBUTING.md +291 -291
- package/INSTRUCTIONS.md +327 -327
- package/LICENSE +202 -202
- package/README.md +231 -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 +12 -12
- package/dist/generators/use-async-data/templates.js +17 -17
- package/dist/generators/use-fetch/templates.js +14 -14
- package/dist/index.js +39 -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 +254 -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 +139 -139
- package/src/generators/shared/runtime/useListConnector.ts +148 -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 +205 -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 +170 -170
- package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
- package/src/generators/use-fetch/templates.ts +214 -214
- package/src/index.ts +305 -303
- package/src/module/index.ts +158 -133
- package/src/module/types.ts +39 -31
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
-
/**
|
|
3
|
-
* useDeleteConnector — Runtime connector for DELETE endpoints.
|
|
4
|
-
*
|
|
5
|
-
* Manages:
|
|
6
|
-
* - The target item to delete (set from the table via openDelete)
|
|
7
|
-
* - Modal open/close state
|
|
8
|
-
* - Confirmation logic
|
|
9
|
-
* - Callbacks onSuccess / onError
|
|
10
|
-
*
|
|
11
|
-
* Copied to the user's project alongside the generated connectors.
|
|
12
|
-
*/
|
|
13
|
-
import { ref, computed } from 'vue';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param composableFn The generated delete composable, e.g. useAsyncDataDeletePet
|
|
17
|
-
* @param options Optional configuration
|
|
18
|
-
*/
|
|
19
|
-
export function useDeleteConnector(composableFn, options = {}) {
|
|
20
|
-
// ── State ──────────────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
const target = ref(null);
|
|
23
|
-
const isOpen = ref(false);
|
|
24
|
-
const loading = ref(false);
|
|
25
|
-
const error = ref(null);
|
|
26
|
-
|
|
27
|
-
// Callbacks — set by the developer or the generated component
|
|
28
|
-
const onSuccess = ref(null);
|
|
29
|
-
const onError = ref(null);
|
|
30
|
-
|
|
31
|
-
// ── Actions ────────────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Set the item to delete and open the confirmation modal.
|
|
35
|
-
* Called by useListConnector.openDelete(row).
|
|
36
|
-
*/
|
|
37
|
-
function setTarget(item) {
|
|
38
|
-
target.value = item;
|
|
39
|
-
isOpen.value = true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Cancel the delete operation — close modal and clear target.
|
|
44
|
-
*/
|
|
45
|
-
function cancel() {
|
|
46
|
-
isOpen.value = false;
|
|
47
|
-
target.value = null;
|
|
48
|
-
error.value = null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Confirm the delete — call the underlying composable with the target item.
|
|
53
|
-
*/
|
|
54
|
-
async function confirm() {
|
|
55
|
-
if (!target.value) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
loading.value = true;
|
|
60
|
-
error.value = null;
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// Pass the full target item; the generated composable extracts the id it needs
|
|
64
|
-
const composable = composableFn(target.value);
|
|
65
|
-
|
|
66
|
-
if (composable.execute) {
|
|
67
|
-
await composable.execute();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const err = composable.error?.value;
|
|
71
|
-
if (err) {
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const deletedItem = target.value;
|
|
76
|
-
isOpen.value = false;
|
|
77
|
-
target.value = null;
|
|
78
|
-
|
|
79
|
-
onSuccess.value?.(deletedItem);
|
|
80
|
-
} catch (err) {
|
|
81
|
-
error.value = err;
|
|
82
|
-
onError.value?.(err);
|
|
83
|
-
} finally {
|
|
84
|
-
loading.value = false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ── Derived ────────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
const hasTarget = computed(() => target.value !== null);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
// State
|
|
94
|
-
target,
|
|
95
|
-
isOpen,
|
|
96
|
-
loading,
|
|
97
|
-
error,
|
|
98
|
-
hasTarget,
|
|
99
|
-
|
|
100
|
-
// Callbacks (developer-assignable)
|
|
101
|
-
onSuccess,
|
|
102
|
-
onError,
|
|
103
|
-
|
|
104
|
-
// Actions
|
|
105
|
-
setTarget,
|
|
106
|
-
cancel,
|
|
107
|
-
confirm,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* useDeleteConnector — Runtime connector for DELETE endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Manages:
|
|
6
|
+
* - The target item to delete (set from the table via openDelete)
|
|
7
|
+
* - Modal open/close state
|
|
8
|
+
* - Confirmation logic
|
|
9
|
+
* - Callbacks onSuccess / onError
|
|
10
|
+
*
|
|
11
|
+
* Copied to the user's project alongside the generated connectors.
|
|
12
|
+
*/
|
|
13
|
+
import { ref, computed } from 'vue';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param composableFn The generated delete composable, e.g. useAsyncDataDeletePet
|
|
17
|
+
* @param options Optional configuration
|
|
18
|
+
*/
|
|
19
|
+
export function useDeleteConnector(composableFn, options = {}) {
|
|
20
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const target = ref(null);
|
|
23
|
+
const isOpen = ref(false);
|
|
24
|
+
const loading = ref(false);
|
|
25
|
+
const error = ref(null);
|
|
26
|
+
|
|
27
|
+
// Callbacks — set by the developer or the generated component
|
|
28
|
+
const onSuccess = ref(null);
|
|
29
|
+
const onError = ref(null);
|
|
30
|
+
|
|
31
|
+
// ── Actions ────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Set the item to delete and open the confirmation modal.
|
|
35
|
+
* Called by useListConnector.openDelete(row).
|
|
36
|
+
*/
|
|
37
|
+
function setTarget(item) {
|
|
38
|
+
target.value = item;
|
|
39
|
+
isOpen.value = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Cancel the delete operation — close modal and clear target.
|
|
44
|
+
*/
|
|
45
|
+
function cancel() {
|
|
46
|
+
isOpen.value = false;
|
|
47
|
+
target.value = null;
|
|
48
|
+
error.value = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Confirm the delete — call the underlying composable with the target item.
|
|
53
|
+
*/
|
|
54
|
+
async function confirm() {
|
|
55
|
+
if (!target.value) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
loading.value = true;
|
|
60
|
+
error.value = null;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Pass the full target item; the generated composable extracts the id it needs
|
|
64
|
+
const composable = composableFn(target.value);
|
|
65
|
+
|
|
66
|
+
if (composable.execute) {
|
|
67
|
+
await composable.execute();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const err = composable.error?.value;
|
|
71
|
+
if (err) {
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const deletedItem = target.value;
|
|
76
|
+
isOpen.value = false;
|
|
77
|
+
target.value = null;
|
|
78
|
+
|
|
79
|
+
onSuccess.value?.(deletedItem);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
error.value = err;
|
|
82
|
+
onError.value?.(err);
|
|
83
|
+
} finally {
|
|
84
|
+
loading.value = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Derived ────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const hasTarget = computed(() => target.value !== null);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
// State
|
|
94
|
+
target,
|
|
95
|
+
isOpen,
|
|
96
|
+
loading,
|
|
97
|
+
error,
|
|
98
|
+
hasTarget,
|
|
99
|
+
|
|
100
|
+
// Callbacks (developer-assignable)
|
|
101
|
+
onSuccess,
|
|
102
|
+
onError,
|
|
103
|
+
|
|
104
|
+
// Actions
|
|
105
|
+
setTarget,
|
|
106
|
+
cancel,
|
|
107
|
+
confirm,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
-
/**
|
|
3
|
-
* useDetailConnector — Runtime connector for single-item GET endpoints.
|
|
4
|
-
*
|
|
5
|
-
* Wraps a useAsyncData composable that returns a single object and exposes:
|
|
6
|
-
* - item, loading, error state
|
|
7
|
-
* - load(id) to fetch a specific item on demand
|
|
8
|
-
* - fields derived from the response (used by detail view components)
|
|
9
|
-
*
|
|
10
|
-
* Copied to the user's project alongside the generated connectors.
|
|
11
|
-
*/
|
|
12
|
-
import { ref, computed } from 'vue';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPetById
|
|
16
|
-
* @param options Optional configuration
|
|
17
|
-
*/
|
|
18
|
-
export function useDetailConnector(composableFn, options = {}) {
|
|
19
|
-
const { fields = [] } = options;
|
|
20
|
-
|
|
21
|
-
// The item ID to load — reactive. null = not loaded yet.
|
|
22
|
-
const currentId = ref(null);
|
|
23
|
-
|
|
24
|
-
// ── Execute the underlying composable lazily (only when currentId changes) ─
|
|
25
|
-
// We call the composable with lazy: true so it doesn't auto-fetch on mount.
|
|
26
|
-
// load(id) sets currentId which triggers the watch inside the composable.
|
|
27
|
-
const composable = composableFn(
|
|
28
|
-
computed(() => (currentId.value !== null ? { id: currentId.value } : null)),
|
|
29
|
-
{ lazy: true, immediate: false }
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
// ── Derived state ──────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
const item = computed(() => composable.data?.value ?? null);
|
|
35
|
-
const loading = computed(() => composable.pending?.value ?? false);
|
|
36
|
-
const error = computed(() => composable.error?.value ?? null);
|
|
37
|
-
|
|
38
|
-
// ── Actions ────────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
async function load(id) {
|
|
41
|
-
currentId.value = id;
|
|
42
|
-
await composable.refresh?.();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function clear() {
|
|
46
|
-
currentId.value = null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
// State
|
|
51
|
-
item,
|
|
52
|
-
loading,
|
|
53
|
-
error,
|
|
54
|
-
fields: computed(() => fields),
|
|
55
|
-
|
|
56
|
-
// Actions
|
|
57
|
-
load,
|
|
58
|
-
clear,
|
|
59
|
-
|
|
60
|
-
// Expose composable for advanced use (e.g. useFormConnector loadWith)
|
|
61
|
-
_composable: composable,
|
|
62
|
-
_currentId: currentId,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* useDetailConnector — Runtime connector for single-item GET endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Wraps a useAsyncData composable that returns a single object and exposes:
|
|
6
|
+
* - item, loading, error state
|
|
7
|
+
* - load(id) to fetch a specific item on demand
|
|
8
|
+
* - fields derived from the response (used by detail view components)
|
|
9
|
+
*
|
|
10
|
+
* Copied to the user's project alongside the generated connectors.
|
|
11
|
+
*/
|
|
12
|
+
import { ref, computed } from 'vue';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param composableFn The generated useAsyncData composable, e.g. useAsyncDataGetPetById
|
|
16
|
+
* @param options Optional configuration
|
|
17
|
+
*/
|
|
18
|
+
export function useDetailConnector(composableFn, options = {}) {
|
|
19
|
+
const { fields = [] } = options;
|
|
20
|
+
|
|
21
|
+
// The item ID to load — reactive. null = not loaded yet.
|
|
22
|
+
const currentId = ref(null);
|
|
23
|
+
|
|
24
|
+
// ── Execute the underlying composable lazily (only when currentId changes) ─
|
|
25
|
+
// We call the composable with lazy: true so it doesn't auto-fetch on mount.
|
|
26
|
+
// load(id) sets currentId which triggers the watch inside the composable.
|
|
27
|
+
const composable = composableFn(
|
|
28
|
+
computed(() => (currentId.value !== null ? { id: currentId.value } : null)),
|
|
29
|
+
{ lazy: true, immediate: false }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// ── Derived state ──────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const item = computed(() => composable.data?.value ?? null);
|
|
35
|
+
const loading = computed(() => composable.pending?.value ?? false);
|
|
36
|
+
const error = computed(() => composable.error?.value ?? null);
|
|
37
|
+
|
|
38
|
+
// ── Actions ────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
async function load(id) {
|
|
41
|
+
currentId.value = id;
|
|
42
|
+
await composable.refresh?.();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function clear() {
|
|
46
|
+
currentId.value = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
// State
|
|
51
|
+
item,
|
|
52
|
+
loading,
|
|
53
|
+
error,
|
|
54
|
+
fields: computed(() => fields),
|
|
55
|
+
|
|
56
|
+
// Actions
|
|
57
|
+
load,
|
|
58
|
+
clear,
|
|
59
|
+
|
|
60
|
+
// Expose composable for advanced use (e.g. useFormConnector loadWith)
|
|
61
|
+
_composable: composable,
|
|
62
|
+
_currentId: currentId,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
-
/**
|
|
3
|
-
* useFormConnector — Runtime connector for create/update form endpoints.
|
|
4
|
-
*
|
|
5
|
-
* Responsibilities:
|
|
6
|
-
* - Hold the reactive form model
|
|
7
|
-
* - Validate with a Zod schema (generated at code-gen time) on submit
|
|
8
|
-
* - Merge Zod error messages with per-field overrides from config
|
|
9
|
-
* - Submit the validated data via the provided useAsyncData composable
|
|
10
|
-
* - Optionally pre-fill from a useDetailConnector (loadWith option)
|
|
11
|
-
*
|
|
12
|
-
* Copied to the user's project alongside the generated connectors.
|
|
13
|
-
*/
|
|
14
|
-
import { ref, computed, watch } from 'vue';
|
|
15
|
-
import { mergeZodErrors } from './zod-error-merger.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param composableFn The generated mutation composable, e.g. useAsyncDataCreatePet
|
|
19
|
-
* @param options { schema, fields, loadWith?, errorConfig? }
|
|
20
|
-
*/
|
|
21
|
-
export function useFormConnector(composableFn, options = {}) {
|
|
22
|
-
const { schema, fields = [], loadWith = null, errorConfig = {} } = options;
|
|
23
|
-
|
|
24
|
-
// ── Form state ─────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
const model = ref({});
|
|
27
|
-
const errors = ref({});
|
|
28
|
-
const loading = ref(false);
|
|
29
|
-
const submitError = ref(null);
|
|
30
|
-
const submitted = ref(false);
|
|
31
|
-
|
|
32
|
-
// Callbacks — set by the developer or the generated component
|
|
33
|
-
const onSuccess = ref(null);
|
|
34
|
-
const onError = ref(null);
|
|
35
|
-
|
|
36
|
-
// ── Pre-fill from detail connector ────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
if (loadWith) {
|
|
39
|
-
// When the detail item changes (e.g. user clicks "Edit"), pre-fill the model
|
|
40
|
-
watch(
|
|
41
|
-
() => loadWith.item?.value,
|
|
42
|
-
(newItem) => {
|
|
43
|
-
if (newItem) {
|
|
44
|
-
setValues(newItem);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
{ immediate: true }
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── Actions ────────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
function setValues(data) {
|
|
54
|
-
model.value = { ...model.value, ...data };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function reset() {
|
|
58
|
-
model.value = {};
|
|
59
|
-
errors.value = {};
|
|
60
|
-
submitError.value = null;
|
|
61
|
-
submitted.value = false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function submit() {
|
|
65
|
-
submitted.value = true;
|
|
66
|
-
|
|
67
|
-
// 1. Zod validation (if schema provided)
|
|
68
|
-
if (schema) {
|
|
69
|
-
const result = schema.safeParse(model.value);
|
|
70
|
-
|
|
71
|
-
if (!result.success) {
|
|
72
|
-
const fieldErrors = result.error.flatten().fieldErrors;
|
|
73
|
-
errors.value = mergeZodErrors(fieldErrors, errorConfig);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Clear previous errors on successful validation
|
|
78
|
-
errors.value = {};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 2. Call the underlying composable
|
|
82
|
-
loading.value = true;
|
|
83
|
-
submitError.value = null;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
// The mutation composable accepts the model as its payload
|
|
87
|
-
const composable = composableFn(model.value);
|
|
88
|
-
|
|
89
|
-
// Wait for the async data to resolve
|
|
90
|
-
if (composable.execute) {
|
|
91
|
-
await composable.execute();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const data = composable.data?.value;
|
|
95
|
-
const err = composable.error?.value;
|
|
96
|
-
|
|
97
|
-
if (err) {
|
|
98
|
-
throw err;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
onSuccess.value?.(data);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
submitError.value = err;
|
|
104
|
-
onError.value?.(err);
|
|
105
|
-
} finally {
|
|
106
|
-
loading.value = false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── Derived ────────────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
const isValid = computed(() => {
|
|
113
|
-
if (!schema) return true;
|
|
114
|
-
return schema.safeParse(model.value).success;
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const hasErrors = computed(() => Object.keys(errors.value).length > 0);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
// State
|
|
121
|
-
model,
|
|
122
|
-
errors,
|
|
123
|
-
loading,
|
|
124
|
-
submitError,
|
|
125
|
-
submitted,
|
|
126
|
-
isValid,
|
|
127
|
-
hasErrors,
|
|
128
|
-
fields: computed(() => fields),
|
|
129
|
-
|
|
130
|
-
// Callbacks (developer-assignable)
|
|
131
|
-
onSuccess,
|
|
132
|
-
onError,
|
|
133
|
-
|
|
134
|
-
// Actions
|
|
135
|
-
submit,
|
|
136
|
-
reset,
|
|
137
|
-
setValues,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* useFormConnector — Runtime connector for create/update form endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* - Hold the reactive form model
|
|
7
|
+
* - Validate with a Zod schema (generated at code-gen time) on submit
|
|
8
|
+
* - Merge Zod error messages with per-field overrides from config
|
|
9
|
+
* - Submit the validated data via the provided useAsyncData composable
|
|
10
|
+
* - Optionally pre-fill from a useDetailConnector (loadWith option)
|
|
11
|
+
*
|
|
12
|
+
* Copied to the user's project alongside the generated connectors.
|
|
13
|
+
*/
|
|
14
|
+
import { ref, computed, watch } from 'vue';
|
|
15
|
+
import { mergeZodErrors } from './zod-error-merger.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param composableFn The generated mutation composable, e.g. useAsyncDataCreatePet
|
|
19
|
+
* @param options { schema, fields, loadWith?, errorConfig? }
|
|
20
|
+
*/
|
|
21
|
+
export function useFormConnector(composableFn, options = {}) {
|
|
22
|
+
const { schema, fields = [], loadWith = null, errorConfig = {} } = options;
|
|
23
|
+
|
|
24
|
+
// ── Form state ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const model = ref({});
|
|
27
|
+
const errors = ref({});
|
|
28
|
+
const loading = ref(false);
|
|
29
|
+
const submitError = ref(null);
|
|
30
|
+
const submitted = ref(false);
|
|
31
|
+
|
|
32
|
+
// Callbacks — set by the developer or the generated component
|
|
33
|
+
const onSuccess = ref(null);
|
|
34
|
+
const onError = ref(null);
|
|
35
|
+
|
|
36
|
+
// ── Pre-fill from detail connector ────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
if (loadWith) {
|
|
39
|
+
// When the detail item changes (e.g. user clicks "Edit"), pre-fill the model
|
|
40
|
+
watch(
|
|
41
|
+
() => loadWith.item?.value,
|
|
42
|
+
(newItem) => {
|
|
43
|
+
if (newItem) {
|
|
44
|
+
setValues(newItem);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{ immediate: true }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Actions ────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
function setValues(data) {
|
|
54
|
+
model.value = { ...model.value, ...data };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function reset() {
|
|
58
|
+
model.value = {};
|
|
59
|
+
errors.value = {};
|
|
60
|
+
submitError.value = null;
|
|
61
|
+
submitted.value = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function submit() {
|
|
65
|
+
submitted.value = true;
|
|
66
|
+
|
|
67
|
+
// 1. Zod validation (if schema provided)
|
|
68
|
+
if (schema) {
|
|
69
|
+
const result = schema.safeParse(model.value);
|
|
70
|
+
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
const fieldErrors = result.error.flatten().fieldErrors;
|
|
73
|
+
errors.value = mergeZodErrors(fieldErrors, errorConfig);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Clear previous errors on successful validation
|
|
78
|
+
errors.value = {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. Call the underlying composable
|
|
82
|
+
loading.value = true;
|
|
83
|
+
submitError.value = null;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// The mutation composable accepts the model as its payload
|
|
87
|
+
const composable = composableFn(model.value);
|
|
88
|
+
|
|
89
|
+
// Wait for the async data to resolve
|
|
90
|
+
if (composable.execute) {
|
|
91
|
+
await composable.execute();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = composable.data?.value;
|
|
95
|
+
const err = composable.error?.value;
|
|
96
|
+
|
|
97
|
+
if (err) {
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onSuccess.value?.(data);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
submitError.value = err;
|
|
104
|
+
onError.value?.(err);
|
|
105
|
+
} finally {
|
|
106
|
+
loading.value = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Derived ────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
const isValid = computed(() => {
|
|
113
|
+
if (!schema) return true;
|
|
114
|
+
return schema.safeParse(model.value).success;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const hasErrors = computed(() => Object.keys(errors.value).length > 0);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
// State
|
|
121
|
+
model,
|
|
122
|
+
errors,
|
|
123
|
+
loading,
|
|
124
|
+
submitError,
|
|
125
|
+
submitted,
|
|
126
|
+
isValid,
|
|
127
|
+
hasErrors,
|
|
128
|
+
fields: computed(() => fields),
|
|
129
|
+
|
|
130
|
+
// Callbacks (developer-assignable)
|
|
131
|
+
onSuccess,
|
|
132
|
+
onError,
|
|
133
|
+
|
|
134
|
+
// Actions
|
|
135
|
+
submit,
|
|
136
|
+
reset,
|
|
137
|
+
setValues,
|
|
138
|
+
};
|
|
139
|
+
}
|