nuxt-openapi-hyperfetch 0.3.81-beta → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -212
- package/dist/generators/components/connector-generator/templates.js +67 -17
- package/dist/generators/components/schema-analyzer/intent-detector.js +1 -12
- package/dist/generators/components/schema-analyzer/openapi-reader.js +10 -1
- package/dist/generators/components/schema-analyzer/resource-grouper.js +7 -0
- package/dist/generators/components/schema-analyzer/schema-field-mapper.js +1 -22
- package/dist/generators/components/schema-analyzer/types.d.ts +10 -0
- package/dist/generators/connectors/generator.d.ts +12 -0
- package/dist/generators/connectors/generator.js +115 -0
- package/dist/generators/connectors/runtime/connector-types.d.ts +147 -0
- package/dist/generators/connectors/runtime/connector-types.js +10 -0
- package/dist/generators/connectors/runtime/useCreateConnector.d.ts +26 -0
- package/dist/generators/connectors/runtime/useCreateConnector.js +156 -0
- package/dist/generators/connectors/runtime/useDeleteConnector.d.ts +30 -0
- package/dist/generators/connectors/runtime/useDeleteConnector.js +143 -0
- package/dist/generators/connectors/runtime/useGetAllConnector.d.ts +25 -0
- package/dist/generators/connectors/runtime/useGetAllConnector.js +127 -0
- package/dist/generators/connectors/runtime/useGetConnector.d.ts +15 -0
- package/dist/generators/connectors/runtime/useGetConnector.js +99 -0
- package/dist/generators/connectors/runtime/useUpdateConnector.d.ts +34 -0
- package/dist/generators/connectors/runtime/useUpdateConnector.js +211 -0
- package/dist/generators/connectors/runtime/zod-error-merger.d.ts +23 -0
- package/dist/generators/connectors/runtime/zod-error-merger.js +106 -0
- package/dist/generators/connectors/templates.d.ts +4 -0
- package/dist/generators/connectors/templates.js +376 -0
- package/dist/generators/connectors/types.d.ts +37 -0
- package/dist/generators/connectors/types.js +7 -0
- package/dist/generators/shared/runtime/useDeleteConnector.js +4 -2
- package/dist/generators/shared/runtime/useDetailConnector.d.ts +0 -1
- package/dist/generators/shared/runtime/useDetailConnector.js +9 -20
- package/dist/generators/shared/runtime/useFormConnector.js +4 -3
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +14 -5
- package/dist/generators/use-async-data/templates.js +20 -16
- package/dist/generators/use-fetch/templates.js +1 -1
- package/dist/index.js +1 -16
- package/dist/module/index.js +2 -3
- package/package.json +4 -3
- package/src/cli/prompts.ts +1 -7
- package/src/generators/components/connector-generator/templates.ts +97 -22
- package/src/generators/components/schema-analyzer/intent-detector.ts +1 -16
- package/src/generators/components/schema-analyzer/openapi-reader.ts +14 -1
- package/src/generators/components/schema-analyzer/resource-grouper.ts +9 -0
- package/src/generators/components/schema-analyzer/schema-field-mapper.ts +1 -26
- package/src/generators/components/schema-analyzer/types.ts +11 -0
- package/src/generators/connectors/generator.ts +137 -0
- package/src/generators/connectors/runtime/connector-types.ts +207 -0
- package/src/generators/connectors/runtime/useCreateConnector.ts +199 -0
- package/src/generators/connectors/runtime/useDeleteConnector.ts +179 -0
- package/src/generators/connectors/runtime/useGetAllConnector.ts +151 -0
- package/src/generators/connectors/runtime/useGetConnector.ts +120 -0
- package/src/generators/connectors/runtime/useUpdateConnector.ts +257 -0
- package/src/generators/connectors/runtime/zod-error-merger.ts +119 -0
- package/src/generators/connectors/templates.ts +481 -0
- package/src/generators/connectors/types.ts +39 -0
- package/src/generators/shared/runtime/useDeleteConnector.ts +4 -2
- package/src/generators/shared/runtime/useDetailConnector.ts +8 -19
- package/src/generators/shared/runtime/useFormConnector.ts +4 -3
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +16 -5
- package/src/generators/use-async-data/templates.ts +24 -16
- package/src/generators/use-fetch/templates.ts +1 -1
- package/src/index.ts +2 -19
- package/src/module/index.ts +2 -5
- package/docs/generated-components.md +0 -615
- package/docs/headless-composables-ui.md +0 -569
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
# Headless UI Composables — Feature Spec & Architecture
|
|
2
|
-
|
|
3
|
-
> **Status**: Planned — Capa 2 del sistema de generación de componentes Vue
|
|
4
|
-
> **Dependencia**: Requiere que los composables `useAsyncData` estén generados previamente
|
|
5
|
-
> **Objetivo**: Exponer lógica CRUD de forma headless y framework-agnostic para que cualquier desarrollador pueda construir sus propios componentes UI sobre ella
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Tabla de Contenidos
|
|
10
|
-
|
|
11
|
-
- [Qué es esta feature](#qué-es-esta-feature)
|
|
12
|
-
- [Motivación](#motivación)
|
|
13
|
-
- [Arquitectura de 3 Capas](#arquitectura-de-3-capas)
|
|
14
|
-
- [Diseño del Connector Unificado](#diseño-del-connector-unificado)
|
|
15
|
-
- [Contratos de los Sub-Connectors](#contratos-de-los-sub-connectors)
|
|
16
|
-
- [Validación con Zod](#validación-con-zod)
|
|
17
|
-
- [Detección automática de campo tipo en formularios](#detección-automática-de-campo-tipo-en-formularios)
|
|
18
|
-
- [Integración con paginación](#integración-con-paginación)
|
|
19
|
-
- [Estructura de archivos a crear](#estructura-de-archivos-a-crear)
|
|
20
|
-
- [Plan de implementación](#plan-de-implementación)
|
|
21
|
-
- [Ejemplos de uso por el developer](#ejemplos-de-uso-por-el-developer)
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Qué es esta feature
|
|
26
|
-
|
|
27
|
-
El **Connector Generator** genera, por cada recurso detectado en el OpenAPI spec, un composable Vue 3 headless llamado `use{Resource}Connector.ts`.
|
|
28
|
-
|
|
29
|
-
Este composable:
|
|
30
|
-
- Encapsula toda la lógica de negocio (fetch, submit, estado, paginación, errores)
|
|
31
|
-
- No tiene ningún template HTML — es 100% lógica reutilizable
|
|
32
|
-
- Expone una API clara y fuertemente tipada
|
|
33
|
-
- Es el puente entre los composables generados (`useAsyncDataGetPets`) y cualquier componente UI
|
|
34
|
-
|
|
35
|
-
El developer puede:
|
|
36
|
-
1. Usar el connector directamente para construir sus propios componentes
|
|
37
|
-
2. Dejar que el generador de componentes (Capa 3) genere componentes `.vue` de NuxtUI/PrimeVue sobre él
|
|
38
|
-
3. Usar solo una parte del connector (ej: solo `table`, ignorando los formularios)
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Motivación
|
|
43
|
-
|
|
44
|
-
Sin esta capa, cada adaptador UI (NuxtUI, PrimeVue, Vuetify…) tendría que reimplementar:
|
|
45
|
-
- La lógica de fetch y re-fetch
|
|
46
|
-
- El estado de loading, errores, datos
|
|
47
|
-
- La gestión de paginación
|
|
48
|
-
- La apertura de modales y estado de formularios
|
|
49
|
-
- El pre-relleno de formulario de update con datos del get
|
|
50
|
-
|
|
51
|
-
Con el Connector, esa lógica se escribe **una sola vez**. Los adaptadores solo añaden el template.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Arquitectura de 3 Capas
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
59
|
-
│ CAPA 1 — Schema Analyzer (src/generators/components/) │
|
|
60
|
-
│ Lee OpenAPI YAML/JSON directo │
|
|
61
|
-
│ Detecta intents por HTTP method + path pattern + schema │
|
|
62
|
-
│ Agrupa en recursos por tag (prioridad) o path prefix │
|
|
63
|
-
│ Produce un ResourceMap tipado │
|
|
64
|
-
└─────────────────────────────────────────────────────────────┘
|
|
65
|
-
↓ alimenta
|
|
66
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
67
|
-
│ CAPA 2 — Connector Generator (esta feature) │
|
|
68
|
-
│ Genera use{Resource}Connector.ts por recurso │
|
|
69
|
-
│ Composables headless — lógica sin template │
|
|
70
|
-
│ Usa los composables useAsyncData ya generados │
|
|
71
|
-
│ Es consumido por Capa 3 o directamente por el developer │
|
|
72
|
-
└─────────────────────────────────────────────────────────────┘
|
|
73
|
-
↓ consume
|
|
74
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
75
|
-
│ CAPA 3 — Component Generator (ver generated-components.md)│
|
|
76
|
-
│ Genera .vue por recurso (NuxtUI, PrimeVue, etc.) │
|
|
77
|
-
│ Importa use{Resource}Connector.ts │
|
|
78
|
-
│ Solo contiene template + binding — cero lógica propia │
|
|
79
|
-
└─────────────────────────────────────────────────────────────┘
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Diseño del Connector Unificado
|
|
85
|
-
|
|
86
|
-
Cada recurso produce **un único composable** con secciones independientes:
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
// composables/connectors/usePetsConnector.ts
|
|
90
|
-
// AUTO-GENERATED — DO NOT EDIT
|
|
91
|
-
// Generated by nxh — https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
|
|
92
|
-
|
|
93
|
-
import { useAsyncDataGetPets } from '../use-async-data/use-async-data-get-pets'
|
|
94
|
-
import { useAsyncDataGetPet } from '../use-async-data/use-async-data-get-pet'
|
|
95
|
-
import { useAsyncDataCreatePet } from '../use-async-data/use-async-data-create-pet'
|
|
96
|
-
import { useAsyncDataUpdatePet } from '../use-async-data/use-async-data-update-pet'
|
|
97
|
-
import { useAsyncDataDeletePet } from '../use-async-data/use-async-data-delete-pet'
|
|
98
|
-
import { useListConnector } from '#nxh/runtime/useListConnector'
|
|
99
|
-
import { useDetailConnector } from '#nxh/runtime/useDetailConnector'
|
|
100
|
-
import { useFormConnector } from '#nxh/runtime/useFormConnector'
|
|
101
|
-
import { useDeleteConnector } from '#nxh/runtime/useDeleteConnector'
|
|
102
|
-
|
|
103
|
-
export function usePetsConnector() {
|
|
104
|
-
const table = useListConnector(useAsyncDataGetPets, {
|
|
105
|
-
paginated: true,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
const detail = useDetailConnector(useAsyncDataGetPet)
|
|
109
|
-
|
|
110
|
-
const createForm = useFormConnector(useAsyncDataCreatePet, {
|
|
111
|
-
schema: PetCreateSchema, // inferido del request body del POST
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
const updateForm = useFormConnector(useAsyncDataUpdatePet, {
|
|
115
|
-
schema: PetUpdateSchema, // inferido del request body del PUT
|
|
116
|
-
loadWith: detail, // pre-rellena con GET /pets/{id} si existe
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const deleteAction = useDeleteConnector(useAsyncDataDeletePet)
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
table,
|
|
123
|
-
detail,
|
|
124
|
-
createForm,
|
|
125
|
-
updateForm,
|
|
126
|
-
deleteAction,
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
El connector **solo incluye las secciones que existen en el spec**. Si no hay `DELETE`, no se genera `deleteAction`. Si no hay `PUT`, no se genera `updateForm`.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Contratos de los Sub-Connectors
|
|
136
|
-
|
|
137
|
-
### `useListConnector(composable, options)`
|
|
138
|
-
|
|
139
|
-
Wrappea un composable GET que devuelve array:
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
interface ListConnectorReturn<T> {
|
|
143
|
-
// Estado
|
|
144
|
-
rows: ComputedRef<T[]>
|
|
145
|
-
columns: ComputedRef<ColumnDef[]> // generado de response schema
|
|
146
|
-
loading: ComputedRef<boolean>
|
|
147
|
-
error: ComputedRef<any>
|
|
148
|
-
|
|
149
|
-
// Paginación (si paginated: true)
|
|
150
|
-
pagination: ComputedRef<PaginationState>
|
|
151
|
-
goToPage: (page: number) => void
|
|
152
|
-
nextPage: () => void
|
|
153
|
-
prevPage: () => void
|
|
154
|
-
|
|
155
|
-
// Acciones
|
|
156
|
-
refresh: () => void
|
|
157
|
-
selected: Ref<T[]> // filas seleccionadas
|
|
158
|
-
onRowSelect: (row: T) => void
|
|
159
|
-
|
|
160
|
-
// Para apertura de modales (coordinación con PetsCrud.vue)
|
|
161
|
-
openCreate: () => void
|
|
162
|
-
openUpdate: (row: T) => void
|
|
163
|
-
openDelete: (row: T) => void
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
interface ColumnDef {
|
|
167
|
-
key: string
|
|
168
|
-
label: string // viene de index.ts del developer (o key capitalizado)
|
|
169
|
-
sortable?: boolean
|
|
170
|
-
type: 'text' | 'number' | 'date' | 'boolean' | 'badge'
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### `useDetailConnector(composable)`
|
|
175
|
-
|
|
176
|
-
Wrappea un composable GET que devuelve un objeto:
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
interface DetailConnectorReturn<T> {
|
|
180
|
-
item: Ref<T | null>
|
|
181
|
-
loading: ComputedRef<boolean>
|
|
182
|
-
error: ComputedRef<any>
|
|
183
|
-
load: (id: string | number) => Promise<void>
|
|
184
|
-
fields: ComputedRef<FieldDef[]> // para vistas de detalle
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### `useFormConnector(composable, options)`
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
import type { ZodSchema } from 'zod'
|
|
192
|
-
|
|
193
|
-
interface FormConnectorOptions<T> {
|
|
194
|
-
schema: ZodSchema // Zod schema generado del request body OpenAPI
|
|
195
|
-
loadWith?: DetailConnectorReturn<T> // pre-rellena desde detail si se pasa
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
interface FormConnectorReturn<T> {
|
|
199
|
-
// Estado del formulario
|
|
200
|
-
model: Ref<Partial<T>>
|
|
201
|
-
fields: ComputedRef<FormFieldDef[]>
|
|
202
|
-
loading: ComputedRef<boolean>
|
|
203
|
-
errors: ComputedRef<Record<string, string>> // { name: 'Name is required' }
|
|
204
|
-
isValid: ComputedRef<boolean>
|
|
205
|
-
|
|
206
|
-
// Acciones
|
|
207
|
-
submit: () => Promise<void> // valida con Zod antes de llamar al composable
|
|
208
|
-
reset: () => void
|
|
209
|
-
setValues: (data: Partial<T>) => void // llamado por loadWith automáticamente
|
|
210
|
-
|
|
211
|
-
// Callbacks
|
|
212
|
-
onSuccess: Ref<(result: T) => void>
|
|
213
|
-
onError: Ref<(error: any) => void>
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface FormFieldDef {
|
|
217
|
-
key: string
|
|
218
|
-
label: string
|
|
219
|
-
type: 'input' | 'textarea' | 'select' | 'checkbox' | 'datepicker' | 'number'
|
|
220
|
-
required: boolean
|
|
221
|
-
options?: { label: string; value: any }[] // para type: 'select' (enum en OpenAPI)
|
|
222
|
-
placeholder?: string
|
|
223
|
-
hidden?: boolean // readOnly: true del schema → hidden por defecto
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**Inferencia automática de tipo de campo** (del schema OpenAPI):
|
|
228
|
-
|
|
229
|
-
| OpenAPI schema | `FormFieldDef.type` generado |
|
|
230
|
-
|-------------------------|------------------------------|
|
|
231
|
-
| `type: string` | `input` |
|
|
232
|
-
| `type: string, format: date` | `datepicker` |
|
|
233
|
-
| `type: string, format: date-time` | `datepicker` |
|
|
234
|
-
| `type: string, maxLength > 200` | `textarea` |
|
|
235
|
-
| `type: integer / number` | `number` |
|
|
236
|
-
| `type: boolean` | `checkbox` |
|
|
237
|
-
| `enum: [...]` | `select` |
|
|
238
|
-
| `readOnly: true` | campo omitido del form (configurable) |
|
|
239
|
-
|
|
240
|
-
El developer puede sobreescribir cualquier campo en `components/nxh/pets/index.ts`:
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
export const config = {
|
|
244
|
-
fields: {
|
|
245
|
-
bio: { type: 'textarea' }, // override de tipo de input
|
|
246
|
-
status: { type: 'select', options: [
|
|
247
|
-
{ label: 'Active', value: 'active' },
|
|
248
|
-
{ label: 'Inactive', value: 'inactive' },
|
|
249
|
-
]},
|
|
250
|
-
id: { hidden: true }, // forzar oculto aunque no sea readOnly
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### `useDeleteConnector(composable)`
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
interface DeleteConnectorReturn<T> {
|
|
259
|
-
loading: ComputedRef<boolean>
|
|
260
|
-
error: ComputedRef<any>
|
|
261
|
-
target: Ref<T | null> // el item que se va a borrar
|
|
262
|
-
setTarget: (item: T) => void
|
|
263
|
-
confirm: () => Promise<void>
|
|
264
|
-
cancel: () => void
|
|
265
|
-
onSuccess: Ref<(item: T) => void>
|
|
266
|
-
onError: Ref<(error: any) => void>
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Validación con Zod
|
|
273
|
-
|
|
274
|
-
La validación ocurre en **tiempo de ejecución dentro de `useFormConnector`**, usando schemas Zod que fueron generados en **code-gen time** por el Schema Analyzer a partir del request body del OpenAPI spec.
|
|
275
|
-
|
|
276
|
-
### Cómo funciona internamente `useFormConnector`
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
// src/generators/shared/runtime/useFormConnector.ts (runtime — se copia al proyecto)
|
|
280
|
-
import { ref, computed } from 'vue'
|
|
281
|
-
import type { ZodSchema } from 'zod'
|
|
282
|
-
|
|
283
|
-
export function useFormConnector<T>(composable: Function, options: FormConnectorOptions<T>) {
|
|
284
|
-
const model = ref<Partial<T>>({})
|
|
285
|
-
const errors = ref<Record<string, string>>({})
|
|
286
|
-
const loading = ref(false)
|
|
287
|
-
|
|
288
|
-
async function submit() {
|
|
289
|
-
// 1. Validar con Zod antes de cualquier llamada HTTP
|
|
290
|
-
const result = options.schema.safeParse(model.value)
|
|
291
|
-
|
|
292
|
-
if (!result.success) {
|
|
293
|
-
// Zod devuelve errores por campo: { name: ['Required'], status: ['Invalid value'] }
|
|
294
|
-
const fieldErrors = result.error.flatten().fieldErrors
|
|
295
|
-
// Convertir array de mensajes a string único por campo
|
|
296
|
-
errors.value = Object.fromEntries(
|
|
297
|
-
Object.entries(fieldErrors).map(([key, msgs]) => [key, msgs?.[0] ?? 'Invalid'])
|
|
298
|
-
)
|
|
299
|
-
return // No se hace la llamada HTTP
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
errors.value = {}
|
|
303
|
-
loading.value = true
|
|
304
|
-
try {
|
|
305
|
-
const response = await composable(result.data)
|
|
306
|
-
options.onSuccess?.(response)
|
|
307
|
-
} catch (err) {
|
|
308
|
-
options.onError?.(err)
|
|
309
|
-
} finally {
|
|
310
|
-
loading.value = false
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return { model, errors, loading, submit, /* ... */ }
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### Cómo el Schema Analyzer genera el schema Zod
|
|
319
|
-
|
|
320
|
-
El `schema-field-mapper.ts` convierte cada propiedad del request body OpenAPI a su equivalente Zod:
|
|
321
|
-
|
|
322
|
-
| OpenAPI | Zod generado |
|
|
323
|
-
|------------------------------------|----------------------------------------------|
|
|
324
|
-
| `type: string` + required | `z.string().min(1)` |
|
|
325
|
-
| `type: string` + opcional | `z.string().optional()` |
|
|
326
|
-
| `type: string, minLength: 3` | `z.string().min(3)` |
|
|
327
|
-
| `type: string, maxLength: 100` | `z.string().max(100)` |
|
|
328
|
-
| `type: string, format: email` | `z.string().email()` |
|
|
329
|
-
| `type: string, format: uri` | `z.string().url()` |
|
|
330
|
-
| `type: integer / number` + req. | `z.number().int()` / `z.number()` |
|
|
331
|
-
| `type: boolean` | `z.boolean()` |
|
|
332
|
-
| `enum: ['a','b']` + required | `z.enum(['a', 'b'])` |
|
|
333
|
-
| `enum: ['a','b']` + opcional | `z.enum(['a', 'b']).optional()` |
|
|
334
|
-
| `type: array, items: { type: string }` | `z.array(z.string())` |
|
|
335
|
-
| `type: array, minItems: 1` | `z.array(z.string()).min(1)` |
|
|
336
|
-
| `readOnly: true` | campo excluido del schema (no se valida) |
|
|
337
|
-
|
|
338
|
-
### Mensajes de error — 3 niveles
|
|
339
|
-
|
|
340
|
-
**Nivel 1 — Mensajes por defecto de Zod** (en inglés, zero config):
|
|
341
|
-
```
|
|
342
|
-
{ name: 'String must contain at least 1 character(s)' }
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**Nivel 2 — `errorMap` global** (traducciones genéricas para todo el proyecto):
|
|
346
|
-
```ts
|
|
347
|
-
// plugins/zod-i18n.ts (el developer crea esto en su proyecto Nuxt)
|
|
348
|
-
import { z } from 'zod'
|
|
349
|
-
|
|
350
|
-
z.setErrorMap((issue, ctx) => {
|
|
351
|
-
switch (issue.code) {
|
|
352
|
-
case 'too_small':
|
|
353
|
-
if (issue.type === 'string' && issue.minimum === 1)
|
|
354
|
-
return { message: 'Este campo es obligatorio' }
|
|
355
|
-
return { message: `Mínimo ${issue.minimum} caracteres` }
|
|
356
|
-
case 'invalid_enum_value':
|
|
357
|
-
return { message: `Valor no válido` }
|
|
358
|
-
case 'invalid_type':
|
|
359
|
-
if (issue.received === 'undefined')
|
|
360
|
-
return { message: 'Este campo es obligatorio' }
|
|
361
|
-
default:
|
|
362
|
-
return { message: ctx.defaultError }
|
|
363
|
-
}
|
|
364
|
-
})
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**Nivel 3 — Mensajes por campo** (en `components/nxh/pets/index.ts`, máximo control):
|
|
368
|
-
```ts
|
|
369
|
-
export const config = {
|
|
370
|
-
fields: {
|
|
371
|
-
name: {
|
|
372
|
-
label: 'Nombre',
|
|
373
|
-
errors: {
|
|
374
|
-
required: 'El nombre es obligatorio',
|
|
375
|
-
min: 'El nombre debe tener al menos 1 carácter',
|
|
376
|
-
max: 'El nombre no puede superar 100 caracteres',
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
status: {
|
|
380
|
-
label: 'Estado',
|
|
381
|
-
errors: {
|
|
382
|
-
invalid_enum_value: 'Selecciona un estado válido',
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
`useFormConnector` merge los mensajes del config sobre los de Zod: si el campo tiene `errors.required`, ese mensaje reemplaza al de Zod para ese campo. Los adaptadores UI (NuxtUI, PrimeVue) solo leen `errors.value[campo]` — framework-agnostic.
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Integración con paginación
|
|
394
|
-
|
|
395
|
-
Si el endpoint del list tiene respuesta paginada (detectada por el Schema Analyzer al ver campos como `total`, `totalPages` en la respuesta, o configuración en `nxh.config`), `useListConnector` activa automáticamente `paginated: true` en el composable `useAsyncData` correspondiente, reutilizando el sistema de paginación ya implementado en `src/generators/shared/runtime/pagination.ts`.
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Estructura de archivos a crear
|
|
400
|
-
|
|
401
|
-
### Archivos nuevos en `src/` (en el generador)
|
|
402
|
-
|
|
403
|
-
```
|
|
404
|
-
src/
|
|
405
|
-
generators/
|
|
406
|
-
components/ ← NUEVO directorio
|
|
407
|
-
schema-analyzer/
|
|
408
|
-
index.ts ← entry point del analyzer
|
|
409
|
-
types.ts ← ResourceMap, Intent, FieldDef, etc.
|
|
410
|
-
intent-detector.ts ← detecta intent por HTTP+path+schema
|
|
411
|
-
resource-grouper.ts ← agrupa endpoints en recursos por tag/path
|
|
412
|
-
schema-field-mapper.ts ← OpenAPI schema → FormFieldDef[]
|
|
413
|
-
openapi-reader.ts ← lee y parsea el YAML/JSON del spec
|
|
414
|
-
connector-generator/
|
|
415
|
-
index.ts ← entry point del connector generator
|
|
416
|
-
types.ts ← ConnectorInfo, SubConnectorDef, etc.
|
|
417
|
-
templates.ts ← plantillas de texto para los .ts generados
|
|
418
|
-
generator.ts ← orquesta la generación de connectors
|
|
419
|
-
shared/
|
|
420
|
-
naming.ts ← convenciones de nombres (usePetsConnector, etc.)
|
|
421
|
-
paths.ts ← resolución de paths de output
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### Archivos runtime nuevos en `src/generators/shared/runtime/`
|
|
425
|
-
|
|
426
|
-
```
|
|
427
|
-
src/generators/shared/runtime/
|
|
428
|
-
useListConnector.ts ← NUEVO — conector genérico para listas
|
|
429
|
-
useDetailConnector.ts ← NUEVO — conector genérico para detalle
|
|
430
|
-
useFormConnector.ts ← NUEVO — conector genérico para formularios (usa Zod)
|
|
431
|
-
useDeleteConnector.ts ← NUEVO — conector genérico para delete
|
|
432
|
-
zod-error-merger.ts ← NUEVO — merge errores Zod con config.fields.errors
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
Estos archivos son **runtime** — se copian al proyecto del usuario igual que `apiHelpers.ts` y `pagination.ts`.
|
|
436
|
-
|
|
437
|
-
> **Dependencia de runtime:** `zod` debe estar instalado en el proyecto del usuario (`npm i zod`). Se añade como `peerDependency` en el package.json del generador.
|
|
438
|
-
|
|
439
|
-
### Archivos que se generan en el proyecto del usuario
|
|
440
|
-
|
|
441
|
-
```
|
|
442
|
-
composables/
|
|
443
|
-
connectors/
|
|
444
|
-
usePetsConnector.ts ← AUTO-GENERATED, regenerable
|
|
445
|
-
useCategoryConnector.ts
|
|
446
|
-
useOrderConnector.ts
|
|
447
|
-
...
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
---
|
|
451
|
-
|
|
452
|
-
## Plan de implementación
|
|
453
|
-
|
|
454
|
-
### Fase 1 — Schema Analyzer
|
|
455
|
-
|
|
456
|
-
**Archivos:** `src/generators/components/schema-analyzer/`
|
|
457
|
-
|
|
458
|
-
1. `openapi-reader.ts` — Leer YAML/JSON con `js-yaml` o `@apidevtools/swagger-parser`. Producir el spec como objeto tipado
|
|
459
|
-
2. `types.ts` — Definir `ResourceMap`, `ResourceInfo`, `EndpointInfo`, `Intent`, `FieldDef`, `ColumnDef`
|
|
460
|
-
3. `intent-detector.ts` — Lógica de detección:
|
|
461
|
-
- Señal 1: HTTP method (`GET` → list/detail, `POST` → create, `PUT/PATCH` → update, `DELETE` → delete)
|
|
462
|
-
- Señal 2: Path pattern (`/pets` sin path params → list candidate, `/pets/{id}` → detail/update/delete)
|
|
463
|
-
- Señal 3: Response schema (array → `list`, object → `detail`)
|
|
464
|
-
- Override: `nxh.config.js` `components['GET /pets/search'] = { intent: 'list' }`
|
|
465
|
-
4. `resource-grouper.ts` — Agrupa endpoints con el mismo tag (prioridad) o path prefix (fallback) en un `ResourceInfo`
|
|
466
|
-
5. `schema-field-mapper.ts` — Convierte OpenAPI property schema → `FormFieldDef` Y genera el **string de código Zod** correspondiente (ver tabla de mapeo en la sección [Validación con Zod](#validación-con-zod))
|
|
467
|
-
|
|
468
|
-
### Fase 2 — Runtime Connectors
|
|
469
|
-
|
|
470
|
-
**Archivos:** `src/generators/shared/runtime/`
|
|
471
|
-
|
|
472
|
-
1. `useListConnector.ts` — Recibe composable `useAsyncData` + opciones, devuelve `ListConnectorReturn`
|
|
473
|
-
2. `useDetailConnector.ts` — Recibe composable `useAsyncData`, devuelve `DetailConnectorReturn`
|
|
474
|
-
3. `useFormConnector.ts` — Recibe composable `useAsyncData` mutación + Zod schema + `loadWith` opcional:
|
|
475
|
-
- Llama `schema.safeParse(model.value)` en `submit()` antes de la llamada HTTP
|
|
476
|
-
- Convierte `error.flatten().fieldErrors` a `Record<string, string>`
|
|
477
|
-
- Merge con mensajes de `config.fields[key].errors` cuando existen
|
|
478
|
-
- Expone `errors: Ref<Record<string, string>>` — framework-agnostic
|
|
479
|
-
4. `useDeleteConnector.ts` — Recibe composable `useAsyncData` delete, devuelve `DeleteConnectorReturn`
|
|
480
|
-
5. `zod-error-merger.ts` — Helper que merge errores Zod con overrides del `config.fields.errors`
|
|
481
|
-
|
|
482
|
-
### Fase 3 — Connector Generator
|
|
483
|
-
|
|
484
|
-
**Archivos:** `src/generators/components/connector-generator/`
|
|
485
|
-
|
|
486
|
-
1. `types.ts` — `ConnectorInfo` (todo lo necesario para generar el `.ts`)
|
|
487
|
-
2. `templates.ts` — Funciones que producen el string del archivo `use{Resource}Connector.ts`
|
|
488
|
-
3. `generator.ts` — Toma el `ResourceMap` de Fase 1, genera un archivo por recurso
|
|
489
|
-
4. Integración en `src/index.ts` con nuevo comando/opción CLI
|
|
490
|
-
|
|
491
|
-
### Fase 4 — Integración en CLI
|
|
492
|
-
|
|
493
|
-
Nuevo flag en `nxh generate`:
|
|
494
|
-
```
|
|
495
|
-
--components <type> Generate UI components: connectors, nuxtui, primevue
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
O nuevo comando separado:
|
|
499
|
-
```
|
|
500
|
-
nxh components --input swagger.yaml --output ./components/nxh --ui nuxtui
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
---
|
|
504
|
-
|
|
505
|
-
## Ejemplos de uso por el developer
|
|
506
|
-
|
|
507
|
-
### Uso básico — tabla simple
|
|
508
|
-
|
|
509
|
-
```vue
|
|
510
|
-
<!-- pages/pets.vue -->
|
|
511
|
-
<script setup>
|
|
512
|
-
import { usePetsConnector } from '~/composables/connectors/usePetsConnector'
|
|
513
|
-
|
|
514
|
-
const { table } = usePetsConnector()
|
|
515
|
-
</script>
|
|
516
|
-
|
|
517
|
-
<template>
|
|
518
|
-
<!-- Con NuxtUI -->
|
|
519
|
-
<UTable :rows="table.rows.value" :columns="table.columns.value" :loading="table.loading.value" />
|
|
520
|
-
</template>
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
### Uso avanzado — CRUD completo custom
|
|
524
|
-
|
|
525
|
-
```vue
|
|
526
|
-
<!-- pages/pets-admin.vue -->
|
|
527
|
-
<script setup>
|
|
528
|
-
import { usePetsConnector } from '~/composables/connectors/usePetsConnector'
|
|
529
|
-
|
|
530
|
-
const { table, createForm, updateForm, deleteAction } = usePetsConnector()
|
|
531
|
-
|
|
532
|
-
// Hook personalizado
|
|
533
|
-
createForm.onSuccess.value = () => {
|
|
534
|
-
table.refresh()
|
|
535
|
-
useToast().add({ title: 'Pet created!' })
|
|
536
|
-
}
|
|
537
|
-
</script>
|
|
538
|
-
|
|
539
|
-
<template>
|
|
540
|
-
<!-- El developer monta su propio HTML con la lógica del connector -->
|
|
541
|
-
<MyCustomTable v-bind="table" @edit="updateForm.setValues" @delete="deleteAction.setTarget" />
|
|
542
|
-
<MyCustomForm v-bind="createForm" />
|
|
543
|
-
</template>
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### Traducción de columnas mediante index.ts
|
|
547
|
-
|
|
548
|
-
```ts
|
|
549
|
-
// components/nxh/pets/index.ts (generado una vez, editado por el developer)
|
|
550
|
-
export const config = {
|
|
551
|
-
columns: {
|
|
552
|
-
name: { label: 'Nombre' },
|
|
553
|
-
status: { label: 'Estado' },
|
|
554
|
-
createdAt: { label: 'Fecha de creación' },
|
|
555
|
-
},
|
|
556
|
-
fields: {
|
|
557
|
-
status: {
|
|
558
|
-
type: 'select',
|
|
559
|
-
options: [
|
|
560
|
-
{ label: 'Disponible', value: 'available' },
|
|
561
|
-
{ label: 'Vendido', value: 'sold' },
|
|
562
|
-
]
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
showReadonlyFields: false,
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
El generated `usePetsConnector.ts` importa este config y lo aplica sobre los `ColumnDef` y `FormFieldDef` antes de exponerlos.
|