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,254 +1,307 @@
|
|
|
1
|
-
import { pascalCase, kebabCase } from 'change-case';
|
|
2
|
-
import type { ResourceInfo } from '../schema-analyzer/types.js';
|
|
3
|
-
|
|
4
|
-
// ─── File header ──────────────────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
function generateFileHeader(): string {
|
|
7
|
-
return `/**
|
|
8
|
-
* ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
|
9
|
-
*
|
|
10
|
-
* This file was automatically generated by nuxt-openapi-generator.
|
|
11
|
-
* Any manual changes will be overwritten on the next generation.
|
|
12
|
-
*
|
|
13
|
-
* @generated by nuxt-openapi-generator
|
|
14
|
-
* @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/* eslint-disable */
|
|
18
|
-
// @ts-nocheck
|
|
19
|
-
`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ─── Naming helpers ───────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* operationId → useAsyncData composable name.
|
|
26
|
-
* 'getPets' → 'useAsyncDataGetPets'
|
|
27
|
-
*/
|
|
28
|
-
function toAsyncDataName(operationId: string): string {
|
|
29
|
-
return `useAsyncData${pascalCase(operationId)}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* composable name → kebab-case file name (without .ts).
|
|
34
|
-
* 'useAsyncDataGetPets' → 'use-async-data-get-pets'
|
|
35
|
-
*/
|
|
36
|
-
function toFileName(composableName: string): string {
|
|
37
|
-
return kebabCase(composableName);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── Section builders ─────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Build all `import` lines for a resource connector.
|
|
44
|
-
*/
|
|
45
|
-
function buildImports(resource: ResourceInfo, composablesRelDir: string): string {
|
|
46
|
-
const lines: string[] = [];
|
|
47
|
-
|
|
48
|
-
// zod
|
|
49
|
-
lines.push(`import { z } from 'zod';`);
|
|
50
|
-
lines.push('');
|
|
51
|
-
|
|
52
|
-
// runtime helpers (Nuxt alias — set up by the Nuxt module)
|
|
53
|
-
const runtimeHelpers: string[] = [];
|
|
54
|
-
if (resource.listEndpoint) {
|
|
55
|
-
runtimeHelpers.push('useListConnector');
|
|
56
|
-
}
|
|
57
|
-
if (resource.detailEndpoint) {
|
|
58
|
-
runtimeHelpers.push('useDetailConnector');
|
|
59
|
-
}
|
|
60
|
-
if (resource.createEndpoint || resource.updateEndpoint) {
|
|
61
|
-
runtimeHelpers.push('useFormConnector');
|
|
62
|
-
}
|
|
63
|
-
if (resource.deleteEndpoint) {
|
|
64
|
-
runtimeHelpers.push('useDeleteConnector');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
for (const helper of runtimeHelpers) {
|
|
68
|
-
lines.push(`import { ${helper} } from '#nxh/runtime/${helper}';`);
|
|
69
|
-
}
|
|
70
|
-
lines.push('');
|
|
71
|
-
|
|
72
|
-
// generated useAsyncData composables
|
|
73
|
-
const addImport = (operationId: string): void => {
|
|
74
|
-
const name = toAsyncDataName(operationId);
|
|
75
|
-
const file = toFileName(name);
|
|
76
|
-
lines.push(`import { ${name} } from '${composablesRelDir}/${file}';`);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
if (resource.listEndpoint) {
|
|
80
|
-
addImport(resource.listEndpoint.operationId);
|
|
81
|
-
}
|
|
82
|
-
if (resource.detailEndpoint) {
|
|
83
|
-
addImport(resource.detailEndpoint.operationId);
|
|
84
|
-
}
|
|
85
|
-
if (resource.createEndpoint) {
|
|
86
|
-
addImport(resource.createEndpoint.operationId);
|
|
87
|
-
}
|
|
88
|
-
if (resource.updateEndpoint) {
|
|
89
|
-
addImport(resource.updateEndpoint.operationId);
|
|
90
|
-
}
|
|
91
|
-
if (resource.deleteEndpoint) {
|
|
92
|
-
addImport(resource.deleteEndpoint.operationId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return lines.join('\n');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Build Zod schema const declarations.
|
|
100
|
-
*/
|
|
101
|
-
function buildZodSchemas(resource: ResourceInfo): string {
|
|
102
|
-
const lines: string[] = [];
|
|
103
|
-
const pascal = pascalCase(resource.name);
|
|
104
|
-
|
|
105
|
-
if (resource.zodSchemas.create) {
|
|
106
|
-
lines.push(`const ${pascal}CreateSchema = ${resource.zodSchemas.create};`);
|
|
107
|
-
lines.push('');
|
|
108
|
-
}
|
|
109
|
-
if (resource.zodSchemas.update) {
|
|
110
|
-
lines.push(`const ${pascal}UpdateSchema = ${resource.zodSchemas.update};`);
|
|
111
|
-
lines.push('');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Derive TS types via z.infer
|
|
115
|
-
if (resource.zodSchemas.create) {
|
|
116
|
-
lines.push(`type ${pascal}CreateInput = z.infer<typeof ${pascal}CreateSchema>;`);
|
|
117
|
-
}
|
|
118
|
-
if (resource.zodSchemas.update) {
|
|
119
|
-
lines.push(`type ${pascal}UpdateInput = z.infer<typeof ${pascal}UpdateSchema>;`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return lines.join('\n');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Build
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
1
|
+
import { pascalCase, kebabCase } from 'change-case';
|
|
2
|
+
import type { ResourceInfo } from '../schema-analyzer/types.js';
|
|
3
|
+
|
|
4
|
+
// ─── File header ──────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
function generateFileHeader(): string {
|
|
7
|
+
return `/**
|
|
8
|
+
* ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
|
9
|
+
*
|
|
10
|
+
* This file was automatically generated by nuxt-openapi-generator.
|
|
11
|
+
* Any manual changes will be overwritten on the next generation.
|
|
12
|
+
*
|
|
13
|
+
* @generated by nuxt-openapi-generator
|
|
14
|
+
* @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* eslint-disable */
|
|
18
|
+
// @ts-nocheck
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Naming helpers ───────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* operationId → useAsyncData composable name.
|
|
26
|
+
* 'getPets' → 'useAsyncDataGetPets'
|
|
27
|
+
*/
|
|
28
|
+
function toAsyncDataName(operationId: string): string {
|
|
29
|
+
return `useAsyncData${pascalCase(operationId)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* composable name → kebab-case file name (without .ts).
|
|
34
|
+
* 'useAsyncDataGetPets' → 'use-async-data-get-pets'
|
|
35
|
+
*/
|
|
36
|
+
function toFileName(composableName: string): string {
|
|
37
|
+
return kebabCase(composableName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Section builders ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build all `import` lines for a resource connector.
|
|
44
|
+
*/
|
|
45
|
+
function buildImports(resource: ResourceInfo, composablesRelDir: string): string {
|
|
46
|
+
const lines: string[] = [];
|
|
47
|
+
|
|
48
|
+
// zod
|
|
49
|
+
lines.push(`import { z } from 'zod';`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
|
|
52
|
+
// runtime helpers (Nuxt alias — set up by the Nuxt module)
|
|
53
|
+
const runtimeHelpers: string[] = [];
|
|
54
|
+
if (resource.listEndpoint) {
|
|
55
|
+
runtimeHelpers.push('useListConnector');
|
|
56
|
+
}
|
|
57
|
+
if (resource.detailEndpoint) {
|
|
58
|
+
runtimeHelpers.push('useDetailConnector');
|
|
59
|
+
}
|
|
60
|
+
if (resource.createEndpoint || resource.updateEndpoint) {
|
|
61
|
+
runtimeHelpers.push('useFormConnector');
|
|
62
|
+
}
|
|
63
|
+
if (resource.deleteEndpoint) {
|
|
64
|
+
runtimeHelpers.push('useDeleteConnector');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const helper of runtimeHelpers) {
|
|
68
|
+
lines.push(`import { ${helper} } from '#nxh/runtime/${helper}';`);
|
|
69
|
+
}
|
|
70
|
+
lines.push('');
|
|
71
|
+
|
|
72
|
+
// generated useAsyncData composables
|
|
73
|
+
const addImport = (operationId: string): void => {
|
|
74
|
+
const name = toAsyncDataName(operationId);
|
|
75
|
+
const file = toFileName(name);
|
|
76
|
+
lines.push(`import { ${name} } from '${composablesRelDir}/${file}';`);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (resource.listEndpoint) {
|
|
80
|
+
addImport(resource.listEndpoint.operationId);
|
|
81
|
+
}
|
|
82
|
+
if (resource.detailEndpoint) {
|
|
83
|
+
addImport(resource.detailEndpoint.operationId);
|
|
84
|
+
}
|
|
85
|
+
if (resource.createEndpoint) {
|
|
86
|
+
addImport(resource.createEndpoint.operationId);
|
|
87
|
+
}
|
|
88
|
+
if (resource.updateEndpoint) {
|
|
89
|
+
addImport(resource.updateEndpoint.operationId);
|
|
90
|
+
}
|
|
91
|
+
if (resource.deleteEndpoint) {
|
|
92
|
+
addImport(resource.deleteEndpoint.operationId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build Zod schema const declarations.
|
|
100
|
+
*/
|
|
101
|
+
function buildZodSchemas(resource: ResourceInfo): string {
|
|
102
|
+
const lines: string[] = [];
|
|
103
|
+
const pascal = pascalCase(resource.name);
|
|
104
|
+
|
|
105
|
+
if (resource.zodSchemas.create) {
|
|
106
|
+
lines.push(`const ${pascal}CreateSchema = ${resource.zodSchemas.create};`);
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
if (resource.zodSchemas.update) {
|
|
110
|
+
lines.push(`const ${pascal}UpdateSchema = ${resource.zodSchemas.update};`);
|
|
111
|
+
lines.push('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Derive TS types via z.infer
|
|
115
|
+
if (resource.zodSchemas.create) {
|
|
116
|
+
lines.push(`type ${pascal}CreateInput = z.infer<typeof ${pascal}CreateSchema>;`);
|
|
117
|
+
}
|
|
118
|
+
if (resource.zodSchemas.update) {
|
|
119
|
+
lines.push(`type ${pascal}UpdateInput = z.infer<typeof ${pascal}UpdateSchema>;`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return lines.join('\n');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build a const array with the column definitions inferred from the resource.
|
|
127
|
+
* Returns an empty string if the resource has no columns.
|
|
128
|
+
*/
|
|
129
|
+
function buildColumns(resource: ResourceInfo): string {
|
|
130
|
+
if (!resource.columns || resource.columns.length === 0) {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
const camel = resource.composableName
|
|
134
|
+
.replace(/^use/, '')
|
|
135
|
+
.replace(/Connector$/, '')
|
|
136
|
+
.replace(/^./, (c) => c.toLowerCase());
|
|
137
|
+
const varName = `${camel}Columns`;
|
|
138
|
+
const entries = resource.columns
|
|
139
|
+
.map((col) => ` { key: '${col.key}', label: '${col.label}', type: '${col.type}' }`)
|
|
140
|
+
.join(',\n');
|
|
141
|
+
return `const ${varName} = [\n${entries},\n];`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build the body of the exported connector function.
|
|
146
|
+
*/
|
|
147
|
+
function buildFunctionBody(resource: ResourceInfo): string {
|
|
148
|
+
const pascal = pascalCase(resource.name);
|
|
149
|
+
const hasColumns = resource.columns && resource.columns.length > 0;
|
|
150
|
+
const camel = resource.composableName
|
|
151
|
+
.replace(/^use/, '')
|
|
152
|
+
.replace(/Connector$/, '')
|
|
153
|
+
.replace(/^./, (c) => c.toLowerCase());
|
|
154
|
+
const columnsVar = `${camel}Columns`;
|
|
155
|
+
const subConnectors: string[] = [];
|
|
156
|
+
|
|
157
|
+
// Destructure options param — only what's relevant for this resource
|
|
158
|
+
const optionKeys: string[] = [];
|
|
159
|
+
if (resource.listEndpoint && hasColumns) {
|
|
160
|
+
optionKeys.push('columnLabels', 'columnLabel');
|
|
161
|
+
}
|
|
162
|
+
if (resource.createEndpoint && resource.zodSchemas.create) {
|
|
163
|
+
optionKeys.push('createSchema');
|
|
164
|
+
}
|
|
165
|
+
if (resource.updateEndpoint && resource.zodSchemas.update) {
|
|
166
|
+
optionKeys.push('updateSchema');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const optionsDestructure =
|
|
170
|
+
optionKeys.length > 0 ? ` const { ${optionKeys.join(', ')} } = options;\n` : '';
|
|
171
|
+
|
|
172
|
+
if (resource.listEndpoint) {
|
|
173
|
+
const fn = toAsyncDataName(resource.listEndpoint.operationId);
|
|
174
|
+
// paginated: true tells useListConnector to expose pagination helpers
|
|
175
|
+
// (goToPage, nextPage, prevPage, setPerPage, pagination ref).
|
|
176
|
+
// We set it whenever the spec declares a list endpoint that has a response schema,
|
|
177
|
+
// which is a reliable proxy for "this API returns structured data worth paginating".
|
|
178
|
+
const paginatedFlag = resource.listEndpoint.responseSchema ? 'paginated: true' : '';
|
|
179
|
+
const columnsArg = hasColumns ? `columns: ${columnsVar}` : '';
|
|
180
|
+
const labelArgs = hasColumns ? 'columnLabels, columnLabel' : '';
|
|
181
|
+
const allArgs = [paginatedFlag, columnsArg, labelArgs].filter(Boolean).join(', ');
|
|
182
|
+
const opts = allArgs ? `{ ${allArgs} }` : '{}';
|
|
183
|
+
subConnectors.push(` const table = useListConnector(${fn}, ${opts});`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (resource.detailEndpoint) {
|
|
187
|
+
const fn = toAsyncDataName(resource.detailEndpoint.operationId);
|
|
188
|
+
subConnectors.push(` const detail = useDetailConnector(${fn});`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (resource.createEndpoint) {
|
|
192
|
+
const fn = toAsyncDataName(resource.createEndpoint.operationId);
|
|
193
|
+
const schemaArg = resource.zodSchemas.create
|
|
194
|
+
? `{ schema: ${pascal}CreateSchema, schemaOverride: createSchema }`
|
|
195
|
+
: '{}';
|
|
196
|
+
subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg});`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (resource.updateEndpoint) {
|
|
200
|
+
const fn = toAsyncDataName(resource.updateEndpoint.operationId);
|
|
201
|
+
const hasDetail = !!resource.detailEndpoint;
|
|
202
|
+
|
|
203
|
+
// Build the options argument for useFormConnector:
|
|
204
|
+
// schema → Zod schema for client-side validation before submission
|
|
205
|
+
// loadWith → reference to the detail connector so the form auto-fills
|
|
206
|
+
// when detail.item changes (user clicks "Edit" on a row)
|
|
207
|
+
//
|
|
208
|
+
// Four combinations are possible depending on what the spec provides:
|
|
209
|
+
let schemaArg = '{}';
|
|
210
|
+
if (resource.zodSchemas.update && hasDetail) {
|
|
211
|
+
// Best case: validate AND pre-fill from detail
|
|
212
|
+
schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema, loadWith: detail }`;
|
|
213
|
+
} else if (resource.zodSchemas.update) {
|
|
214
|
+
// Validate, but no detail endpoint to pre-fill from
|
|
215
|
+
schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema }`;
|
|
216
|
+
} else if (hasDetail) {
|
|
217
|
+
// No Zod schema (no request body in spec), but still pre-fill from detail
|
|
218
|
+
schemaArg = `{ loadWith: detail }`;
|
|
219
|
+
}
|
|
220
|
+
subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg});`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (resource.deleteEndpoint) {
|
|
224
|
+
const fn = toAsyncDataName(resource.deleteEndpoint.operationId);
|
|
225
|
+
subConnectors.push(` const deleteAction = useDeleteConnector(${fn});`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Return object — only include what was built
|
|
229
|
+
const returnKeys: string[] = [];
|
|
230
|
+
if (resource.listEndpoint) {
|
|
231
|
+
returnKeys.push('table');
|
|
232
|
+
}
|
|
233
|
+
if (resource.detailEndpoint) {
|
|
234
|
+
returnKeys.push('detail');
|
|
235
|
+
}
|
|
236
|
+
if (resource.createEndpoint) {
|
|
237
|
+
returnKeys.push('createForm');
|
|
238
|
+
}
|
|
239
|
+
if (resource.updateEndpoint) {
|
|
240
|
+
returnKeys.push('updateForm');
|
|
241
|
+
}
|
|
242
|
+
if (resource.deleteEndpoint) {
|
|
243
|
+
returnKeys.push('deleteAction');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const returnStatement = ` return { ${returnKeys.join(', ')} };`;
|
|
247
|
+
|
|
248
|
+
return [
|
|
249
|
+
`export function ${resource.composableName}(options = {}) {`,
|
|
250
|
+
optionsDestructure.trimEnd(),
|
|
251
|
+
...subConnectors,
|
|
252
|
+
returnStatement,
|
|
253
|
+
`}`,
|
|
254
|
+
]
|
|
255
|
+
.filter((s) => s !== '')
|
|
256
|
+
.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Generate the full source of a `use{Resource}Connector.ts` file.
|
|
263
|
+
*
|
|
264
|
+
* @param resource ResourceInfo produced by Schema Analyzer
|
|
265
|
+
* @param composablesRelDir Relative path from the connector dir to the
|
|
266
|
+
* useAsyncData composables dir (e.g. '../use-async-data')
|
|
267
|
+
*/
|
|
268
|
+
export function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string): string {
|
|
269
|
+
const header = generateFileHeader();
|
|
270
|
+
const imports = buildImports(resource, composablesRelDir);
|
|
271
|
+
const schemas = buildZodSchemas(resource);
|
|
272
|
+
const columns = buildColumns(resource);
|
|
273
|
+
const fn = buildFunctionBody(resource);
|
|
274
|
+
|
|
275
|
+
// Assemble file: header + imports + (optional) Zod blocks + columns const + function body.
|
|
276
|
+
// Each section ends with its own trailing newline; join with \n adds one blank
|
|
277
|
+
// line between sections, which matches Prettier's output for this structure.
|
|
278
|
+
const parts: string[] = [header, imports];
|
|
279
|
+
if (schemas.trim()) {
|
|
280
|
+
parts.push(schemas);
|
|
281
|
+
}
|
|
282
|
+
if (columns.trim()) {
|
|
283
|
+
parts.push(columns);
|
|
284
|
+
}
|
|
285
|
+
parts.push(fn);
|
|
286
|
+
|
|
287
|
+
return parts.join('\n') + '\n';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Derive the output filename for a connector.
|
|
292
|
+
* 'usePetsConnector' → 'use-pets-connector.ts'
|
|
293
|
+
*/
|
|
294
|
+
export function connectorFileName(composableName: string): string {
|
|
295
|
+
return `${kebabCase(composableName)}.ts`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Generate an index barrel file that re-exports all connectors.
|
|
300
|
+
*/
|
|
301
|
+
export function generateConnectorIndexFile(composableNames: string[]): string {
|
|
302
|
+
const header = generateFileHeader();
|
|
303
|
+
const exports = composableNames
|
|
304
|
+
.map((name) => `export { ${name} } from './${kebabCase(name)}';`)
|
|
305
|
+
.join('\n');
|
|
306
|
+
return `${header}${exports}\n`;
|
|
307
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for the Connector Generator — Fase 3.
|
|
3
|
-
*
|
|
4
|
-
* The Connector Generator reads the ResourceMap produced by the Schema Analyzer
|
|
5
|
-
* and writes one `use{Resource}Connector.ts` file per resource.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface ConnectorGeneratorOptions {
|
|
9
|
-
/** Absolute or relative path to the OpenAPI YAML/JSON spec */
|
|
10
|
-
inputSpec: string;
|
|
11
|
-
/** Directory where connector files will be written. E.g. ./composables/connectors */
|
|
12
|
-
outputDir: string;
|
|
13
|
-
/**
|
|
14
|
-
* Directory where the useAsyncData composables live, expressed as a path
|
|
15
|
-
* relative to outputDir. Defaults to '../use-async-data'.
|
|
16
|
-
*/
|
|
17
|
-
composablesRelDir?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Directory where runtime helpers will be copied to, expressed relative to
|
|
20
|
-
* outputDir. Defaults to '../runtime'.
|
|
21
|
-
*/
|
|
22
|
-
runtimeRelDir?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ConnectorFileInfo {
|
|
26
|
-
/** PascalCase resource name. E.g. 'Pet' */
|
|
27
|
-
resourceName: string;
|
|
28
|
-
/** Generated composable function name. E.g. 'usePetsConnector' */
|
|
29
|
-
composableName: string;
|
|
30
|
-
/** Output filename (kebab-case). E.g. 'use-pets-connector.ts' */
|
|
31
|
-
fileName: string;
|
|
32
|
-
/** Formatted TypeScript source ready to be written to disk */
|
|
33
|
-
content: string;
|
|
34
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Connector Generator — Fase 3.
|
|
3
|
+
*
|
|
4
|
+
* The Connector Generator reads the ResourceMap produced by the Schema Analyzer
|
|
5
|
+
* and writes one `use{Resource}Connector.ts` file per resource.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ConnectorGeneratorOptions {
|
|
9
|
+
/** Absolute or relative path to the OpenAPI YAML/JSON spec */
|
|
10
|
+
inputSpec: string;
|
|
11
|
+
/** Directory where connector files will be written. E.g. ./composables/connectors */
|
|
12
|
+
outputDir: string;
|
|
13
|
+
/**
|
|
14
|
+
* Directory where the useAsyncData composables live, expressed as a path
|
|
15
|
+
* relative to outputDir. Defaults to '../use-async-data'.
|
|
16
|
+
*/
|
|
17
|
+
composablesRelDir?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Directory where runtime helpers will be copied to, expressed relative to
|
|
20
|
+
* outputDir. Defaults to '../runtime'.
|
|
21
|
+
*/
|
|
22
|
+
runtimeRelDir?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ConnectorFileInfo {
|
|
26
|
+
/** PascalCase resource name. E.g. 'Pet' */
|
|
27
|
+
resourceName: string;
|
|
28
|
+
/** Generated composable function name. E.g. 'usePetsConnector' */
|
|
29
|
+
composableName: string;
|
|
30
|
+
/** Output filename (kebab-case). E.g. 'use-pets-connector.ts' */
|
|
31
|
+
fileName: string;
|
|
32
|
+
/** Formatted TypeScript source ready to be written to disk */
|
|
33
|
+
content: string;
|
|
34
|
+
}
|