nuxt-openapi-hyperfetch 0.3.0-beta → 0.3.2-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/dist/generators/components/connector-generator/generator.js +2 -1
- package/dist/generators/components/connector-generator/templates.d.ts +1 -1
- package/dist/generators/components/connector-generator/templates.js +126 -44
- package/dist/generators/shared/runtime/connector-types.d.ts +104 -0
- package/dist/generators/shared/runtime/connector-types.js +10 -0
- package/dist/generators/shared/runtime/useListConnector.d.ts +5 -3
- package/dist/generators/shared/runtime/useListConnector.js +6 -4
- package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +8 -2
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +9 -3
- package/dist/generators/use-async-data/templates.js +24 -8
- package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +9 -2
- package/dist/generators/use-fetch/templates.js +9 -5
- package/dist/module/index.js +0 -3
- package/package.json +1 -1
- package/src/generators/components/connector-generator/generator.ts +2 -1
- package/src/generators/components/connector-generator/templates.ts +159 -44
- package/src/generators/shared/runtime/connector-types.ts +142 -0
- package/src/generators/shared/runtime/useListConnector.ts +6 -4
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +33 -5
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +30 -8
- package/src/generators/use-async-data/templates.ts +24 -9
- package/src/generators/use-fetch/runtime/useApiRequest.ts +34 -4
- package/src/generators/use-fetch/templates.ts +9 -6
- package/src/module/index.ts +0 -3
|
@@ -7,6 +7,7 @@ import { generateConnectorFile, connectorFileName, generateConnectorIndexFile, }
|
|
|
7
7
|
import { createClackLogger } from '../../../cli/logger.js';
|
|
8
8
|
// Runtime files that must be copied to the user's project
|
|
9
9
|
const RUNTIME_FILES = [
|
|
10
|
+
'connector-types.ts',
|
|
10
11
|
'useListConnector.ts',
|
|
11
12
|
'useDetailConnector.ts',
|
|
12
13
|
'useFormConnector.ts',
|
|
@@ -61,7 +62,7 @@ export async function generateConnectors(options, logger = createClackLogger())
|
|
|
61
62
|
const generatedNames = [];
|
|
62
63
|
for (const resource of resourceMap.values()) {
|
|
63
64
|
try {
|
|
64
|
-
const code = generateConnectorFile(resource, composablesRelDir);
|
|
65
|
+
const code = generateConnectorFile(resource, composablesRelDir, '../..', runtimeRelDir);
|
|
65
66
|
const formatted = await formatCode(code, logger);
|
|
66
67
|
const fileName = connectorFileName(resource.composableName);
|
|
67
68
|
const filePath = path.join(outputDir, fileName);
|
|
@@ -6,7 +6,7 @@ import type { ResourceInfo } from '../schema-analyzer/types.js';
|
|
|
6
6
|
* @param composablesRelDir Relative path from the connector dir to the
|
|
7
7
|
* useAsyncData composables dir (e.g. '../use-async-data')
|
|
8
8
|
*/
|
|
9
|
-
export declare function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string): string;
|
|
9
|
+
export declare function generateConnectorFile(resource: ResourceInfo, composablesRelDir: string, sdkRelDir?: string, runtimeRelDir?: string): string;
|
|
10
10
|
/**
|
|
11
11
|
* Derive the output filename for a connector.
|
|
12
12
|
* 'usePetsConnector' → 'use-pets-connector.ts'
|
|
@@ -12,7 +12,6 @@ function generateFileHeader() {
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/* eslint-disable */
|
|
15
|
-
// @ts-nocheck
|
|
16
15
|
`;
|
|
17
16
|
}
|
|
18
17
|
// ─── Naming helpers ───────────────────────────────────────────────────────────
|
|
@@ -34,16 +33,33 @@ function toFileName(composableName) {
|
|
|
34
33
|
/**
|
|
35
34
|
* Build all `import` lines for a resource connector.
|
|
36
35
|
*/
|
|
37
|
-
function buildImports(resource, composablesRelDir) {
|
|
36
|
+
function buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir) {
|
|
38
37
|
const lines = [];
|
|
39
38
|
// zod
|
|
40
39
|
lines.push(`import { z } from 'zod';`);
|
|
41
40
|
lines.push('');
|
|
42
|
-
//
|
|
43
|
-
const
|
|
41
|
+
// connector-types — structural interfaces for return types
|
|
42
|
+
const connectorTypeImports = ['ListConnectorReturn'];
|
|
43
|
+
if (resource.detailEndpoint) {
|
|
44
|
+
connectorTypeImports.push('DetailConnectorReturn');
|
|
45
|
+
}
|
|
46
|
+
if (resource.createEndpoint || resource.updateEndpoint) {
|
|
47
|
+
connectorTypeImports.push('FormConnectorReturn');
|
|
48
|
+
}
|
|
49
|
+
if (resource.deleteEndpoint) {
|
|
50
|
+
connectorTypeImports.push('DeleteConnectorReturn');
|
|
51
|
+
}
|
|
52
|
+
lines.push(`import type { ${connectorTypeImports.join(', ')} } from '${runtimeRelDir}/connector-types';`);
|
|
53
|
+
lines.push('');
|
|
54
|
+
// SDK request/response types (for the params overload signature)
|
|
44
55
|
if (resource.listEndpoint) {
|
|
45
|
-
|
|
56
|
+
const requestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
|
|
57
|
+
lines.push(`import type { ${requestTypeName} } from '${sdkRelDir}';`);
|
|
58
|
+
lines.push('');
|
|
46
59
|
}
|
|
60
|
+
// runtime helpers — relative path works in both CLI and Nuxt module contexts
|
|
61
|
+
// useListConnector is always imported to support the optional factory pattern
|
|
62
|
+
const runtimeHelpers = ['useListConnector'];
|
|
47
63
|
if (resource.detailEndpoint) {
|
|
48
64
|
runtimeHelpers.push('useDetailConnector');
|
|
49
65
|
}
|
|
@@ -54,7 +70,7 @@ function buildImports(resource, composablesRelDir) {
|
|
|
54
70
|
runtimeHelpers.push('useDeleteConnector');
|
|
55
71
|
}
|
|
56
72
|
for (const helper of runtimeHelpers) {
|
|
57
|
-
lines.push(`import { ${helper} } from '
|
|
73
|
+
lines.push(`import { ${helper} } from '${runtimeRelDir}/${helper}';`);
|
|
58
74
|
}
|
|
59
75
|
lines.push('');
|
|
60
76
|
// generated useAsyncData composables
|
|
@@ -121,6 +137,61 @@ function buildColumns(resource) {
|
|
|
121
137
|
.join(',\n');
|
|
122
138
|
return `const ${varName} = [\n${entries},\n];`;
|
|
123
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Build the TypeScript options interface for a connector.
|
|
142
|
+
* Only includes fields relevant to the endpoints present on the resource.
|
|
143
|
+
*/
|
|
144
|
+
function buildOptionsInterface(resource) {
|
|
145
|
+
const typeName = `${pascalCase(resource.composableName)}Options`;
|
|
146
|
+
const hasColumns = resource.columns && resource.columns.length > 0;
|
|
147
|
+
const fields = [];
|
|
148
|
+
if (resource.listEndpoint && hasColumns) {
|
|
149
|
+
fields.push(` columnLabels?: Record<string, string>;`);
|
|
150
|
+
fields.push(` columnLabel?: (key: string) => string;`);
|
|
151
|
+
}
|
|
152
|
+
if (resource.createEndpoint && resource.zodSchemas.create) {
|
|
153
|
+
fields.push(` createSchema?: z.ZodTypeAny | ((base: z.ZodTypeAny) => z.ZodTypeAny);`);
|
|
154
|
+
}
|
|
155
|
+
if (resource.updateEndpoint && resource.zodSchemas.update) {
|
|
156
|
+
fields.push(` updateSchema?: z.ZodTypeAny | ((base: z.ZodTypeAny) => z.ZodTypeAny);`);
|
|
157
|
+
}
|
|
158
|
+
if (fields.length === 0) {
|
|
159
|
+
return `type ${typeName} = Record<string, never>;`;
|
|
160
|
+
}
|
|
161
|
+
return [`interface ${typeName} {`, ...fields, `}`].join('\n');
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the TypeScript return type for a connector.
|
|
165
|
+
*/
|
|
166
|
+
function buildReturnType(resource) {
|
|
167
|
+
const pascal = pascalCase(resource.name);
|
|
168
|
+
const typeName = `${pascalCase(resource.composableName)}Return`;
|
|
169
|
+
const fields = [];
|
|
170
|
+
// table is always present in the return type:
|
|
171
|
+
// - if listEndpoint exists → ListConnectorReturn<T> (always defined)
|
|
172
|
+
// - if no listEndpoint → ListConnectorReturn<unknown> | undefined (only when factory passed)
|
|
173
|
+
if (resource.listEndpoint) {
|
|
174
|
+
fields.push(` table: ListConnectorReturn<${pascal}>;`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
fields.push(` table: ListConnectorReturn<unknown> | undefined;`);
|
|
178
|
+
}
|
|
179
|
+
if (resource.detailEndpoint) {
|
|
180
|
+
fields.push(` detail: DetailConnectorReturn<${pascal}>;`);
|
|
181
|
+
}
|
|
182
|
+
if (resource.createEndpoint) {
|
|
183
|
+
const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
|
|
184
|
+
fields.push(` createForm: FormConnectorReturn<${inputType}>;`);
|
|
185
|
+
}
|
|
186
|
+
if (resource.updateEndpoint) {
|
|
187
|
+
const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
|
|
188
|
+
fields.push(` updateForm: FormConnectorReturn<${inputType}>;`);
|
|
189
|
+
}
|
|
190
|
+
if (resource.deleteEndpoint) {
|
|
191
|
+
fields.push(` deleteAction: DeleteConnectorReturn<${pascal}>;`);
|
|
192
|
+
}
|
|
193
|
+
return [`type ${typeName} = {`, ...fields, `};`].join('\n');
|
|
194
|
+
}
|
|
124
195
|
/**
|
|
125
196
|
* Build the body of the exported connector function.
|
|
126
197
|
*/
|
|
@@ -133,6 +204,9 @@ function buildFunctionBody(resource) {
|
|
|
133
204
|
.replace(/^./, (c) => c.toLowerCase());
|
|
134
205
|
const columnsVar = `${camel}Columns`;
|
|
135
206
|
const subConnectors = [];
|
|
207
|
+
// Derived type names — must match buildOptionsInterface / buildReturnType
|
|
208
|
+
const optionsTypeName = `${pascalCase(resource.composableName)}Options`;
|
|
209
|
+
const returnTypeName = `${pascalCase(resource.composableName)}Return`;
|
|
136
210
|
// Destructure options param — only what's relevant for this resource
|
|
137
211
|
const optionKeys = [];
|
|
138
212
|
if (resource.listEndpoint && hasColumns) {
|
|
@@ -145,63 +219,57 @@ function buildFunctionBody(resource) {
|
|
|
145
219
|
optionKeys.push('updateSchema');
|
|
146
220
|
}
|
|
147
221
|
const optionsDestructure = optionKeys.length > 0 ? ` const { ${optionKeys.join(', ')} } = options;\n` : '';
|
|
222
|
+
// ── List / table sub-connector ─────────────────────────────────────────────
|
|
148
223
|
if (resource.listEndpoint) {
|
|
149
224
|
const fn = toAsyncDataName(resource.listEndpoint.operationId);
|
|
150
|
-
|
|
151
|
-
// (goToPage, nextPage, prevPage, setPerPage, pagination ref).
|
|
152
|
-
// We set it whenever the spec declares a list endpoint that has a response schema,
|
|
153
|
-
// which is a reliable proxy for "this API returns structured data worth paginating".
|
|
225
|
+
const listRequestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
|
|
154
226
|
const paginatedFlag = resource.listEndpoint.responseSchema ? 'paginated: true' : '';
|
|
155
227
|
const columnsArg = hasColumns ? `columns: ${columnsVar}` : '';
|
|
156
228
|
const labelArgs = hasColumns ? 'columnLabels, columnLabel' : '';
|
|
157
229
|
const allArgs = [paginatedFlag, columnsArg, labelArgs].filter(Boolean).join(', ');
|
|
158
230
|
const opts = allArgs ? `{ ${allArgs} }` : '{}';
|
|
159
|
-
|
|
231
|
+
// Factory: if the first arg is a function the user provided their own composable;
|
|
232
|
+
// otherwise build a default factory from the plain params object.
|
|
233
|
+
subConnectors.push(` const isFactory = typeof paramsOrSource === 'function';`, ` const listFactory = isFactory`, ` ? (paramsOrSource as () => unknown)`, ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName});`, ` const table = useListConnector(listFactory, ${opts}) as unknown as ListConnectorReturn<${pascal}>;`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// No list endpoint — support optional factory for developer-provided list
|
|
237
|
+
subConnectors.push(` const table = paramsOrSource`, ` ? (useListConnector(paramsOrSource as () => unknown, {}) as unknown as ListConnectorReturn<unknown>)`, ` : undefined;`);
|
|
160
238
|
}
|
|
161
239
|
if (resource.detailEndpoint) {
|
|
162
240
|
const fn = toAsyncDataName(resource.detailEndpoint.operationId);
|
|
163
|
-
subConnectors.push(` const detail = useDetailConnector(${fn})
|
|
241
|
+
subConnectors.push(` const detail = useDetailConnector(${fn}) as unknown as DetailConnectorReturn<${pascal}>;`);
|
|
164
242
|
}
|
|
165
243
|
if (resource.createEndpoint) {
|
|
166
244
|
const fn = toAsyncDataName(resource.createEndpoint.operationId);
|
|
245
|
+
const inputType = resource.zodSchemas.create ? `${pascal}CreateInput` : `Record<string, unknown>`;
|
|
167
246
|
const schemaArg = resource.zodSchemas.create
|
|
168
247
|
? `{ schema: ${pascal}CreateSchema, schemaOverride: createSchema }`
|
|
169
248
|
: '{}';
|
|
170
|
-
subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg})
|
|
249
|
+
subConnectors.push(` const createForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
|
|
171
250
|
}
|
|
172
251
|
if (resource.updateEndpoint) {
|
|
173
252
|
const fn = toAsyncDataName(resource.updateEndpoint.operationId);
|
|
174
253
|
const hasDetail = !!resource.detailEndpoint;
|
|
175
|
-
|
|
176
|
-
// schema → Zod schema for client-side validation before submission
|
|
177
|
-
// loadWith → reference to the detail connector so the form auto-fills
|
|
178
|
-
// when detail.item changes (user clicks "Edit" on a row)
|
|
179
|
-
//
|
|
180
|
-
// Four combinations are possible depending on what the spec provides:
|
|
254
|
+
const inputType = resource.zodSchemas.update ? `${pascal}UpdateInput` : `Record<string, unknown>`;
|
|
181
255
|
let schemaArg = '{}';
|
|
182
256
|
if (resource.zodSchemas.update && hasDetail) {
|
|
183
|
-
// Best case: validate AND pre-fill from detail
|
|
184
257
|
schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema, loadWith: detail }`;
|
|
185
258
|
}
|
|
186
259
|
else if (resource.zodSchemas.update) {
|
|
187
|
-
// Validate, but no detail endpoint to pre-fill from
|
|
188
260
|
schemaArg = `{ schema: ${pascal}UpdateSchema, schemaOverride: updateSchema }`;
|
|
189
261
|
}
|
|
190
262
|
else if (hasDetail) {
|
|
191
|
-
// No Zod schema (no request body in spec), but still pre-fill from detail
|
|
192
263
|
schemaArg = `{ loadWith: detail }`;
|
|
193
264
|
}
|
|
194
|
-
subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg})
|
|
265
|
+
subConnectors.push(` const updateForm = useFormConnector(${fn}, ${schemaArg}) as unknown as FormConnectorReturn<${inputType}>;`);
|
|
195
266
|
}
|
|
196
267
|
if (resource.deleteEndpoint) {
|
|
197
268
|
const fn = toAsyncDataName(resource.deleteEndpoint.operationId);
|
|
198
|
-
subConnectors.push(` const deleteAction = useDeleteConnector(${fn})
|
|
199
|
-
}
|
|
200
|
-
// Return object — only include what was built
|
|
201
|
-
const returnKeys = [];
|
|
202
|
-
if (resource.listEndpoint) {
|
|
203
|
-
returnKeys.push('table');
|
|
269
|
+
subConnectors.push(` const deleteAction = useDeleteConnector(${fn}) as unknown as DeleteConnectorReturn<${pascal}>;`);
|
|
204
270
|
}
|
|
271
|
+
// Return object — always includes table (undefined when no list + no factory)
|
|
272
|
+
const returnKeys = ['table'];
|
|
205
273
|
if (resource.detailEndpoint) {
|
|
206
274
|
returnKeys.push('detail');
|
|
207
275
|
}
|
|
@@ -214,16 +282,27 @@ function buildFunctionBody(resource) {
|
|
|
214
282
|
if (resource.deleteEndpoint) {
|
|
215
283
|
returnKeys.push('deleteAction');
|
|
216
284
|
}
|
|
217
|
-
const returnStatement = ` return { ${returnKeys.join(', ')} };`;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
285
|
+
const returnStatement = ` return { ${returnKeys.join(', ')} } as ${returnTypeName};`;
|
|
286
|
+
// ── Function signature ─────────────────────────────────────────────────────
|
|
287
|
+
// Resources WITH a list endpoint: two overloads (factory | params).
|
|
288
|
+
// Resources WITHOUT a list endpoint: single signature with optional factory.
|
|
289
|
+
const lines = [];
|
|
290
|
+
if (resource.listEndpoint) {
|
|
291
|
+
const listRequestTypeName = `${pascalCase(resource.listEndpoint.operationId)}Request`;
|
|
292
|
+
lines.push(`export function ${resource.composableName}(source: () => unknown, options?: ${optionsTypeName}): ${returnTypeName};`, `export function ${resource.composableName}(params?: ${listRequestTypeName}, options?: ${optionsTypeName}): ${returnTypeName};`, `export function ${resource.composableName}(paramsOrSource?: ${listRequestTypeName} | (() => unknown), options: ${optionsTypeName} = {}): ${returnTypeName} {`);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
lines.push(`export function ${resource.composableName}(source?: () => unknown, options: ${optionsTypeName} = {}): ${returnTypeName} {`);
|
|
296
|
+
// Alias so the body can use paramsOrSource uniformly
|
|
297
|
+
lines.push(` const paramsOrSource = source;`);
|
|
298
|
+
}
|
|
299
|
+
if (optionsDestructure.trim()) {
|
|
300
|
+
lines.push(optionsDestructure.trimEnd());
|
|
301
|
+
}
|
|
302
|
+
lines.push(...subConnectors);
|
|
303
|
+
lines.push(returnStatement);
|
|
304
|
+
lines.push(`}`);
|
|
305
|
+
return lines.join('\n');
|
|
227
306
|
}
|
|
228
307
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
229
308
|
/**
|
|
@@ -233,15 +312,16 @@ function buildFunctionBody(resource) {
|
|
|
233
312
|
* @param composablesRelDir Relative path from the connector dir to the
|
|
234
313
|
* useAsyncData composables dir (e.g. '../use-async-data')
|
|
235
314
|
*/
|
|
236
|
-
export function generateConnectorFile(resource, composablesRelDir) {
|
|
315
|
+
export function generateConnectorFile(resource, composablesRelDir, sdkRelDir = '../..', runtimeRelDir = '../../runtime') {
|
|
237
316
|
const header = generateFileHeader();
|
|
238
|
-
const imports = buildImports(resource, composablesRelDir);
|
|
317
|
+
const imports = buildImports(resource, composablesRelDir, sdkRelDir, runtimeRelDir);
|
|
239
318
|
const schemas = buildZodSchemas(resource);
|
|
240
319
|
const columns = buildColumns(resource);
|
|
320
|
+
const optionsInterface = buildOptionsInterface(resource);
|
|
321
|
+
const returnType = buildReturnType(resource);
|
|
241
322
|
const fn = buildFunctionBody(resource);
|
|
242
|
-
// Assemble file: header + imports + (optional) Zod blocks + columns const +
|
|
243
|
-
//
|
|
244
|
-
// line between sections, which matches Prettier's output for this structure.
|
|
323
|
+
// Assemble file: header + imports + (optional) Zod blocks + columns const +
|
|
324
|
+
// options interface + return type + function body.
|
|
245
325
|
const parts = [header, imports];
|
|
246
326
|
if (schemas.trim()) {
|
|
247
327
|
parts.push(schemas);
|
|
@@ -249,6 +329,8 @@ export function generateConnectorFile(resource, composablesRelDir) {
|
|
|
249
329
|
if (columns.trim()) {
|
|
250
330
|
parts.push(columns);
|
|
251
331
|
}
|
|
332
|
+
parts.push(optionsInterface);
|
|
333
|
+
parts.push(returnType);
|
|
252
334
|
parts.push(fn);
|
|
253
335
|
return parts.join('\n') + '\n';
|
|
254
336
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connector-types.ts — Structural return type interfaces for the 4 runtime connectors.
|
|
3
|
+
*
|
|
4
|
+
* Uses locally-defined minimal type aliases for Ref/ComputedRef/ShallowRef so this
|
|
5
|
+
* file compiles in the CLI context (where Vue is not installed) and remains
|
|
6
|
+
* structurally compatible 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 ShallowRef<T> = {
|
|
14
|
+
value: T;
|
|
15
|
+
};
|
|
16
|
+
type ComputedRef<T> = {
|
|
17
|
+
readonly value: T;
|
|
18
|
+
};
|
|
19
|
+
export interface ColumnDef {
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
type: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FormFieldDef {
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
type: string;
|
|
28
|
+
required: boolean;
|
|
29
|
+
options?: {
|
|
30
|
+
label: string;
|
|
31
|
+
value: string;
|
|
32
|
+
}[];
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
hidden?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface PaginationState {
|
|
37
|
+
page: number;
|
|
38
|
+
perPage: number;
|
|
39
|
+
total: number;
|
|
40
|
+
totalPages: number;
|
|
41
|
+
goToPage: (page: number) => void;
|
|
42
|
+
nextPage: () => void;
|
|
43
|
+
prevPage: () => void;
|
|
44
|
+
setPerPage: (n: number) => void;
|
|
45
|
+
}
|
|
46
|
+
export interface ListConnectorReturn<TRow = unknown> {
|
|
47
|
+
rows: ComputedRef<TRow[]>;
|
|
48
|
+
columns: ComputedRef<ColumnDef[]>;
|
|
49
|
+
loading: ComputedRef<boolean>;
|
|
50
|
+
error: ComputedRef<unknown>;
|
|
51
|
+
pagination: ComputedRef<PaginationState | null>;
|
|
52
|
+
goToPage: (page: number) => void;
|
|
53
|
+
nextPage: () => void;
|
|
54
|
+
prevPage: () => void;
|
|
55
|
+
setPerPage: (n: number) => void;
|
|
56
|
+
selected: Ref<TRow[]>;
|
|
57
|
+
onRowSelect: (row: TRow) => void;
|
|
58
|
+
clearSelection: () => void;
|
|
59
|
+
refresh: () => void;
|
|
60
|
+
create: () => void;
|
|
61
|
+
update: (row: TRow) => void;
|
|
62
|
+
remove: (row: TRow) => void;
|
|
63
|
+
_createTrigger: Ref<number>;
|
|
64
|
+
_updateTarget: ShallowRef<TRow | null>;
|
|
65
|
+
_deleteTarget: ShallowRef<TRow | null>;
|
|
66
|
+
}
|
|
67
|
+
export interface DetailConnectorReturn<TItem = unknown> {
|
|
68
|
+
item: ComputedRef<TItem | null>;
|
|
69
|
+
loading: ComputedRef<boolean>;
|
|
70
|
+
error: ComputedRef<unknown>;
|
|
71
|
+
fields: ComputedRef<FormFieldDef[]>;
|
|
72
|
+
load: (id: string | number) => Promise<void>;
|
|
73
|
+
clear: () => void;
|
|
74
|
+
_composable: unknown;
|
|
75
|
+
_currentId: Ref<string | number | null>;
|
|
76
|
+
}
|
|
77
|
+
export interface FormConnectorReturn<TInput = Record<string, unknown>> {
|
|
78
|
+
model: Ref<Partial<TInput>>;
|
|
79
|
+
errors: Ref<Record<string, string[]>>;
|
|
80
|
+
loading: Ref<boolean>;
|
|
81
|
+
submitError: Ref<unknown>;
|
|
82
|
+
submitted: Ref<boolean>;
|
|
83
|
+
isValid: ComputedRef<boolean>;
|
|
84
|
+
hasErrors: ComputedRef<boolean>;
|
|
85
|
+
fields: ComputedRef<FormFieldDef[]>;
|
|
86
|
+
onSuccess: Ref<((data: unknown) => void) | null>;
|
|
87
|
+
onError: Ref<((err: unknown) => void) | null>;
|
|
88
|
+
submit: () => Promise<void>;
|
|
89
|
+
reset: () => void;
|
|
90
|
+
setValues: (data: Partial<TInput>) => void;
|
|
91
|
+
}
|
|
92
|
+
export interface DeleteConnectorReturn<TItem = unknown> {
|
|
93
|
+
target: Ref<TItem | null>;
|
|
94
|
+
isOpen: Ref<boolean>;
|
|
95
|
+
loading: Ref<boolean>;
|
|
96
|
+
error: Ref<unknown>;
|
|
97
|
+
hasTarget: ComputedRef<boolean>;
|
|
98
|
+
onSuccess: Ref<((item: TItem) => void) | null>;
|
|
99
|
+
onError: Ref<((err: unknown) => void) | null>;
|
|
100
|
+
setTarget: (item: TItem) => void;
|
|
101
|
+
cancel: () => void;
|
|
102
|
+
confirm: () => Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connector-types.ts — Structural return type interfaces for the 4 runtime connectors.
|
|
3
|
+
*
|
|
4
|
+
* Uses locally-defined minimal type aliases for Ref/ComputedRef/ShallowRef so this
|
|
5
|
+
* file compiles in the CLI context (where Vue is not installed) and remains
|
|
6
|
+
* structurally compatible 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 {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param
|
|
3
|
-
*
|
|
2
|
+
* @param factory A zero-argument function that calls and returns the underlying
|
|
3
|
+
* useAsyncData composable, e.g. () => useAsyncDataGetPets(params)
|
|
4
|
+
* The factory is called once during connector setup (inside setup()).
|
|
5
|
+
* @param options Configuration for the list connector
|
|
4
6
|
*/
|
|
5
|
-
export declare function useListConnector(
|
|
7
|
+
export declare function useListConnector(factory: any, options?: {}): {
|
|
6
8
|
rows: any;
|
|
7
9
|
columns: any;
|
|
8
10
|
loading: any;
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { ref, computed, shallowRef } from 'vue';
|
|
14
14
|
/**
|
|
15
|
-
* @param
|
|
16
|
-
*
|
|
15
|
+
* @param factory A zero-argument function that calls and returns the underlying
|
|
16
|
+
* useAsyncData composable, e.g. () => useAsyncDataGetPets(params)
|
|
17
|
+
* The factory is called once during connector setup (inside setup()).
|
|
18
|
+
* @param options Configuration for the list connector
|
|
17
19
|
*/
|
|
18
|
-
export function useListConnector(
|
|
20
|
+
export function useListConnector(factory, options = {}) {
|
|
19
21
|
const { paginated = false, columns = [], columnLabels = {}, columnLabel = null } = options;
|
|
20
22
|
// ── Execute the underlying composable ──────────────────────────────────────
|
|
21
|
-
const composable =
|
|
23
|
+
const composable = factory();
|
|
22
24
|
// ── Derived state ──────────────────────────────────────────────────────────
|
|
23
25
|
const rows = computed(() => {
|
|
24
26
|
const data = composable.data?.value;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { UseFetchOptions } from '#app';
|
|
2
2
|
import { type ApiRequestOptions as BaseApiRequestOptions } from '../../shared/runtime/apiHelpers.js';
|
|
3
|
+
type PickInput = ReadonlyArray<string> | undefined;
|
|
4
|
+
type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
|
|
5
|
+
type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
|
|
3
6
|
/**
|
|
4
7
|
* Options for useAsyncData API requests with lifecycle callbacks.
|
|
5
8
|
* Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
|
|
6
9
|
* Native options like baseURL, method, body, headers, query, lazy, server, immediate, dedupe, etc. are all available.
|
|
7
10
|
* watch: boolean (true = auto-watch reactive params, false = disable auto-refresh)
|
|
8
11
|
*/
|
|
9
|
-
export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> & Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'watch'> & {
|
|
12
|
+
export type ApiAsyncDataOptions<T, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'watch'> & {
|
|
13
|
+
pick?: PickT;
|
|
14
|
+
transform?: (data: PickedData<T, PickT>) => DataT;
|
|
10
15
|
/**
|
|
11
16
|
* Enable automatic refresh when reactive params/url change (default: true).
|
|
12
17
|
* Set to false to disable auto-refresh entirely.
|
|
@@ -22,4 +27,5 @@ export type ApiAsyncDataOptions<T> = BaseApiRequestOptions<T> & Omit<UseFetchOpt
|
|
|
22
27
|
* - Global headers from useApiHeaders or $getApiHeaders
|
|
23
28
|
* - Watch pattern for reactive parameters
|
|
24
29
|
*/
|
|
25
|
-
export declare function useApiAsyncData<T>(key: string, url: string | (() => string), options?:
|
|
30
|
+
export declare function useApiAsyncData<T, Options extends ApiAsyncDataOptions<T, any, any> = ApiAsyncDataOptions<T>>(key: string, url: string | (() => string), options?: Options): any;
|
|
31
|
+
export {};
|
|
@@ -10,16 +10,21 @@ export interface RawResponse<T> {
|
|
|
10
10
|
status: number;
|
|
11
11
|
statusText: string;
|
|
12
12
|
}
|
|
13
|
+
type PickInput = ReadonlyArray<string> | undefined;
|
|
14
|
+
type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
|
|
15
|
+
type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
|
|
13
16
|
/**
|
|
14
17
|
* Options for useAsyncData Raw API requests.
|
|
15
18
|
* Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
|
|
16
19
|
* onSuccess receives data AND the full response (headers, status, statusText).
|
|
17
20
|
*/
|
|
18
|
-
export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> & Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
|
|
21
|
+
export type ApiAsyncDataRawOptions<T, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'onSuccess' | 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick' | 'onSuccess'> & {
|
|
22
|
+
pick?: PickT;
|
|
23
|
+
transform?: (data: PickedData<T, PickT>) => DataT;
|
|
19
24
|
/**
|
|
20
25
|
* Called when the request succeeds — receives both data and the full response object.
|
|
21
26
|
*/
|
|
22
|
-
onSuccess?: (data:
|
|
27
|
+
onSuccess?: (data: DataT, response: {
|
|
23
28
|
headers: Headers;
|
|
24
29
|
status: number;
|
|
25
30
|
statusText: string;
|
|
@@ -38,4 +43,5 @@ export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSucces
|
|
|
38
43
|
* - Global headers from useApiHeaders or $getApiHeaders
|
|
39
44
|
* - Watch pattern for reactive parameters
|
|
40
45
|
*/
|
|
41
|
-
export declare function useApiAsyncDataRaw<T>(key: string, url: string | (() => string), options?:
|
|
46
|
+
export declare function useApiAsyncDataRaw<T, DataT = T, PickT extends PickInput = undefined, Options extends ApiAsyncDataRawOptions<T, DataT, PickT> = ApiAsyncDataRawOptions<T, DataT, PickT>>(key: string, url: string | (() => string), options?: Options): any;
|
|
47
|
+
export {};
|
|
@@ -98,14 +98,16 @@ function generateImports(method, apiImportPath, isRaw) {
|
|
|
98
98
|
function generateFunctionBody(method, isRaw, generateOptions) {
|
|
99
99
|
const hasParams = !!method.requestType;
|
|
100
100
|
const paramsArg = hasParams ? `params: ${method.requestType}` : '';
|
|
101
|
+
const responseType = method.responseType !== 'void' ? method.responseType : 'void';
|
|
101
102
|
// Determine the options type based on isRaw
|
|
102
103
|
const optionsType = isRaw
|
|
103
|
-
? `ApiAsyncDataRawOptions<${
|
|
104
|
-
: `ApiAsyncDataOptions<${
|
|
105
|
-
const
|
|
104
|
+
? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
|
|
105
|
+
: `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
|
|
106
|
+
const optionsDefaultType = isRaw
|
|
107
|
+
? `ApiAsyncDataRawOptions<${responseType}, DataT, PickT>`
|
|
108
|
+
: `ApiAsyncDataOptions<${responseType}, DataT, PickT>`;
|
|
109
|
+
const optionsArg = `options?: Options`;
|
|
106
110
|
const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
|
|
107
|
-
// Determine the response type generic
|
|
108
|
-
const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
|
|
109
111
|
// Generate unique key for useAsyncData
|
|
110
112
|
const composableName = isRaw && method.rawMethodName
|
|
111
113
|
? `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`
|
|
@@ -116,15 +118,29 @@ function generateFunctionBody(method, isRaw, generateOptions) {
|
|
|
116
118
|
const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
|
|
117
119
|
// Choose the correct wrapper function
|
|
118
120
|
const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
|
|
121
|
+
const wrapperCall = isRaw
|
|
122
|
+
? `${wrapperFunction}<${responseType}, DataT, PickT, Options>`
|
|
123
|
+
: `${wrapperFunction}<${responseType}, Options>`;
|
|
124
|
+
const returnType = isRaw
|
|
125
|
+
? `ReturnType<typeof ${wrapperFunction}<${responseType}, DataT, PickT, Options>>`
|
|
126
|
+
: `ReturnType<typeof ${wrapperFunction}<${responseType}, Options>>`;
|
|
119
127
|
const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
|
|
120
128
|
const argsExtraction = hasParams
|
|
121
129
|
? ` const _hasKey = typeof args[0] === 'string'\n const params = _hasKey ? args[1] : args[0]\n const options = _hasKey ? { cacheKey: args[0], ...args[2] } : args[1]`
|
|
122
130
|
: ` const _hasKey = typeof args[0] === 'string'\n const options = _hasKey ? { cacheKey: args[0], ...args[1] } : args[0]`;
|
|
123
|
-
return `${description}export function ${composableName}
|
|
124
|
-
|
|
131
|
+
return `${description}export function ${composableName}<
|
|
132
|
+
DataT = ${responseType},
|
|
133
|
+
PickT extends ReadonlyArray<string> | undefined = undefined,
|
|
134
|
+
Options extends ${optionsType} = ${optionsDefaultType}
|
|
135
|
+
>(key: string, ${args}): ${returnType}
|
|
136
|
+
export function ${composableName}<
|
|
137
|
+
DataT = ${responseType},
|
|
138
|
+
PickT extends ReadonlyArray<string> | undefined = undefined,
|
|
139
|
+
Options extends ${optionsType} = ${optionsDefaultType}
|
|
140
|
+
>(${args}): ${returnType}
|
|
125
141
|
export function ${composableName}(...args: any[]) {
|
|
126
142
|
${argsExtraction}${pInit}
|
|
127
|
-
return ${
|
|
143
|
+
return ${wrapperCall}(${key}, ${url}, ${fetchOptions})
|
|
128
144
|
}`;
|
|
129
145
|
}
|
|
130
146
|
/**
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { UseFetchOptions } from '#app';
|
|
2
2
|
import { type ApiRequestOptions as BaseApiRequestOptions } from '../../shared/runtime/apiHelpers.js';
|
|
3
|
+
type PickInput = ReadonlyArray<string> | undefined;
|
|
4
|
+
type HasNestedPath<K extends ReadonlyArray<string>> = Extract<K[number], `${string}.${string}`> extends never ? false : true;
|
|
5
|
+
type PickedData<T, K extends PickInput> = K extends ReadonlyArray<string> ? HasNestedPath<K> extends true ? any : Pick<T, Extract<K[number], keyof T>> : T;
|
|
3
6
|
/**
|
|
4
7
|
* Options for useFetch API requests with lifecycle callbacks.
|
|
5
8
|
* Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
|
|
6
9
|
* Native options like baseURL, method, body, headers, query, lazy, server, immediate, etc. are all available.
|
|
7
10
|
*/
|
|
8
|
-
export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetchOptions<T>, 'transform' | 'pick'
|
|
11
|
+
export type ApiRequestOptions<T = any, DataT = T, PickT extends PickInput = undefined> = Omit<BaseApiRequestOptions<T>, 'transform' | 'pick'> & Omit<UseFetchOptions<T, DataT>, 'transform' | 'pick'> & {
|
|
12
|
+
pick?: PickT;
|
|
13
|
+
transform?: (data: PickedData<T, PickT>) => DataT;
|
|
14
|
+
};
|
|
9
15
|
/**
|
|
10
16
|
* Enhanced useFetch wrapper with lifecycle callbacks and request interception
|
|
11
17
|
*
|
|
@@ -42,4 +48,5 @@ export type ApiRequestOptions<T = any> = BaseApiRequestOptions<T> & Omit<UseFetc
|
|
|
42
48
|
* });
|
|
43
49
|
* ```
|
|
44
50
|
*/
|
|
45
|
-
export declare function useApiRequest<T = any, Options extends ApiRequestOptions<T> = ApiRequestOptions<T>>(url: string | (() => string), options?: Options): any;
|
|
51
|
+
export declare function useApiRequest<T = any, Options extends ApiRequestOptions<T, any, any> = ApiRequestOptions<T>>(url: string | (() => string), options?: Options): any;
|
|
52
|
+
export {};
|
|
@@ -87,16 +87,20 @@ function generateImports(method, apiImportPath) {
|
|
|
87
87
|
function generateFunctionBody(method, options) {
|
|
88
88
|
const hasParams = !!method.requestType;
|
|
89
89
|
const paramsArg = hasParams ? `params: ${method.requestType}` : '';
|
|
90
|
-
const
|
|
91
|
-
const
|
|
90
|
+
const responseType = method.responseType !== 'void' ? method.responseType : 'void';
|
|
91
|
+
const optionsType = `ApiRequestOptions<${responseType}, DataT, PickT>`;
|
|
92
|
+
const optionsArg = `options?: Options`;
|
|
92
93
|
const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
|
|
93
|
-
const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
|
|
94
94
|
const url = generateUrl(method);
|
|
95
95
|
const fetchOptions = generateFetchOptions(method, options);
|
|
96
96
|
const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
|
|
97
97
|
const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
|
|
98
|
-
return `${description}export const ${method.composableName} =
|
|
99
|
-
|
|
98
|
+
return `${description}export const ${method.composableName} = <
|
|
99
|
+
DataT = ${responseType},
|
|
100
|
+
PickT extends ReadonlyArray<string> | undefined = undefined,
|
|
101
|
+
Options extends ${optionsType} = ApiRequestOptions<${responseType}, DataT, PickT>
|
|
102
|
+
>(${args}) => {${pInit}
|
|
103
|
+
return useApiRequest<${responseType}, Options>(${url}, ${fetchOptions})
|
|
100
104
|
}`;
|
|
101
105
|
}
|
|
102
106
|
/**
|
package/dist/module/index.js
CHANGED
|
@@ -70,15 +70,12 @@ export default defineNuxtModule({
|
|
|
70
70
|
if (options.createUseAsyncDataConnectors &&
|
|
71
71
|
selectedGenerators.includes('useAsyncData')) {
|
|
72
72
|
const connectorsOutputDir = path.join(composablesOutputDir, 'connectors');
|
|
73
|
-
const runtimeDir = path.join(resolvedOutput, 'runtime');
|
|
74
73
|
await generateConnectors({
|
|
75
74
|
inputSpec: resolvedInput,
|
|
76
75
|
outputDir: connectorsOutputDir,
|
|
77
76
|
composablesRelDir: '../use-async-data',
|
|
78
77
|
runtimeRelDir: '../../runtime',
|
|
79
78
|
}, logger);
|
|
80
|
-
// Register #nxh alias so generated connector imports resolve
|
|
81
|
-
nuxt.options.alias['#nxh'] = runtimeDir;
|
|
82
79
|
}
|
|
83
80
|
};
|
|
84
81
|
// --- Hooks: dev build / production build ---
|