feat(crud): tipos y generador de DDL para recursos CRUD

Anade los tipos CRUDResource, CRUDField, CRUDListParams y CRUDListResult
que modelan un recurso CRUD sobre SQLite, junto con dos funciones puras:
- crud_define_resource valida nombre, tabla y campos (tipos SQLite validos,
  nombres reservados, duplicados) antes de retornar el CRUDResource.
- crud_generate_table_sql genera el DDL CREATE TABLE IF NOT EXISTS con
  id TEXT PRIMARY KEY, timestamps estandar y, si aplica, deleted_at para
  soft delete.

Primera capa de 0021 — el resto (handlers + registro de rutas) se apoya
sobre estas estructuras.
This commit is contained in:
2026-04-18 17:15:21 +02:00
parent 3bc2d2573d
commit 4c88adc183
10 changed files with 562 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
---
name: CRUDField
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: |
type CRUDField struct {
Name string
Type string
Required bool
Unique bool
Default string
Validations map[string]string
}
description: "Define un campo de un recurso CRUD con su tipo SQLite, constraints y validaciones. Se agrega al slice Fields de CRUDResource."
tags: [crud, field, sqlite, validation, rest, infra]
uses_types: []
file_path: "functions/infra/crud_resource.go"
---
## Ejemplo
```go
field := CRUDField{
Name: "name",
Type: "TEXT",
Required: true,
Unique: true,
Validations: map[string]string{
"min_length": "1",
"max_length": "100",
},
}
```
## Notas
Tipo producto. Type debe ser uno de: TEXT, INTEGER, REAL, BLOB. Default es SQL literal (por ejemplo "'active'" o "0"). Validations es un mapa generico con claves: min_length, max_length, pattern (TEXT), min, max (INTEGER/REAL), enum ("a,b,c" para TEXT). Las validaciones se evaluan en Go antes de cada INSERT/UPDATE.
+35
View File
@@ -0,0 +1,35 @@
---
name: CRUDListParams
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: |
type CRUDListParams struct {
Page int
PerPage int
SortBy string
SortDir string
Filters map[string]string
}
description: "Parametros de paginacion, orden y filtrado de un endpoint list CRUD. Se extrae de los query params de la request HTTP."
tags: [crud, list, pagination, filter, sort, infra]
uses_types: []
file_path: "functions/infra/crud_resource.go"
---
## Ejemplo
```go
params := CRUDListParams{
Page: 1,
PerPage: 20,
SortBy: "created_at",
SortDir: "desc",
Filters: map[string]string{"status": "active"},
}
```
## Notas
Tipo producto. Page es 1-based con default 1. PerPage tiene default 20 y se satura a 100. SortDir solo acepta "asc" o "desc" (default "desc"). Filters usa igualdad exacta en WHERE — los campos se validan contra la definicion del recurso para evitar SQL injection.
+35
View File
@@ -0,0 +1,35 @@
---
name: CRUDListResult
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: |
type CRUDListResult struct {
Items []map[string]any `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
}
description: "Resultado paginado de un endpoint list CRUD. Incluye los items de la pagina y metadatos de paginacion para el cliente."
tags: [crud, list, pagination, result, infra]
uses_types: []
file_path: "functions/infra/crud_resource.go"
---
## Ejemplo
```go
result := CRUDListResult{
Items: []map[string]any{{"id": "a", "name": "X"}},
Total: 42,
Page: 1,
PerPage: 20,
TotalPages: 3,
}
```
## Notas
Tipo producto. Items es la lista de registros de la pagina actual serializados como map[string]any (schema dinamico). Total es el conteo global sin paginacion. TotalPages = ceil(Total / PerPage). Se serializa directamente como JSON con HTTPJSONResponse.
+36
View File
@@ -0,0 +1,36 @@
---
name: CRUDResource
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: |
type CRUDResource struct {
Name string
Table string
Fields []CRUDField
SoftDelete bool
}
description: "Define un recurso CRUD completo (nombre, tabla, campos y modo de borrado) para generar handlers HTTP y SQL DDL. Se usa como input de crud_generate_table_sql y crud_generate_handlers."
tags: [crud, resource, http, sqlite, rest, infra]
uses_types: [CRUDField_go_infra]
file_path: "functions/infra/crud_resource.go"
---
## Ejemplo
```go
res := CRUDResource{
Name: "project",
Table: "projects",
Fields: []CRUDField{
{Name: "name", Type: "TEXT", Required: true, Unique: true},
{Name: "description", Type: "TEXT"},
},
SoftDelete: false,
}
```
## Notas
Tipo producto. Name es el singular en snake_case ("project"), Table es el plural SQL ("projects"). Fields no incluye id ni timestamps: esos los gestiona el generador. Si SoftDelete es true, la tabla tendra una columna deleted_at TEXT y el handler delete hara UPDATE en vez de DELETE.