nicola-framework 1.0.4 → 1.0.5
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 +391 -108
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -10,30 +10,60 @@ Nicola expone un **servidor HTTP nativo** con un **router tipo Express** y utili
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 📌 Índice
|
|
14
|
+
|
|
15
|
+
- [Qué incluye](#-qué-incluye)
|
|
16
|
+
- [Instalación](#-instalación)
|
|
17
|
+
- [Crear un proyecto (CLI)](#-crear-un-proyecto-cli)
|
|
18
|
+
- [Levantar el servidor (dev)](#-levantar-el-servidor-dev)
|
|
19
|
+
- [Quickstart (manual)](#-quickstart-manual)
|
|
20
|
+
- [Guía del Router](#-guía-del-router)
|
|
21
|
+
- [Request/Response (lo que hay)](#-requestresponse-lo-que-hay)
|
|
22
|
+
- [Manejo de errores](#-manejo-de-errores)
|
|
23
|
+
- [Middlewares](#-middlewares)
|
|
24
|
+
- [Seguridad (Regulator + JWT)](#-seguridad-regulator--jwt)
|
|
25
|
+
- [Dynamo ORM (Postgres)](#-dynamo-orm-postgres)
|
|
26
|
+
- [Variables de entorno](#-variables-de-entorno)
|
|
27
|
+
- [Tests](#-tests)
|
|
28
|
+
- [Troubleshooting](#-troubleshooting)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✅ Qué incluye
|
|
33
|
+
|
|
34
|
+
Esta lista está alineada con el **código actual** del repositorio:
|
|
14
35
|
|
|
15
36
|
- **Core/Router**: `Nicola` (default) extiende `Remote`.
|
|
16
37
|
- **Body parsing**: JSON si `Content-Type` incluye `application/json` (límite ~2MB). Si no, `req.body = {}`.
|
|
17
|
-
- **Helpers de response**: `res.json(data)` y `res.send(text)`.
|
|
18
|
-
-
|
|
38
|
+
- **Helpers de response**: `res.json(data)` y `res.send(text)`.
|
|
39
|
+
- Nota: no existe `res.status()`; usa `res.statusCode`.
|
|
40
|
+
- **CORS**: `EasyCors()` permite `*` y responde `OPTIONS` con `204`.
|
|
19
41
|
- **Security headers**: `Teleforce` aplica headers básicos (no-sniff, frame deny, etc.).
|
|
20
42
|
- **Logger**: `Shadowgraph` loggea al terminar la respuesta.
|
|
21
|
-
- **Errores**: si un handler lanza error o llama `next(err)`, se responde HTML via `BlackBox`.
|
|
22
|
-
-
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
43
|
+
- **Errores**: si un handler lanza error o llama `next(err)`, se responde HTML via `BlackBox`.
|
|
44
|
+
- En `NODE_ENV=production` se ocultan mensaje/stack al cliente.
|
|
45
|
+
- **JWT**: `Coherer` (HS256) via métodos **estáticos** y requiere `NICOLA_SECRET`.
|
|
46
|
+
- **ORM**: `Dynamo` soporta **Postgres** (driver `postgres`). `pg` es dependencia opcional.
|
|
47
|
+
- **Hot reload**: `LiveCurrent` reinicia el proceso Node al detectar cambios (en `process.cwd()`).
|
|
25
48
|
|
|
26
49
|
---
|
|
27
50
|
|
|
28
51
|
## 📦 Instalación
|
|
29
52
|
|
|
53
|
+
Requisitos:
|
|
54
|
+
|
|
55
|
+
- Node.js >= 16
|
|
56
|
+
- Proyecto ESM (Nicola es ESM)
|
|
57
|
+
|
|
58
|
+
Instalar como dependencia del proyecto:
|
|
59
|
+
|
|
30
60
|
```bash
|
|
31
61
|
npm install nicola-framework
|
|
32
62
|
```
|
|
33
63
|
|
|
34
64
|
### (Opcional) Postgres
|
|
35
65
|
|
|
36
|
-
El dialecto Postgres usa `pg` por import dinámico.
|
|
66
|
+
El dialecto Postgres usa `pg` por import dinámico. Si vas a usar Dynamo con Postgres:
|
|
37
67
|
|
|
38
68
|
```bash
|
|
39
69
|
npm install pg
|
|
@@ -41,150 +71,376 @@ npm install pg
|
|
|
41
71
|
|
|
42
72
|
---
|
|
43
73
|
|
|
44
|
-
##
|
|
74
|
+
## 🧰 Crear un proyecto (CLI)
|
|
75
|
+
|
|
76
|
+
Nicola incluye un CLI con dos comandos:
|
|
77
|
+
|
|
78
|
+
- `init <nombre>`: crea una estructura mínima.
|
|
79
|
+
- `start`: ejecuta `app.js` con hot reload (LiveCurrent).
|
|
45
80
|
|
|
46
|
-
###
|
|
81
|
+
### Opción A: sin instalar global (recomendado)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx nicola init mi-api
|
|
85
|
+
cd mi-api
|
|
86
|
+
npm install
|
|
87
|
+
npm start
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Opción B: instalando global
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install -g nicola-framework
|
|
94
|
+
nicola init mi-api
|
|
95
|
+
cd mi-api
|
|
96
|
+
npm install
|
|
97
|
+
nicola start
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Qué genera `nicola init`
|
|
101
|
+
|
|
102
|
+
La CLI crea:
|
|
103
|
+
|
|
104
|
+
- `app.js`
|
|
105
|
+
- `src/controllers/user.controller.js`
|
|
106
|
+
- `src/routes/user.Routes.js`
|
|
107
|
+
- `package.json` con `"type": "module"` y script `start`.
|
|
108
|
+
|
|
109
|
+
El `app.js` generado monta las rutas así:
|
|
47
110
|
|
|
48
111
|
```js
|
|
49
|
-
import Nicola from
|
|
112
|
+
import Nicola, { Regulator } from "nicola-framework";
|
|
113
|
+
import UserRoute from "./src/routes/user.Routes.js";
|
|
114
|
+
|
|
115
|
+
Regulator.load();
|
|
50
116
|
|
|
51
117
|
const app = new Nicola();
|
|
52
118
|
|
|
53
|
-
app.
|
|
54
|
-
|
|
119
|
+
app.use("/user", UserRoute);
|
|
120
|
+
|
|
121
|
+
app.get("/", (req, res) => {
|
|
122
|
+
res.json({ message: "Bienvenido a tu proyecto en Nicola" });
|
|
55
123
|
});
|
|
56
124
|
|
|
57
125
|
app.listen(3000, () => {
|
|
58
|
-
console.log(
|
|
126
|
+
console.log("Servidor corriendo en http://localhost:3000");
|
|
59
127
|
});
|
|
60
|
-
|
|
61
|
-
// Opcional: timeouts del server (en ms)
|
|
62
|
-
// NICOLA_REQUEST_TIMEOUT=30000
|
|
63
|
-
// NICOLA_HEADERS_TIMEOUT=10000
|
|
64
|
-
// NICOLA_KEEP_ALIVE_TIMEOUT=65000
|
|
65
128
|
```
|
|
66
129
|
|
|
67
|
-
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 🚀 Levantar el servidor (dev)
|
|
133
|
+
|
|
134
|
+
Si tu entrypoint es `app.js` (como genera la CLI), tienes dos opciones:
|
|
135
|
+
|
|
136
|
+
- `npm start` (en proyecto generado)
|
|
137
|
+
- `nicola start` / `npx nicola start`
|
|
138
|
+
|
|
139
|
+
`start` usa LiveCurrent, que:
|
|
140
|
+
|
|
141
|
+
- observa cambios en el directorio actual (recursivo)
|
|
142
|
+
- ignora `node_modules`
|
|
143
|
+
- reinicia el proceso cuando detecta un cambio
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## ⚡ Quickstart (manual)
|
|
148
|
+
|
|
149
|
+
Servidor HTTP básico:
|
|
68
150
|
|
|
69
151
|
```js
|
|
70
|
-
import
|
|
152
|
+
import Nicola from "nicola-framework";
|
|
71
153
|
|
|
72
154
|
const app = new Nicola();
|
|
73
|
-
const api = new Remote();
|
|
74
155
|
|
|
75
|
-
|
|
76
|
-
res.json({
|
|
156
|
+
app.get("/", (req, res) => {
|
|
157
|
+
res.json({ ok: true, message: "Hello from Nicola!" });
|
|
77
158
|
});
|
|
78
159
|
|
|
79
|
-
|
|
80
|
-
|
|
160
|
+
app.listen(3000, () => {
|
|
161
|
+
console.log("Server running on http://localhost:3000");
|
|
81
162
|
});
|
|
163
|
+
```
|
|
82
164
|
|
|
83
|
-
|
|
84
|
-
|
|
165
|
+
Opcional: timeouts del server (en ms). Nicola lee estas variables al llamar `listen()`:
|
|
166
|
+
|
|
167
|
+
```env
|
|
168
|
+
NICOLA_REQUEST_TIMEOUT=30000
|
|
169
|
+
NICOLA_HEADERS_TIMEOUT=10000
|
|
170
|
+
NICOLA_KEEP_ALIVE_TIMEOUT=65000
|
|
85
171
|
```
|
|
86
172
|
|
|
87
173
|
---
|
|
88
174
|
|
|
89
|
-
##
|
|
175
|
+
## 🧭 Guía del Router
|
|
90
176
|
|
|
91
|
-
###
|
|
177
|
+
### 1) Rutas básicas
|
|
92
178
|
|
|
93
|
-
|
|
94
|
-
- `app.get/post/put/patch/delete(path, ...handlers)`
|
|
95
|
-
- `app.use([path], ...handlers | router)`
|
|
96
|
-
- `app.listen(port, [callback])`
|
|
179
|
+
`Nicola` y `Remote` soportan:
|
|
97
180
|
|
|
98
|
-
|
|
99
|
-
- `Nicola.listen()` ejecuta internamente `Shadowgraph`, `EasyCors` y `Teleforce` en cada request.
|
|
100
|
-
- `req.query` se construye desde querystring.
|
|
181
|
+
- `get`, `post`, `put`, `patch`, `delete`
|
|
101
182
|
|
|
102
|
-
|
|
183
|
+
```js
|
|
184
|
+
import Nicola from "nicola-framework";
|
|
103
185
|
|
|
104
|
-
|
|
186
|
+
const app = new Nicola();
|
|
105
187
|
|
|
106
|
-
|
|
188
|
+
app.get("/ping", (req, res) => {
|
|
189
|
+
res.statusCode = 200;
|
|
190
|
+
res.end("pong");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.listen(3000);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 2) Params (`/users/:id`)
|
|
197
|
+
|
|
198
|
+
Cuando la ruta tiene `:param`, Nicola crea:
|
|
199
|
+
|
|
200
|
+
- `req.params` (objeto con strings)
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
app.get("/users/:id", (req, res) => {
|
|
204
|
+
res.json({ id: req.params.id });
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 3) Routers anidados (`use`)
|
|
209
|
+
|
|
210
|
+
Puedes montar un router dentro de otro:
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
import { Nicola, Remote } from "nicola-framework";
|
|
214
|
+
|
|
215
|
+
const app = new Nicola();
|
|
216
|
+
const api = new Remote();
|
|
217
|
+
|
|
218
|
+
api.get("/ping", (req, res) => {
|
|
219
|
+
res.end("pong");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
app.use("/api", api);
|
|
223
|
+
app.listen(3000);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Importante: el mount path es estricto. `/api` hace match con `/api/...` pero NO con `/apix/...`.
|
|
227
|
+
|
|
228
|
+
### 4) Middlewares
|
|
229
|
+
|
|
230
|
+
Un middleware tiene firma `(req, res, next)`:
|
|
107
231
|
|
|
108
232
|
```js
|
|
109
233
|
app.use((req, res, next) => {
|
|
110
|
-
//
|
|
111
|
-
if (req.url ===
|
|
234
|
+
// no existe res.status(); usa res.statusCode
|
|
235
|
+
if (req.url === "/blocked") {
|
|
112
236
|
res.statusCode = 403;
|
|
113
|
-
res.end(
|
|
237
|
+
res.end("Forbidden");
|
|
114
238
|
return;
|
|
115
239
|
}
|
|
116
240
|
next();
|
|
117
241
|
});
|
|
118
242
|
```
|
|
119
243
|
|
|
120
|
-
|
|
244
|
+
Nicola soporta handlers sync y async (Promise). Si un handler async rechaza, el error se propaga a `next(err)`.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 🧾 Request/Response (lo que hay)
|
|
249
|
+
|
|
250
|
+
### Request (`req`)
|
|
251
|
+
|
|
252
|
+
Nicola trabaja sobre `http.IncomingMessage` y añade:
|
|
253
|
+
|
|
254
|
+
- `req.url`: **solo pathname** (sin querystring). Se reescribe internamente.
|
|
255
|
+
- `req.query`: objeto creado desde `?a=1&b=hola`.
|
|
256
|
+
- `req.params`: solo existe en rutas con `:param`.
|
|
257
|
+
- `req.body`: solo se parsea si `Content-Type` incluye `application/json`.
|
|
258
|
+
- inválido => `400 Bad Request: Invalid JSON`
|
|
259
|
+
- > ~2MB => `413 Request Entity Too Large`
|
|
260
|
+
- si no es JSON => `{}`
|
|
261
|
+
|
|
262
|
+
### Response (`res`)
|
|
263
|
+
|
|
264
|
+
Nicola trabaja sobre `http.ServerResponse` y añade helpers:
|
|
265
|
+
|
|
266
|
+
- `res.json(data)` → setea `Content-Type: application/json` y serializa.
|
|
267
|
+
- `res.send(text)` → setea `Content-Type: text/plain`.
|
|
268
|
+
|
|
269
|
+
Para status codes, usa:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
res.statusCode = 201;
|
|
273
|
+
res.json({ created: true });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 💥 Manejo de errores
|
|
279
|
+
|
|
280
|
+
Si ocurre un error en la cadena de handlers:
|
|
281
|
+
|
|
282
|
+
- `throw new Error(...)`
|
|
283
|
+
- o `next(err)`
|
|
284
|
+
|
|
285
|
+
Nicola responde con `BlackBox` (HTML):
|
|
286
|
+
|
|
287
|
+
- en `NODE_ENV=production` el cliente ve `Internal Server Error` sin stack
|
|
288
|
+
- en dev, incluye `err.message` y stack
|
|
289
|
+
|
|
290
|
+
Ejemplo:
|
|
121
291
|
|
|
122
292
|
```js
|
|
123
|
-
app.get(
|
|
124
|
-
throw new Error(
|
|
293
|
+
app.get("/boom", (req, res) => {
|
|
294
|
+
throw new Error("Boom");
|
|
125
295
|
});
|
|
126
296
|
```
|
|
127
297
|
|
|
128
298
|
---
|
|
129
299
|
|
|
130
|
-
##
|
|
300
|
+
## 🧩 Middlewares
|
|
131
301
|
|
|
132
|
-
### `
|
|
302
|
+
### `Insulator(schema)` (validación de body)
|
|
133
303
|
|
|
134
|
-
|
|
304
|
+
Valida que existan campos y que su tipo coincida con `typeof`.
|
|
135
305
|
|
|
136
306
|
```js
|
|
137
|
-
import {
|
|
307
|
+
import { Insulator } from "nicola-framework";
|
|
138
308
|
|
|
139
|
-
|
|
309
|
+
const schema = {
|
|
310
|
+
name: "string",
|
|
311
|
+
age: "number",
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
app.post("/users", Insulator(schema), (req, res) => {
|
|
315
|
+
res.json({ ok: true });
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Respuestas típicas:
|
|
320
|
+
|
|
321
|
+
- falta campo → `400` y mensaje `Falta campo: name`
|
|
322
|
+
- tipo incorrecto → `400` y mensaje `El campo age debe ser number`
|
|
323
|
+
|
|
324
|
+
### `EasyCors(options)`
|
|
325
|
+
|
|
326
|
+
Soporta:
|
|
327
|
+
|
|
328
|
+
- `origin: "*"` (default)
|
|
329
|
+
- `origin: ["https://app.com", "http://localhost:5173"]`
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
import { EasyCors } from "nicola-framework";
|
|
333
|
+
|
|
334
|
+
app.use(EasyCors({ origin: ["https://mi-front.com"] }));
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Nota importante sobre `Nicola.listen()`: internamente siempre ejecuta `EasyCors()` **antes** de tu router.
|
|
338
|
+
|
|
339
|
+
- puedes sobreescribir headers CORS en tus handlers para requests normales
|
|
340
|
+
- pero el preflight `OPTIONS` se resuelve ahí mismo (204), antes de que corran tus rutas
|
|
341
|
+
|
|
342
|
+
### `Teleforce`
|
|
343
|
+
|
|
344
|
+
Agrega headers de seguridad básicos:
|
|
345
|
+
|
|
346
|
+
- `X-Content-Type-Options: nosniff`
|
|
347
|
+
- `X-Frame-Options: Deny`
|
|
348
|
+
- `X-XSS-Protection: 1`
|
|
349
|
+
|
|
350
|
+
### `Shadowgraph`
|
|
351
|
+
|
|
352
|
+
Logger simple al finalizar la respuesta:
|
|
353
|
+
|
|
354
|
+
`[GET] /ruta - 200 OK - 12ms`
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 🔐 Seguridad (Regulator + JWT)
|
|
359
|
+
|
|
360
|
+
### `Regulator.load()` (.env)
|
|
361
|
+
|
|
362
|
+
Lee `.env` desde `process.cwd()` y copia valores a `process.env`.
|
|
363
|
+
|
|
364
|
+
Formato soportado:
|
|
365
|
+
|
|
366
|
+
- `KEY=value`
|
|
367
|
+
- líneas vacías OK
|
|
368
|
+
- comentarios con `#` al inicio
|
|
369
|
+
|
|
370
|
+
Ejemplo:
|
|
371
|
+
|
|
372
|
+
```env
|
|
373
|
+
NICOLA_SECRET=mi-secreto-super-seguro
|
|
374
|
+
NODE_ENV=production
|
|
140
375
|
```
|
|
141
376
|
|
|
142
377
|
### `Coherer` (JWT HS256)
|
|
143
378
|
|
|
144
|
-
`Coherer` es una clase con métodos
|
|
379
|
+
`Coherer` es una clase con métodos estáticos:
|
|
380
|
+
|
|
381
|
+
- `Coherer.sign(payload, { expiresIn })`
|
|
382
|
+
- `Coherer.verify(token)`
|
|
383
|
+
|
|
384
|
+
`expiresIn` soporta formato **número + unidad**:
|
|
385
|
+
|
|
386
|
+
- `10s`, `15m`, `24h`, `7d`, `1y`
|
|
387
|
+
|
|
388
|
+
Ejemplo:
|
|
145
389
|
|
|
146
390
|
```js
|
|
147
|
-
import { Regulator, Coherer } from
|
|
391
|
+
import { Regulator, Coherer } from "nicola-framework";
|
|
148
392
|
|
|
149
393
|
Regulator.load();
|
|
150
394
|
|
|
151
395
|
const token = Coherer.sign(
|
|
152
|
-
{ userId: 123, role:
|
|
153
|
-
{ expiresIn:
|
|
396
|
+
{ userId: 123, role: "admin" },
|
|
397
|
+
{ expiresIn: "24h" }
|
|
154
398
|
);
|
|
155
399
|
|
|
156
400
|
const payload = Coherer.verify(token);
|
|
157
401
|
console.log(payload.userId);
|
|
158
402
|
```
|
|
159
403
|
|
|
160
|
-
|
|
404
|
+
Middleware típico para proteger rutas (Bearer token):
|
|
161
405
|
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
406
|
+
```js
|
|
407
|
+
import { Coherer } from "nicola-framework";
|
|
408
|
+
|
|
409
|
+
const auth = (req, res, next) => {
|
|
410
|
+
const authHeader = req.headers.authorization || "";
|
|
411
|
+
const [, token] = authHeader.split(" ");
|
|
412
|
+
try {
|
|
413
|
+
req.user = Coherer.verify(token);
|
|
414
|
+
next();
|
|
415
|
+
} catch (err) {
|
|
416
|
+
res.statusCode = 401;
|
|
417
|
+
res.end("Unauthorized");
|
|
418
|
+
}
|
|
419
|
+
};
|
|
167
420
|
```
|
|
168
421
|
|
|
169
422
|
---
|
|
170
423
|
|
|
171
|
-
## 🗃️ Dynamo (
|
|
424
|
+
## 🗃️ Dynamo ORM (Postgres)
|
|
172
425
|
|
|
173
|
-
###
|
|
426
|
+
### Conexión
|
|
174
427
|
|
|
175
|
-
`Dynamo.connect()`
|
|
428
|
+
`Dynamo.connect()` no recibe config: lee variables de entorno.
|
|
176
429
|
|
|
177
430
|
```js
|
|
178
|
-
import { Regulator, Dynamo } from
|
|
431
|
+
import { Regulator, Dynamo } from "nicola-framework";
|
|
179
432
|
|
|
180
433
|
Regulator.load();
|
|
181
434
|
await Dynamo.connect();
|
|
182
435
|
|
|
183
|
-
//
|
|
436
|
+
// ... usar modelos/queries ...
|
|
437
|
+
|
|
184
438
|
await Dynamo.disconnect();
|
|
185
439
|
```
|
|
186
440
|
|
|
187
|
-
|
|
441
|
+
### Variables soportadas
|
|
442
|
+
|
|
443
|
+
Mínimo:
|
|
188
444
|
|
|
189
445
|
```env
|
|
190
446
|
DB_DRIVER=postgres
|
|
@@ -193,85 +449,112 @@ DB_PORT=5432
|
|
|
193
449
|
DB_USER=postgres
|
|
194
450
|
DB_PASS=postgres
|
|
195
451
|
DB_NAME=mydb
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Alternativa: `DB_URL` (tiene prioridad sobre las variables separadas):
|
|
196
455
|
|
|
197
|
-
|
|
198
|
-
|
|
456
|
+
```env
|
|
457
|
+
DB_DRIVER=postgres
|
|
458
|
+
DB_URL=postgres://user:pass@localhost:5432/mydb
|
|
199
459
|
```
|
|
200
460
|
|
|
461
|
+
SSL opcional via `DB_SSLMODE`:
|
|
462
|
+
|
|
463
|
+
- `require` → SSL sin verificación estricta
|
|
464
|
+
- `verify-ca` / `verify-full` → SSL con verificación
|
|
465
|
+
- `disable` / `prefer` → sin SSL
|
|
466
|
+
|
|
201
467
|
### Modelos
|
|
202
468
|
|
|
469
|
+
Un modelo es una clase que extiende `Dynamo.Model` y define:
|
|
470
|
+
|
|
471
|
+
- `static tableName` (requerido)
|
|
472
|
+
- `static schema` (opcional, para validar en `create`)
|
|
473
|
+
|
|
203
474
|
```js
|
|
204
|
-
import { Dynamo } from
|
|
475
|
+
import { Dynamo } from "nicola-framework";
|
|
205
476
|
|
|
206
477
|
export default class User extends Dynamo.Model {
|
|
207
|
-
static tableName =
|
|
478
|
+
static tableName = "users";
|
|
208
479
|
|
|
209
480
|
static schema = {
|
|
210
|
-
name: { type:
|
|
211
|
-
email: { type:
|
|
212
|
-
age: { type:
|
|
481
|
+
name: { type: "string", required: true },
|
|
482
|
+
email: { type: "string", required: true },
|
|
483
|
+
age: { type: "number", required: false },
|
|
213
484
|
};
|
|
214
485
|
}
|
|
215
486
|
```
|
|
216
487
|
|
|
217
|
-
###
|
|
488
|
+
### Operaciones comunes
|
|
218
489
|
|
|
219
490
|
```js
|
|
220
|
-
//
|
|
491
|
+
// Obtener todo
|
|
221
492
|
const users = await User.all();
|
|
222
493
|
|
|
223
|
-
// Where
|
|
224
|
-
const active = await User.where(
|
|
494
|
+
// Where (si omites operador, asume '=')
|
|
495
|
+
const active = await User.where("active", true).get();
|
|
225
496
|
|
|
226
|
-
//
|
|
227
|
-
const
|
|
497
|
+
// Select (string con comas o array)
|
|
498
|
+
const names = await User.select("name,email").get();
|
|
228
499
|
|
|
229
|
-
//
|
|
230
|
-
await User.
|
|
231
|
-
await User.where('id', 1).delete();
|
|
500
|
+
// Insert (valida con schema)
|
|
501
|
+
const created = await User.create({ name: "Alice", email: "a@a.com", age: 20 });
|
|
232
502
|
|
|
233
|
-
//
|
|
234
|
-
|
|
503
|
+
// Update / Delete (recomendado: siempre con where)
|
|
504
|
+
await User.where("id", 1).update({ name: "Alice 2" });
|
|
505
|
+
await User.where("id", 1).delete();
|
|
235
506
|
|
|
236
|
-
// Order
|
|
237
|
-
const latest = await User.query().orderBy(
|
|
507
|
+
// Order + limit + offset
|
|
508
|
+
const latest = await User.query().orderBy("id", "DESC").limit(10).offset(0).get();
|
|
238
509
|
```
|
|
239
510
|
|
|
511
|
+
Notas importantes:
|
|
512
|
+
|
|
513
|
+
- `update()` y `delete()` devuelven `count` (rowCount).
|
|
514
|
+
- Evita `User.update({...})` o `User.delete()` sin `where(...)` porque operaría sobre toda la tabla.
|
|
515
|
+
|
|
240
516
|
---
|
|
241
517
|
|
|
242
|
-
##
|
|
518
|
+
## 🌱 Variables de entorno
|
|
243
519
|
|
|
244
|
-
|
|
520
|
+
Nicola lee:
|
|
245
521
|
|
|
246
|
-
|
|
522
|
+
- `NODE_ENV` (`production` activa modo seguro en errores)
|
|
523
|
+
- `NICOLA_SECRET` (JWT)
|
|
524
|
+
- `NICOLA_REQUEST_TIMEOUT`, `NICOLA_HEADERS_TIMEOUT`, `NICOLA_KEEP_ALIVE_TIMEOUT`
|
|
525
|
+
- `DB_DRIVER`, `DB_URL` o `DB_HOST/DB_PORT/DB_USER/DB_PASS/DB_NAME`, `DB_SSLMODE`
|
|
247
526
|
|
|
248
|
-
|
|
249
|
-
import { Insulator } from 'nicola-framework';
|
|
527
|
+
---
|
|
250
528
|
|
|
251
|
-
|
|
252
|
-
name: 'string',
|
|
253
|
-
age: 'number'
|
|
254
|
-
};
|
|
529
|
+
## 🧪 Tests
|
|
255
530
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
531
|
+
Este repo incluye tests con Jest + Supertest.
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
npm test
|
|
259
535
|
```
|
|
260
536
|
|
|
261
|
-
|
|
537
|
+
---
|
|
262
538
|
|
|
263
|
-
|
|
539
|
+
## 🧯 Troubleshooting
|
|
264
540
|
|
|
265
|
-
|
|
541
|
+
### 1) "Please configure, NICOLA_SECRET..."
|
|
266
542
|
|
|
267
|
-
|
|
543
|
+
- define `NICOLA_SECRET` en tu `.env` y corre `Regulator.load()` antes de usar `Coherer`.
|
|
268
544
|
|
|
269
|
-
|
|
270
|
-
import { LiveCurrent } from 'nicola-framework';
|
|
545
|
+
### 2) "Por favor utiliza el comando npm install pg"
|
|
271
546
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
547
|
+
- instala `pg` si vas a usar `DB_DRIVER=postgres`.
|
|
548
|
+
|
|
549
|
+
### 3) El body llega vacío
|
|
550
|
+
|
|
551
|
+
- Nicola solo parsea JSON cuando `Content-Type` incluye `application/json`.
|
|
552
|
+
- `multipart/form-data` y `application/x-www-form-urlencoded` no están soportados (por ahora).
|
|
553
|
+
|
|
554
|
+
### 4) CORS en preflight no aplica como esperas
|
|
555
|
+
|
|
556
|
+
- `Nicola.listen()` ejecuta `EasyCors()` y responde `OPTIONS` con `204` antes de tus rutas.
|
|
557
|
+
- si necesitas lógica avanzada de preflight, usa `Remote` + `http.createServer(...)` y monta tus middlewares manualmente.
|
|
275
558
|
|
|
276
559
|
---
|
|
277
560
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nicola-framework",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Web framework for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"middleware",
|
|
25
25
|
"orm"
|
|
26
26
|
],
|
|
27
|
-
"bin":
|
|
27
|
+
"bin": {
|
|
28
|
+
"nicola": "./bin/nicola.js",
|
|
29
|
+
"nicola-framework": "./bin/nicola.js"
|
|
30
|
+
},
|
|
28
31
|
"author": "Erick Mauricio Tiznado Rodriguez",
|
|
29
32
|
"license": "MIT",
|
|
30
33
|
"devDependencies": {
|