nuxt-openapi-hyperfetch 0.3.8-beta → 1.0.0

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 (64) hide show
  1. package/README.md +218 -212
  2. package/dist/generators/components/connector-generator/templates.js +67 -17
  3. package/dist/generators/components/schema-analyzer/intent-detector.js +1 -12
  4. package/dist/generators/components/schema-analyzer/openapi-reader.js +10 -1
  5. package/dist/generators/components/schema-analyzer/resource-grouper.js +7 -0
  6. package/dist/generators/components/schema-analyzer/schema-field-mapper.js +1 -22
  7. package/dist/generators/components/schema-analyzer/types.d.ts +10 -0
  8. package/dist/generators/connectors/generator.d.ts +12 -0
  9. package/dist/generators/connectors/generator.js +115 -0
  10. package/dist/generators/connectors/runtime/connector-types.d.ts +147 -0
  11. package/dist/generators/connectors/runtime/connector-types.js +10 -0
  12. package/dist/generators/connectors/runtime/useCreateConnector.d.ts +26 -0
  13. package/dist/generators/connectors/runtime/useCreateConnector.js +156 -0
  14. package/dist/generators/connectors/runtime/useDeleteConnector.d.ts +30 -0
  15. package/dist/generators/connectors/runtime/useDeleteConnector.js +143 -0
  16. package/dist/generators/connectors/runtime/useGetAllConnector.d.ts +25 -0
  17. package/dist/generators/connectors/runtime/useGetAllConnector.js +127 -0
  18. package/dist/generators/connectors/runtime/useGetConnector.d.ts +15 -0
  19. package/dist/generators/connectors/runtime/useGetConnector.js +99 -0
  20. package/dist/generators/connectors/runtime/useUpdateConnector.d.ts +34 -0
  21. package/dist/generators/connectors/runtime/useUpdateConnector.js +211 -0
  22. package/dist/generators/connectors/runtime/zod-error-merger.d.ts +23 -0
  23. package/dist/generators/connectors/runtime/zod-error-merger.js +106 -0
  24. package/dist/generators/connectors/templates.d.ts +4 -0
  25. package/dist/generators/connectors/templates.js +376 -0
  26. package/dist/generators/connectors/types.d.ts +37 -0
  27. package/dist/generators/connectors/types.js +7 -0
  28. package/dist/generators/shared/runtime/useDeleteConnector.js +4 -2
  29. package/dist/generators/shared/runtime/useDetailConnector.d.ts +0 -1
  30. package/dist/generators/shared/runtime/useDetailConnector.js +9 -20
  31. package/dist/generators/shared/runtime/useFormConnector.js +4 -3
  32. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +14 -5
  33. package/dist/generators/use-async-data/templates.js +20 -16
  34. package/dist/generators/use-fetch/templates.js +1 -1
  35. package/dist/index.js +1 -16
  36. package/dist/module/index.js +2 -3
  37. package/package.json +4 -3
  38. package/src/cli/prompts.ts +1 -7
  39. package/src/generators/components/connector-generator/templates.ts +97 -22
  40. package/src/generators/components/schema-analyzer/intent-detector.ts +1 -16
  41. package/src/generators/components/schema-analyzer/openapi-reader.ts +14 -1
  42. package/src/generators/components/schema-analyzer/resource-grouper.ts +9 -0
  43. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +1 -26
  44. package/src/generators/components/schema-analyzer/types.ts +11 -0
  45. package/src/generators/connectors/generator.ts +137 -0
  46. package/src/generators/connectors/runtime/connector-types.ts +207 -0
  47. package/src/generators/connectors/runtime/useCreateConnector.ts +199 -0
  48. package/src/generators/connectors/runtime/useDeleteConnector.ts +179 -0
  49. package/src/generators/connectors/runtime/useGetAllConnector.ts +151 -0
  50. package/src/generators/connectors/runtime/useGetConnector.ts +120 -0
  51. package/src/generators/connectors/runtime/useUpdateConnector.ts +257 -0
  52. package/src/generators/connectors/runtime/zod-error-merger.ts +119 -0
  53. package/src/generators/connectors/templates.ts +481 -0
  54. package/src/generators/connectors/types.ts +39 -0
  55. package/src/generators/shared/runtime/useDeleteConnector.ts +4 -2
  56. package/src/generators/shared/runtime/useDetailConnector.ts +8 -19
  57. package/src/generators/shared/runtime/useFormConnector.ts +4 -3
  58. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +16 -5
  59. package/src/generators/use-async-data/templates.ts +24 -16
  60. package/src/generators/use-fetch/templates.ts +1 -1
  61. package/src/index.ts +2 -19
  62. package/src/module/index.ts +2 -5
  63. package/docs/generated-components.md +0 -615
  64. package/docs/headless-composables-ui.md +0 -569
@@ -23,8 +23,8 @@ function toAsyncDataName(operationId) {
23
23
  return `useAsyncData${pascalCase(operationId)}`;
24
24
  }
25
25
  /**
26
- * composable name → PascalCase file name (without .ts), matching useAsyncData generator output.
27
- * 'useAsyncDataGetPets' → 'useAsyncDataGetPets'
26
+ * composable name → kebab-case file name (without .ts).
27
+ * 'useAsyncDataGetPets' → 'use-async-data-get-pets'
28
28
  */
29
29
  function toFileName(composableName) {
30
30
  return composableName;
@@ -35,14 +35,20 @@ function toFileName(composableName) {
35
35
  */
36
36
  function buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir) {
37
37
  const lines = [];
38
+ // vue — shallowRef needed when form, delete, or detail endpoints exist
39
+ const needsShallowRef = !!(resource.createEndpoint ||
40
+ resource.updateEndpoint ||
41
+ resource.deleteEndpoint ||
42
+ resource.detailEndpoint);
43
+ const needsComputed = !!resource.detailEndpoint;
44
+ if (needsShallowRef) {
45
+ const vueImports = needsComputed ? `shallowRef, computed` : `shallowRef`;
46
+ lines.push(`import { ${vueImports} } from 'vue';`);
47
+ lines.push('');
48
+ }
38
49
  // zod
39
50
  lines.push(`import { z } from 'zod';`);
40
51
  lines.push('');
41
- // Vue — computed needed for the detail connector wrapper
42
- if (resource.detailEndpoint) {
43
- lines.push(`import { computed } from 'vue';`);
44
- lines.push('');
45
- }
46
52
  // connector-types — structural interfaces for return types
47
53
  const connectorTypeImports = ['ListConnectorReturn'];
48
54
  if (resource.detailEndpoint) {
@@ -160,6 +166,12 @@ function buildOptionsInterface(resource) {
160
166
  if (resource.updateEndpoint && resource.zodSchemas.update) {
161
167
  fields.push(` updateSchema?: z.ZodTypeAny | ((base: z.ZodTypeAny) => z.ZodTypeAny);`);
162
168
  }
169
+ if (resource.createEndpoint || resource.updateEndpoint || resource.deleteEndpoint) {
170
+ fields.push(` onRequest?: (ctx: any) => void | Promise<void>;`);
171
+ fields.push(` onSuccess?: (data: any) => void;`);
172
+ fields.push(` onError?: (err: any) => void;`);
173
+ fields.push(` onFinish?: () => void;`);
174
+ }
163
175
  if (fields.length === 0) {
164
176
  return `type ${typeName} = Record<string, never>;`;
165
177
  }
@@ -185,11 +197,15 @@ function buildReturnType(resource) {
185
197
  fields.push(` detail: DetailConnectorReturn<${pascal}>;`);
186
198
  }
187
199
  if (resource.createEndpoint) {
188
- const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
200
+ const inputType = resource.zodSchemas.create
201
+ ? `${pascal}CreateInput`
202
+ : `Record<string, unknown>`;
189
203
  fields.push(` createForm: FormConnectorReturn<${inputType}>;`);
190
204
  }
191
205
  if (resource.updateEndpoint) {
192
- const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
206
+ const inputType = resource.zodSchemas.update
207
+ ? `${pascal}UpdateInput`
208
+ : `Record<string, unknown>`;
193
209
  fields.push(` updateForm: FormConnectorReturn<${inputType}>;`);
194
210
  }
195
211
  if (resource.deleteEndpoint) {
@@ -197,6 +213,17 @@ function buildReturnType(resource) {
197
213
  }
198
214
  return [`type ${typeName} = {`, ...fields, `};`].join('\n');
199
215
  }
216
+ /**
217
+ * Build the 3 generated lines for a composable that is keyed by a single path param
218
+ * (detail, delete). Uses computed(() => ({ param: ref.value })) so that p.value is
219
+ * always an object during setup — avoids `null.param` crash when Nuxt evaluates computedKey.
220
+ */
221
+ function buildPathParamComposableLines(prefix, fn, pathParam) {
222
+ return [
223
+ ` const ${prefix}Ref = shallowRef(null);`,
224
+ ` const ${prefix}Composable = ${fn}(computed(() => ({ ${pathParam}: ${prefix}Ref.value })) as any, { immediate: false });`,
225
+ ];
226
+ }
200
227
  /**
201
228
  * Build the body of the exported connector function.
202
229
  */
@@ -223,6 +250,12 @@ function buildFunctionBody(resource) {
223
250
  if (resource.updateEndpoint && resource.zodSchemas.update) {
224
251
  optionKeys.push('updateSchema');
225
252
  }
253
+ const hasMutations = !!(resource.createEndpoint ||
254
+ resource.updateEndpoint ||
255
+ resource.deleteEndpoint);
256
+ if (hasMutations) {
257
+ optionKeys.push('onRequest', 'onSuccess', 'onError', 'onFinish');
258
+ }
226
259
  const optionsDestructure = optionKeys.length > 0 ? ` const { ${optionKeys.join(', ')} } = options;\n` : '';
227
260
  // ── List / table sub-connector ─────────────────────────────────────────────
228
261
  if (resource.listEndpoint) {
@@ -243,23 +276,28 @@ function buildFunctionBody(resource) {
243
276
  }
244
277
  if (resource.detailEndpoint) {
245
278
  const fn = toAsyncDataName(resource.detailEndpoint.operationId);
246
- // Wrap the composable to map the generic idRef to the actual path param name.
247
- // useDetailConnector passes a ref<id> and calls refresh() after updating it.
248
279
  const pathParam = resource.detailEndpoint.pathParams[0] ?? 'id';
249
- subConnectors.push(` const detail = useDetailConnector(`, ` (idRef, opts) => ${fn}(computed(() => ({ ${pathParam}: idRef.value })) as any, opts),`, ` ) as unknown as DetailConnectorReturn<${pascal}>;`);
280
+ subConnectors.push(...buildPathParamComposableLines('_detail', fn, pathParam));
281
+ subConnectors.push(` const detail = useDetailConnector((id: any) => { _detailRef.value = id; return _detailComposable; }) as unknown as DetailConnectorReturn<${pascal}>;`);
250
282
  }
251
283
  if (resource.createEndpoint) {
252
284
  const fn = toAsyncDataName(resource.createEndpoint.operationId);
253
- const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
285
+ const inputType = resource.zodSchemas.create
286
+ ? `${pascal}CreateInput`
287
+ : `Record<string, unknown>`;
254
288
  const schemaArg = resource.zodSchemas.create
255
289
  ? `{ schema: ${pascal}CreateSchema, schemaOverride: createSchema }`
256
290
  : '{}';
257
- subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
291
+ subConnectors.push(` const _createRef = shallowRef({});`);
292
+ subConnectors.push(` const _createComposable = ${fn}(_createRef as any, { immediate: false, onRequest, onSuccess, onError, onFinish });`);
293
+ subConnectors.push(` const createForm = useFormConnector((p: any) => { _createRef.value = p; return _createComposable; }, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
258
294
  }
259
295
  if (resource.updateEndpoint) {
260
296
  const fn = toAsyncDataName(resource.updateEndpoint.operationId);
261
297
  const hasDetail = !!resource.detailEndpoint;
262
- const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
298
+ const inputType = resource.zodSchemas.update
299
+ ? `${pascal}UpdateInput`
300
+ : `Record<string, unknown>`;
263
301
  let schemaArg = '{}';
264
302
  if (resource.zodSchemas.update && hasDetail) {
265
303
  schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema, loadWith: detail }`;
@@ -270,11 +308,23 @@ function buildFunctionBody(resource) {
270
308
  else if (hasDetail) {
271
309
  schemaArg = `{ loadWith: detail }`;
272
310
  }
273
- subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
311
+ subConnectors.push(` const _updateRef = shallowRef({});`);
312
+ subConnectors.push(` const _updateComposable = ${fn}(_updateRef as any, { immediate: false, onRequest, onSuccess, onError, onFinish });`);
313
+ subConnectors.push(` const updateForm = useFormConnector((p: any) => { _updateRef.value = p; return _updateComposable; }, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
274
314
  }
275
315
  if (resource.deleteEndpoint) {
276
316
  const fn = toAsyncDataName(resource.deleteEndpoint.operationId);
277
- subConnectors.push(` const deleteAction = useDeleteConnector(${fn}) as unknown as DeleteConnectorReturn<${pascal}>;`);
317
+ const pathParam = resource.deleteEndpoint.pathParams[0];
318
+ if (pathParam) {
319
+ subConnectors.push(...buildPathParamComposableLines('_delete', fn, pathParam));
320
+ subConnectors.push(` const _deleteComposableWithHooks = ${fn}(computed(() => ({ ${pathParam}: _deleteRef.value })) as any, { immediate: false, onRequest, onSuccess, onError, onFinish });`);
321
+ subConnectors.push(` const deleteAction = useDeleteConnector((item: any) => { _deleteRef.value = item?.${pathParam} ?? item?.id ?? item; return _deleteComposableWithHooks; }) as unknown as DeleteConnectorReturn<${pascal}>;`);
322
+ }
323
+ else {
324
+ subConnectors.push(` const _deleteRef = shallowRef({});`);
325
+ subConnectors.push(` const _deleteComposable = ${fn}(_deleteRef as any, { immediate: false, onRequest, onSuccess, onError, onFinish });`);
326
+ subConnectors.push(` const deleteAction = useDeleteConnector((p: any) => { _deleteRef.value = p; return _deleteComposable; }) as unknown as DeleteConnectorReturn<${pascal}>;`);
327
+ }
278
328
  }
279
329
  // Return object — always includes table (undefined when no list + no factory)
280
330
  const returnKeys = ['table'];
@@ -36,13 +36,6 @@ function getSuccessResponseSchema(operation) {
36
36
  function isArraySchema(schema) {
37
37
  return schema.type === 'array' || schema.items !== undefined;
38
38
  }
39
- /** True when schema is a primitive scalar (string, number, integer, boolean) — not a resource */
40
- function isPrimitiveSchema(schema) {
41
- return (schema.type === 'string' ||
42
- schema.type === 'number' ||
43
- schema.type === 'integer' ||
44
- schema.type === 'boolean');
45
- }
46
39
  // ─── Request body schema ──────────────────────────────────────────────────────
47
40
  function getRequestBodySchema(operation) {
48
41
  if (!operation.requestBody?.content) {
@@ -91,11 +84,6 @@ export function detectIntent(method, path, operation) {
91
84
  if (isArraySchema(responseSchema)) {
92
85
  return 'list';
93
86
  }
94
- // Primitive response (string, number, boolean) → not a CRUD resource
95
- // e.g. GET /user/login returns a string token — not a list or detail
96
- if (isPrimitiveSchema(responseSchema)) {
97
- return 'unknown';
98
- }
99
87
  // Object response — distinguish list vs detail by path structure:
100
88
  // GET /pets/{id} → has path param → detail (single item fetch)
101
89
  // GET /pets → no path param → list (likely paginated envelope: { data: [], total: n })
@@ -134,6 +122,7 @@ export function extractEndpoints(path, pathItem) {
134
122
  intent,
135
123
  hasPathParams: pathParams.length > 0,
136
124
  pathParams,
125
+ hasQueryParams: (operation.parameters ?? []).some((p) => p.in === 'query'),
137
126
  };
138
127
  // Attach response schema for GET intents
139
128
  if (method === 'GET') {
@@ -39,7 +39,16 @@ function resolveRefs(node, root, visited = new Set()) {
39
39
  const resolved = resolvePointer(root, ref);
40
40
  const newVisited = new Set(visited);
41
41
  newVisited.add(ref);
42
- return resolveRefs(resolved, root, newVisited);
42
+ const resolvedFull = resolveRefs(resolved, root, newVisited);
43
+ // Annotate resolved schemas from #/components/schemas/Xxx with the original
44
+ // component name so downstream consumers can recover the model type name.
45
+ if (typeof resolvedFull === 'object' &&
46
+ resolvedFull !== null &&
47
+ ref.startsWith('#/components/schemas/')) {
48
+ const refName = ref.split('/').pop();
49
+ return { ...resolvedFull, 'x-ref-name': refName };
50
+ }
51
+ return resolvedFull;
43
52
  }
44
53
  const result = {};
45
54
  for (const [key, value] of Object.entries(obj)) {
@@ -85,10 +85,17 @@ export function buildResourceMap(spec) {
85
85
  ? buildZodSchema(updateEp.requestBodySchema)
86
86
  : undefined;
87
87
  const resourceName = pascalCase(tag);
88
+ // Infer the SDK model type name from the original $ref component name.
89
+ // Priority: detail response > list items > list response (may be envelope object).
90
+ const itemTypeName = detailEp?.responseSchema?.['x-ref-name'] ??
91
+ listEp?.responseSchema?.items?.['x-ref-name'] ??
92
+ listEp?.responseSchema?.['x-ref-name'] ??
93
+ undefined;
88
94
  const info = {
89
95
  name: resourceName,
90
96
  tag,
91
97
  composableName: toConnectorName(tag),
98
+ itemTypeName,
92
99
  endpoints,
93
100
  listEndpoint: listEp,
94
101
  detailEndpoint: detailEp,
@@ -132,7 +132,7 @@ function baseZodExpr(prop) {
132
132
  case 'array':
133
133
  return arrayZodExpr(prop);
134
134
  case 'object':
135
- return objectZodExpr(prop);
135
+ return 'z.record(z.string(), z.unknown())';
136
136
  default:
137
137
  // $ref already resolved, unknown type → permissive
138
138
  return 'z.unknown()';
@@ -181,27 +181,6 @@ function numberZodExpr(prop) {
181
181
  }
182
182
  return expr;
183
183
  }
184
- function objectZodExpr(prop) {
185
- const { additionalProperties } = prop;
186
- // additionalProperties: false or undefined → plain object
187
- if (!additionalProperties || additionalProperties === true) {
188
- return 'z.record(z.unknown())';
189
- }
190
- // additionalProperties has a known primitive type → typed record
191
- const valueExpr = additionalPropsZodExpr(additionalProperties);
192
- return `z.record(${valueExpr})`;
193
- }
194
- function additionalPropsZodExpr(schema) {
195
- switch (schema.type) {
196
- case 'string': return stringZodExpr(schema);
197
- case 'integer': return integerZodExpr(schema);
198
- case 'number': return numberZodExpr(schema);
199
- case 'boolean': return 'z.boolean()';
200
- case 'array': return arrayZodExpr(schema);
201
- case 'object': return objectZodExpr(schema);
202
- default: return 'z.unknown()';
203
- }
204
- }
205
184
  function arrayZodExpr(prop) {
206
185
  const itemExpr = prop.items ? baseZodExpr(prop.items) : 'z.unknown()';
207
186
  let expr = `z.array(${itemExpr})`;
@@ -27,6 +27,8 @@ export interface OpenApiPropertySchema {
27
27
  allOf?: OpenApiPropertySchema[];
28
28
  oneOf?: OpenApiPropertySchema[];
29
29
  anyOf?: OpenApiPropertySchema[];
30
+ /** Injected by the $ref resolver — original component schema name, e.g. 'Pet' */
31
+ 'x-ref-name'?: string;
30
32
  }
31
33
  export interface OpenApiSchema extends OpenApiPropertySchema {
32
34
  required?: string[];
@@ -96,6 +98,8 @@ export interface EndpointInfo {
96
98
  responseSchema?: OpenApiSchema;
97
99
  hasPathParams: boolean;
98
100
  pathParams: string[];
101
+ /** True when the operation has at least one query parameter */
102
+ hasQueryParams: boolean;
99
103
  }
100
104
  export type FieldType = 'input' | 'textarea' | 'select' | 'checkbox' | 'datepicker' | 'number';
101
105
  export interface FormFieldDef {
@@ -136,6 +140,12 @@ export interface ResourceInfo {
136
140
  updateEndpoint?: EndpointInfo;
137
141
  /** The one endpoint detected as delete (DELETE) */
138
142
  deleteEndpoint?: EndpointInfo;
143
+ /**
144
+ * Inferred item model type name (e.g. 'Pet', 'Order') derived from the
145
+ * response schema's original $ref component name. Used for SDK type imports.
146
+ * Undefined when the response type is anonymous/primitive.
147
+ */
148
+ itemTypeName?: string;
139
149
  /** Columns inferred from the list/detail response schema */
140
150
  columns: ColumnDef[];
141
151
  /** Form fields inferred from the request body schema */
@@ -0,0 +1,12 @@
1
+ import type { ConnectorGeneratorOptions } from './types.js';
2
+ import { type Logger } from '../../cli/logger.js';
3
+ /**
4
+ * Generate headless connector composables from an OpenAPI spec.
5
+ *
6
+ * Steps:
7
+ * 1. Analyze the spec → ResourceMap (Schema Analyzer)
8
+ * 2. For each resource: generate connector source, format, write
9
+ * 3. Write an index barrel file
10
+ * 4. Copy runtime helpers to the user's project
11
+ */
12
+ export declare function generateConnectors(options: ConnectorGeneratorOptions, logger?: Logger): Promise<void>;
@@ -0,0 +1,115 @@
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { fileURLToPath } from 'url';
4
+ import { format } from 'prettier';
5
+ import { analyzeSpec } from '../components/schema-analyzer/index.js';
6
+ import { generateConnectorFile, connectorFileName, generateConnectorIndexFile, } from './templates.js';
7
+ import { createClackLogger } from '../../cli/logger.js';
8
+ // Runtime files that must be copied to the user's project
9
+ const RUNTIME_FILES = [
10
+ 'connector-types.ts',
11
+ 'useGetAllConnector.ts',
12
+ 'useGetConnector.ts',
13
+ 'useCreateConnector.ts',
14
+ 'useUpdateConnector.ts',
15
+ 'useDeleteConnector.ts',
16
+ 'zod-error-merger.ts',
17
+ ];
18
+ /**
19
+ * Format TypeScript source with Prettier.
20
+ * Falls back to unformatted code on error.
21
+ */
22
+ async function formatCode(code, logger) {
23
+ try {
24
+ return await format(code, { parser: 'typescript' });
25
+ }
26
+ catch (error) {
27
+ logger.log.warn(`Prettier formatting failed: ${String(error)}`);
28
+ return code;
29
+ }
30
+ }
31
+ /**
32
+ * Generate headless connector composables from an OpenAPI spec.
33
+ *
34
+ * Steps:
35
+ * 1. Analyze the spec → ResourceMap (Schema Analyzer)
36
+ * 2. For each resource: generate connector source, format, write
37
+ * 3. Write an index barrel file
38
+ * 4. Copy runtime helpers to the user's project
39
+ */
40
+ export async function generateConnectors(options, logger = createClackLogger()) {
41
+ const spinner = logger.spinner();
42
+ const outputDir = path.resolve(options.outputDir);
43
+ const composablesRelDir = options.composablesRelDir ?? '../use-async-data';
44
+ const runtimeRelDir = options.runtimeRelDir ?? '../runtime';
45
+ // ── 1. Analyze spec ───────────────────────────────────────────────────────
46
+ spinner.start('Analyzing OpenAPI spec');
47
+ const resourceMap = analyzeSpec(options.inputSpec);
48
+ spinner.stop(`Found ${resourceMap.size} resource(s)`);
49
+ if (resourceMap.size === 0) {
50
+ logger.log.warn('No resources found in spec — nothing to generate');
51
+ return;
52
+ }
53
+ // ── 2. Prepare output directory ───────────────────────────────────────────
54
+ // emptyDir (not ensureDir) so stale connectors from previous runs are removed.
55
+ spinner.start('Preparing output directory');
56
+ await fs.emptyDir(outputDir);
57
+ spinner.stop('Output directory ready');
58
+ // ── 3. Generate connector files ───────────────────────────────────────────
59
+ spinner.start('Generating connector composables');
60
+ let successCount = 0;
61
+ let errorCount = 0;
62
+ const generatedNames = [];
63
+ for (const resource of resourceMap.values()) {
64
+ try {
65
+ const code = generateConnectorFile(resource, composablesRelDir, '../..', runtimeRelDir);
66
+ const formatted = await formatCode(code, logger);
67
+ const fileName = connectorFileName(resource.composableName);
68
+ const filePath = path.join(outputDir, fileName);
69
+ await fs.writeFile(filePath, formatted, 'utf-8');
70
+ generatedNames.push(resource.composableName);
71
+ successCount++;
72
+ }
73
+ catch (error) {
74
+ logger.log.error(`Error generating ${resource.composableName}: ${String(error)}`);
75
+ errorCount++;
76
+ }
77
+ }
78
+ spinner.stop(`Generated ${successCount} connector(s)`);
79
+ // ── 4. Write barrel index ─────────────────────────────────────────────────
80
+ if (generatedNames.length > 0) {
81
+ try {
82
+ const indexCode = generateConnectorIndexFile(generatedNames);
83
+ const formattedIndex = await formatCode(indexCode, logger);
84
+ await fs.writeFile(path.join(outputDir, 'index.ts'), formattedIndex, 'utf-8');
85
+ }
86
+ catch (error) {
87
+ logger.log.warn(`Could not write connector index: ${String(error)}`);
88
+ }
89
+ }
90
+ // ── 5. Copy runtime helpers ───────────────────────────────────────────────
91
+ // Runtime files live in src/ and must be physical .ts files in the user's project
92
+ // so Nuxt/Vite can type-check them.
93
+ //
94
+ // Path resolution trick:
95
+ // • During development (ts-node / tsx): __dirname ≈ src/generators/connectors/
96
+ // • After `tsc` build: __dirname ≈ dist/generators/connectors/
97
+ //
98
+ // In both cases we step up 3 levels and re-enter src/ to find the runtime sources.
99
+ spinner.start('Copying runtime files');
100
+ const runtimeDir = path.resolve(outputDir, runtimeRelDir);
101
+ await fs.ensureDir(runtimeDir); // ensureDir — other runtime files may live here too
102
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
103
+ const runtimeSrcDir = path.resolve(__dirname, '../../../src/generators/connectors/runtime');
104
+ for (const file of RUNTIME_FILES) {
105
+ const src = path.join(runtimeSrcDir, file);
106
+ const dest = path.join(runtimeDir, file);
107
+ await fs.copyFile(src, dest);
108
+ }
109
+ spinner.stop('Runtime files copied');
110
+ // ── 6. Summary ────────────────────────────────────────────────────────────
111
+ if (errorCount > 0) {
112
+ logger.log.warn(`Completed with ${errorCount} error(s)`);
113
+ }
114
+ logger.log.success(`Generated ${successCount} connector(s) in ${outputDir}`);
115
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * connector-types.ts — Structural return type interfaces for the new connector system.
3
+ *
4
+ * Uses locally-defined minimal type aliases for Ref/ComputedRef so this file compiles
5
+ * in the CLI context (where Vue is not installed) and remains structurally compatible
6
+ * with Vue's actual types in the user's Nuxt project.
7
+ *
8
+ * Copied to the user's project alongside the generated connectors and runtime helpers.
9
+ */
10
+ type Ref<T> = {
11
+ value: T;
12
+ };
13
+ type ComputedRef<T> = {
14
+ readonly value: T;
15
+ };
16
+ /** Operation name passed as context to connector-level callbacks. */
17
+ export type ConnectorOperation = 'create' | 'update' | 'delete' | 'get' | 'getAll';
18
+ export interface ConnectorCallbackContext {
19
+ operation: ConnectorOperation;
20
+ }
21
+ export interface ColumnDef {
22
+ key: string;
23
+ label: string;
24
+ type: string;
25
+ }
26
+ export interface FormFieldDef {
27
+ key: string;
28
+ label: string;
29
+ type: string;
30
+ required: boolean;
31
+ options?: {
32
+ label: string;
33
+ value: string;
34
+ }[];
35
+ placeholder?: string;
36
+ hidden?: boolean;
37
+ }
38
+ export interface PaginationState {
39
+ currentPage: number;
40
+ perPage: number;
41
+ total: number;
42
+ totalPages: number;
43
+ hasNextPage: boolean;
44
+ hasPrevPage: boolean;
45
+ goToPage: (page: number) => void;
46
+ nextPage: () => void;
47
+ prevPage: () => void;
48
+ setPerPage: (n: number) => void;
49
+ }
50
+ export interface ConnectorUi {
51
+ isOpen: Ref<boolean>;
52
+ open: (...args: any[]) => void;
53
+ close: () => void;
54
+ }
55
+ export interface GetAllConnectorReturn<TRow = unknown> {
56
+ items: ComputedRef<TRow[]>;
57
+ columns: ComputedRef<ColumnDef[]>;
58
+ loading: ComputedRef<boolean>;
59
+ error: ComputedRef<unknown>;
60
+ pagination: ComputedRef<PaginationState | null>;
61
+ goToPage: (page: number) => void;
62
+ nextPage: () => void;
63
+ prevPage: () => void;
64
+ setPerPage: (n: number) => void;
65
+ selected: Ref<TRow[]>;
66
+ select: (item: TRow) => void;
67
+ deselect: (item: TRow) => void;
68
+ toggleSelect: (item: TRow) => void;
69
+ clearSelection: () => void;
70
+ load: (params?: unknown) => Promise<void>;
71
+ onSuccess: Ref<((items: TRow[]) => void) | null>;
72
+ onError: Ref<((err: unknown) => void) | null>;
73
+ }
74
+ export interface GetConnectorReturn<TItem = unknown> {
75
+ data: Ref<TItem | null>;
76
+ loading: Ref<boolean>;
77
+ error: Ref<unknown>;
78
+ fields: ComputedRef<FormFieldDef[]>;
79
+ load: (id: string | number) => Promise<TItem>;
80
+ clear: () => void;
81
+ onSuccess: (fn: (item: TItem) => void) => void;
82
+ onError: (fn: (err: unknown) => void) => void;
83
+ }
84
+ export interface CreateConnectorReturn<TInput = Record<string, unknown>, TOutput = TInput> {
85
+ model: Ref<Partial<TInput>>;
86
+ errors: Ref<Record<string, string[]>>;
87
+ loading: Ref<boolean>;
88
+ error: Ref<unknown>;
89
+ submitted: Ref<boolean>;
90
+ isValid: ComputedRef<boolean>;
91
+ hasErrors: ComputedRef<boolean>;
92
+ fields: ComputedRef<FormFieldDef[]>;
93
+ execute: (data?: Partial<TInput>) => Promise<TOutput | undefined>;
94
+ refresh: (data?: Partial<TInput>) => Promise<TOutput | undefined>;
95
+ reset: () => void;
96
+ setValues: (data: Partial<TInput>) => void;
97
+ setField: (key: keyof TInput, value: unknown) => void;
98
+ onSuccess: (fn: (data: TOutput) => void) => void;
99
+ onError: (fn: (err: unknown) => void) => void;
100
+ ui: {
101
+ isOpen: Ref<boolean>;
102
+ open: () => void;
103
+ close: () => void;
104
+ };
105
+ }
106
+ export interface UpdateConnectorReturn<TInput = Record<string, unknown>, TOutput = TInput> {
107
+ model: Ref<Partial<TInput>>;
108
+ errors: Ref<Record<string, string[]>>;
109
+ loading: Ref<boolean>;
110
+ error: Ref<unknown>;
111
+ submitted: Ref<boolean>;
112
+ isValid: ComputedRef<boolean>;
113
+ hasErrors: ComputedRef<boolean>;
114
+ fields: ComputedRef<FormFieldDef[]>;
115
+ targetId: Ref<string | number | null>;
116
+ load: (id: string | number) => Promise<void>;
117
+ execute: (id: string | number, data?: Partial<TInput>) => Promise<TOutput | undefined>;
118
+ refresh: (id: string | number, data?: Partial<TInput>) => Promise<TOutput | undefined>;
119
+ reset: () => void;
120
+ setValues: (data: Partial<TInput>) => void;
121
+ setField: (key: keyof TInput, value: unknown) => void;
122
+ onSuccess: (fn: (data: TOutput) => void) => void;
123
+ onError: (fn: (err: unknown) => void) => void;
124
+ ui: {
125
+ isOpen: Ref<boolean>;
126
+ open: (item?: Partial<TInput> & Record<string, unknown>) => void;
127
+ close: () => void;
128
+ };
129
+ }
130
+ export interface DeleteConnectorReturn<TItem = unknown> {
131
+ staged: Ref<TItem | null>;
132
+ hasStaged: ComputedRef<boolean>;
133
+ loading: Ref<boolean>;
134
+ error: Ref<unknown>;
135
+ stage: (item: TItem) => void;
136
+ cancel: () => void;
137
+ execute: (item?: TItem) => Promise<void>;
138
+ refresh: (item?: TItem) => Promise<void>;
139
+ onSuccess: (fn: (item: TItem) => void) => void;
140
+ onError: (fn: (err: unknown) => void) => void;
141
+ ui: {
142
+ isOpen: Ref<boolean>;
143
+ open: (item: TItem) => void;
144
+ close: () => void;
145
+ };
146
+ }
147
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * connector-types.ts — Structural return type interfaces for the new connector system.
3
+ *
4
+ * Uses locally-defined minimal type aliases for Ref/ComputedRef so this file compiles
5
+ * in the CLI context (where Vue is not installed) and remains structurally compatible
6
+ * with Vue's actual types in the user's Nuxt project.
7
+ *
8
+ * Copied to the user's project alongside the generated connectors and runtime helpers.
9
+ */
10
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @param url The endpoint URL string. e.g. '/pet'
3
+ * @param options Configuration: schema, fields, method, baseURL, callbacks, etc.
4
+ */
5
+ export declare function useCreateConnector(url: any, options?: {}): {
6
+ model: any;
7
+ errors: any;
8
+ loading: any;
9
+ error: any;
10
+ submitted: any;
11
+ isValid: any;
12
+ hasErrors: any;
13
+ fields: any;
14
+ execute: (data: any) => Promise<any>;
15
+ refresh: (data: any) => Promise<any>;
16
+ reset: () => void;
17
+ setValues: (data: any) => void;
18
+ setField: (key: any, value: any) => void;
19
+ onSuccess: (fn: any) => void;
20
+ onError: (fn: any) => void;
21
+ ui: {
22
+ isOpen: any;
23
+ open(): void;
24
+ close(): void;
25
+ };
26
+ };