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.
Files changed (68) hide show
  1. package/.editorconfig +26 -26
  2. package/.prettierignore +17 -17
  3. package/CONTRIBUTING.md +291 -291
  4. package/INSTRUCTIONS.md +327 -327
  5. package/LICENSE +202 -202
  6. package/README.md +309 -231
  7. package/dist/cli/config.d.ts +9 -2
  8. package/dist/cli/config.js +1 -1
  9. package/dist/cli/logo.js +5 -5
  10. package/dist/cli/messages.d.ts +1 -0
  11. package/dist/cli/messages.js +2 -0
  12. package/dist/cli/prompts.d.ts +5 -0
  13. package/dist/cli/prompts.js +12 -0
  14. package/dist/cli/types.d.ts +1 -1
  15. package/dist/generators/components/connector-generator/templates.js +68 -19
  16. package/dist/generators/shared/runtime/useFormConnector.js +8 -1
  17. package/dist/generators/shared/runtime/useListConnector.js +13 -6
  18. package/dist/generators/use-async-data/generator.js +4 -0
  19. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
  20. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
  21. package/dist/generators/use-async-data/templates.js +17 -17
  22. package/dist/generators/use-fetch/generator.js +4 -0
  23. package/dist/generators/use-fetch/templates.js +14 -14
  24. package/dist/index.js +40 -27
  25. package/dist/module/index.js +19 -0
  26. package/dist/module/types.d.ts +7 -0
  27. package/docs/API-REFERENCE.md +886 -886
  28. package/docs/generated-components.md +615 -615
  29. package/docs/headless-composables-ui.md +569 -569
  30. package/eslint.config.js +85 -85
  31. package/package.json +1 -1
  32. package/src/cli/config.ts +147 -140
  33. package/src/cli/logger.ts +124 -124
  34. package/src/cli/logo.ts +25 -25
  35. package/src/cli/messages.ts +4 -0
  36. package/src/cli/prompts.ts +14 -1
  37. package/src/cli/types.ts +50 -50
  38. package/src/generators/components/connector-generator/generator.ts +138 -138
  39. package/src/generators/components/connector-generator/templates.ts +307 -254
  40. package/src/generators/components/connector-generator/types.ts +34 -34
  41. package/src/generators/components/schema-analyzer/index.ts +44 -44
  42. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  43. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  44. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  45. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  46. package/src/generators/components/schema-analyzer/types.ts +177 -177
  47. package/src/generators/nuxt-server/generator.ts +272 -272
  48. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  49. package/src/generators/shared/runtime/pagination.ts +323 -323
  50. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  51. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  52. package/src/generators/shared/runtime/useFormConnector.ts +147 -139
  53. package/src/generators/shared/runtime/useListConnector.ts +158 -148
  54. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  55. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  56. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  57. package/src/generators/use-async-data/generator.ts +213 -205
  58. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  59. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  60. package/src/generators/use-async-data/templates.ts +257 -257
  61. package/src/generators/use-fetch/generator.ts +178 -170
  62. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  63. package/src/generators/use-fetch/templates.ts +214 -214
  64. package/src/index.ts +306 -303
  65. package/src/module/index.ts +158 -133
  66. package/src/module/types.ts +39 -31
  67. package/dist/generators/tanstack-query/generator.d.ts +0 -5
  68. 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 the body of the exported connector function.
127
- */
128
- function buildFunctionBody(resource: ResourceInfo): string {
129
- const pascal = pascalCase(resource.name);
130
- const subConnectors: string[] = [];
131
-
132
- if (resource.listEndpoint) {
133
- const fn = toAsyncDataName(resource.listEndpoint.operationId);
134
- // paginated: true tells useListConnector to expose pagination helpers
135
- // (goToPage, nextPage, prevPage, setPerPage, pagination ref).
136
- // We set it whenever the spec declares a list endpoint that has a response schema,
137
- // which is a reliable proxy for "this API returns structured data worth paginating".
138
- const opts = resource.listEndpoint.responseSchema ? '{ paginated: true }' : '{}';
139
- subConnectors.push(` const table = useListConnector(${fn}, ${opts});`);
140
- }
141
-
142
- if (resource.detailEndpoint) {
143
- const fn = toAsyncDataName(resource.detailEndpoint.operationId);
144
- subConnectors.push(` const detail = useDetailConnector(${fn});`);
145
- }
146
-
147
- if (resource.createEndpoint) {
148
- const fn = toAsyncDataName(resource.createEndpoint.operationId);
149
- const schemaArg = resource.zodSchemas.create ? `{ schema: ${pascal}CreateSchema }` : '{}';
150
- subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg});`);
151
- }
152
-
153
- if (resource.updateEndpoint) {
154
- const fn = toAsyncDataName(resource.updateEndpoint.operationId);
155
- const hasDetail = !!resource.detailEndpoint;
156
-
157
- // Build the options argument for useFormConnector:
158
- // schema → Zod schema for client-side validation before submission
159
- // loadWith → reference to the detail connector so the form auto-fills
160
- // when detail.item changes (user clicks "Edit" on a row)
161
- //
162
- // Four combinations are possible depending on what the spec provides:
163
- let schemaArg = '{}';
164
- if (resource.zodSchemas.update && hasDetail) {
165
- // Best case: validate AND pre-fill from detail
166
- schemaArg = `{ schema: ${pascal}UpdateSchema, loadWith: detail }`;
167
- } else if (resource.zodSchemas.update) {
168
- // Validate, but no detail endpoint to pre-fill from
169
- schemaArg = `{ schema: ${pascal}UpdateSchema }`;
170
- } else if (hasDetail) {
171
- // No Zod schema (no request body in spec), but still pre-fill from detail
172
- schemaArg = `{ loadWith: detail }`;
173
- }
174
- subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg});`);
175
- }
176
-
177
- if (resource.deleteEndpoint) {
178
- const fn = toAsyncDataName(resource.deleteEndpoint.operationId);
179
- subConnectors.push(` const deleteAction = useDeleteConnector(${fn});`);
180
- }
181
-
182
- // Return object only include what was built
183
- const returnKeys: string[] = [];
184
- if (resource.listEndpoint) {
185
- returnKeys.push('table');
186
- }
187
- if (resource.detailEndpoint) {
188
- returnKeys.push('detail');
189
- }
190
- if (resource.createEndpoint) {
191
- returnKeys.push('createForm');
192
- }
193
- if (resource.updateEndpoint) {
194
- returnKeys.push('updateForm');
195
- }
196
- if (resource.deleteEndpoint) {
197
- returnKeys.push('deleteAction');
198
- }
199
-
200
- const returnStatement = ` return { ${returnKeys.join(', ')} };`;
201
-
202
- return [
203
- `export function ${resource.composableName}() {`,
204
- ...subConnectors,
205
- returnStatement,
206
- `}`,
207
- ].join('\n');
208
- }
209
-
210
- // ─── Public API ───────────────────────────────────────────────────────────────
211
-
212
- /**
213
- * Generate the full source of a `use{Resource}Connector.ts` file.
214
- *
215
- * @param resource ResourceInfo produced by Schema Analyzer
216
- * @param composablesRelDir Relative path from the connector dir to the
217
- * useAsyncData composables dir (e.g. '../use-async-data')
218
- */
219
- export function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string): string {
220
- const header = generateFileHeader();
221
- const imports = buildImports(resource, composablesRelDir);
222
- const schemas = buildZodSchemas(resource);
223
- const fn = buildFunctionBody(resource);
224
-
225
- // Assemble file: header + imports + (optional) Zod blocks + function body.
226
- // Each section ends with its own trailing newline; join with \n adds one blank
227
- // line between sections, which matches Prettier's output for this structure.
228
- const parts: string[] = [header, imports];
229
- if (schemas.trim()) {
230
- parts.push(schemas);
231
- }
232
- parts.push(fn);
233
-
234
- return parts.join('\n') + '\n';
235
- }
236
-
237
- /**
238
- * Derive the output filename for a connector.
239
- * 'usePetsConnector' → 'use-pets-connector.ts'
240
- */
241
- export function connectorFileName(composableName: string): string {
242
- return `${kebabCase(composableName)}.ts`;
243
- }
244
-
245
- /**
246
- * Generate an index barrel file that re-exports all connectors.
247
- */
248
- export function generateConnectorIndexFile(composableNames: string[]): string {
249
- const header = generateFileHeader();
250
- const exports = composableNames
251
- .map((name) => `export { ${name} } from './${kebabCase(name)}';`)
252
- .join('\n');
253
- return `${header}${exports}\n`;
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
+ }