Runtime
Deno 2.1.4
Permisos
Explícitos ✓
Persistencia
Deno KV
📄
cultivaia-skills-api / src / main.ts
Raw
Copy
▶ Run
main.ts ts
deno.json json
main_test.ts ts
seed.ts ts
1// CULTIVA IA — Skills API · Deno 2.x + KV2// Run: deno task dev3// Deploy: deployctl deploy --project=cultiva-skills main.ts45// ── Types ──────────────────────────────────────────────────────────6interface Skill {7 id: string; // uuid v48 slug: string;9 titulo: string;10 servicio: "Web" | "Marketing" | "Contenido" | "Automatizacion" | "Datos" | "Agentes";11 nivel: "basico" | "intermedio" | "avanzado";12 activo: boolean;13 creadoEn: string; // ISO-860114}1516// ── KV helpers ────────────────────────────────────────────────────17const kv = await Deno.openKv();1819async function getSkill(id: string): Promise<Skill | null> {20 const entry = await kv.get<Skill>(["skills", id]);21 return entry.value;22}2324async function listSkills(servicio?: string): Promise<Skill[]> {25 const entries = kv.list<Skill>({ prefix: ["skills"] });26 const result: Skill[] = [];27 for await (const entry of entries) {28 if (!servicio || entry.value.servicio === servicio)29 result.push(entry.value);30 }31 return result;32}3334async function createSkill(data: Omit<Skill, "id" | "creadoEn">): Promise<Skill> {35 const skill: Skill = {36 id: crypto.randomUUID(),37 creadoEn: new Date().toISOString(),38 ...data,39 };40 await kv.atomic()41 .check({ key: ["skills", skill.id], versionstamp: null })42 .set(["skills", skill.id], skill)43 .set(["by_servicio", skill.servicio, skill.id], skill)44 .commit();45 return skill;46}4748// ── Router ────────────────────────────────────────────────────────49Deno.serve({ port: 8000 }, async (req: Request) => {50 const url = new URL(req.url);51 const path = url.pathname;52 const method = req.method;53 const headers = { "Content-Type": "application/json" };5455 // GET /health56 if (path === "/health")57 return Response.json({ status: "ok", runtime: "deno", v: Deno.version.deno });5859 // GET /skills[?servicio=Web]60 if (method === "GET" && path === "/skills") {61 const srv = url.searchParams.get("servicio") ?? undefined;62 const skills = await listSkills(srv);63 return Response.json({ data: skills, total: skills.length });64 }6566 // GET /skills/:id67 const idMatch = path.match(/^\/skills\/([a-z0-9-]+)$/);68 if (idMatch && method === "GET") {69 const skill = await getSkill(idMatch[1]);70 if (!skill) return Response.json({ error: "not_found" }, { status: 404 });71 return Response.json({ data: skill });72 }7374 // POST /skills75 if (method === "POST" && path === "/skills") {76 const body = await req.json();77 const skill = await createSkill(body);78 return Response.json({ data: skill }, { status: 201 });79 }8081 // DELETE /skills/:id82 if (idMatch && method === "DELETE") {82 await kv.delete(["skills", idMatch[1]]);83 return new Response(null, { status: 204 });84 }8586 return new Response("Not Found", { status: 404 });87});
⬡ Endpoints de la API
| Método | Ruta | Descripción | Respuesta |
|---|---|---|---|
| GET | /health | Estado del servidor y versión Deno | 200 |
| GET | /skills | Listar todas las skills (+ filtro ?servicio=Web) |
200 |
| GET | /skills/:id | Obtener una skill por UUID | 200 404 |
| POST | /skills | Crear nueva skill en KV con UUID autogenerado | 201 |
| DELETE | /skills/:id | Eliminar skill del KV store | 204 |
🔒 Modelo de permisos — deno.json
--allow-net
:8000
--allow-env
DENO_KV_URL,PORT
--allow-read
./data
--allow-write
no necesario
--allow-run
no necesario
--allow-all / -A
NUNCA en prod
◈ Estructura de Deno KV
// Clave jerárquica — acceso O(1) y scan por prefijo
["skills", "8c541a93-..."] → { Skill }
// Índice secundario para filtrar por servicio
["by_servicio", "Web", "8c541a93-..."] → { Skill }
// atomic().check().set().set().commit() garantiza consistencia
["skills", "8c541a93-..."] → { Skill }
["skills", "3d7f2b01-..."] → { Skill }
["skills", "a1c902ef-..."] → { Skill }
// Índice secundario para filtrar por servicio
["by_servicio", "Web", "8c541a93-..."] → { Skill }
["by_servicio", "Marketing", "3d7f2b01-..."] → { Skill }
["by_servicio", "Agentes", "a1c902ef-..."] → { Skill }
// atomic().check().set().set().commit() garantiza consistencia
$ Comandos — desarrollo, test y deploy
$ deno task dev
Task dev deno run --allow-net=:8000 --allow-env=DENO_KV_URL,PORT --allow-read=./data --watch src/main.ts
Listening on http://localhost:8000/
$ curl http://localhost:8000/health
{
"status": "ok",
"runtime": "deno",
"v": "2.1.4"
}
$ curl -X POST http://localhost:8000/skills \
-H "Content-Type: application/json" \
-d '{"slug":"deno-kv-crud","titulo":"API CRUD con Deno KV","servicio":"Web","nivel":"intermedio","activo":true}'
{
"data": {
"id": "8c541a93-1234-4abc-b678-fea290d1e3f0",
"slug": "deno-kv-crud",
"servicio": "Web",
"nivel": "intermedio",
"activo": true,
"creadoEn": "2026-06-16T10:34:22.001Z"
}
}
$ deno test --allow-net --allow-env --allow-read
running 4 tests from ./main_test.ts
✓ GET /health returns ok (12ms)
✓ POST /skills creates skill (34ms)
✓ GET /skills?servicio=Web returns filtered (28ms)
✓ GET /skills/:id 404 on missing (5ms)
ok | 4 passed | 0 failed (82ms)
$ deployctl deploy --project=cultiva-skills main.ts
✓ Deployed to https://cultiva-skills.deno.dev
Task dev deno run --allow-net=:8000 --allow-env=DENO_KV_URL,PORT --allow-read=./data --watch src/main.ts
Listening on http://localhost:8000/
$ curl http://localhost:8000/health
{
"status": "ok",
"runtime": "deno",
"v": "2.1.4"
}
$ curl -X POST http://localhost:8000/skills \
-H "Content-Type: application/json" \
-d '{"slug":"deno-kv-crud","titulo":"API CRUD con Deno KV","servicio":"Web","nivel":"intermedio","activo":true}'
{
"data": {
"id": "8c541a93-1234-4abc-b678-fea290d1e3f0",
"slug": "deno-kv-crud",
"servicio": "Web",
"nivel": "intermedio",
"activo": true,
"creadoEn": "2026-06-16T10:34:22.001Z"
}
}
$ deno test --allow-net --allow-env --allow-read
running 4 tests from ./main_test.ts
✓ GET /health returns ok (12ms)
✓ POST /skills creates skill (34ms)
✓ GET /skills?servicio=Web returns filtered (28ms)
✓ GET /skills/:id 404 on missing (5ms)
ok | 4 passed | 0 failed (82ms)
$ deployctl deploy --project=cultiva-skills main.ts
✓ Deployed to https://cultiva-skills.deno.dev