@aggiovato/yrest 0.2.0 → 0.2.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 +193 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npx @aggiovato/yrest serve db.yml
|
|
|
19
19
|
## Quick start
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
# Create a sample db.yml in the current directory
|
|
22
|
+
# Create a sample db.yml and yrest.config.yml in the current directory
|
|
23
23
|
npx @aggiovato/yrest init
|
|
24
24
|
|
|
25
25
|
# Start the server
|
|
@@ -34,11 +34,15 @@ Resources (base: /):
|
|
|
34
34
|
/posts
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Open `http://localhost:3070/_about` in your browser for a live overview of all generated endpoints.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
37
41
|
## Commands
|
|
38
42
|
|
|
39
43
|
### `init`
|
|
40
44
|
|
|
41
|
-
Creates a sample `db.yml` in the current directory.
|
|
45
|
+
Creates a sample `db.yml` and a `yrest.config.yml` template in the current directory.
|
|
42
46
|
|
|
43
47
|
```bash
|
|
44
48
|
npx @aggiovato/yrest init # basic sample (default)
|
|
@@ -57,22 +61,53 @@ npx @aggiovato/yrest init --sample relational --file api.yml
|
|
|
57
61
|
- `basic` — two independent collections: `users` and `products`
|
|
58
62
|
- `relational` — three collections with `_rel` relationships: `users`, `posts` and `comments`
|
|
59
63
|
|
|
64
|
+
---
|
|
65
|
+
|
|
60
66
|
### `serve`
|
|
61
67
|
|
|
62
68
|
Starts the mock server.
|
|
63
69
|
|
|
64
70
|
```bash
|
|
65
71
|
npx @aggiovato/yrest serve db.yml
|
|
66
|
-
npx @aggiovato/yrest serve db.yml --port 3001
|
|
67
|
-
npx @aggiovato/yrest serve db.yml --
|
|
68
|
-
npx @aggiovato/yrest serve db.yml --
|
|
72
|
+
npx @aggiovato/yrest serve db.yml --port 3001 --host 0.0.0.0
|
|
73
|
+
npx @aggiovato/yrest serve db.yml --base /api --watch
|
|
74
|
+
npx @aggiovato/yrest serve db.yml --readonly --delay 300
|
|
75
|
+
npx @aggiovato/yrest serve db.yml --pageable 20
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Flag | Default | Description |
|
|
79
|
+
| ---------------- | ----------- | ----------------------------------------------------------------------- |
|
|
80
|
+
| `--port` | `3070` | Port to listen on |
|
|
81
|
+
| `--host` | `localhost` | Host to bind |
|
|
82
|
+
| `--base` | _(none)_ | Prefix for all routes (e.g. `/api`) |
|
|
83
|
+
| `--watch` | `false` | Reload `db.yml` automatically when it changes on disk |
|
|
84
|
+
| `--readonly` | `false` | Reject all write operations (POST, PUT, PATCH, DELETE) with `405` |
|
|
85
|
+
| `--delay <ms>` | `0` | Add a fixed delay to all responses (simulates network latency) |
|
|
86
|
+
| `--pageable [n]` | `false` | Wrap GET collection responses in `{ data, pagination }`. Optional limit |
|
|
87
|
+
|
|
88
|
+
All flags can also be set in `yrest.config.yml` (see below). CLI flags always take priority over the config file.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Configuration file
|
|
93
|
+
|
|
94
|
+
`yrest init` creates a `yrest.config.yml` alongside `db.yml`. Options defined here apply every time you run `serve` without needing to type flags:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
# yrest.config.yml
|
|
98
|
+
file: db.yml
|
|
99
|
+
port: 3070
|
|
100
|
+
host: localhost
|
|
101
|
+
# base: /api
|
|
102
|
+
# watch: false
|
|
103
|
+
# readonly: false
|
|
104
|
+
# delay: 0
|
|
105
|
+
# pageable: false # true (limit 10), or a number (custom limit)
|
|
69
106
|
```
|
|
70
107
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
| `--host` | `localhost` | Host to bind |
|
|
75
|
-
| `--base` | _(none)_ | Prefix for all routes |
|
|
108
|
+
**Priority order** (highest wins): CLI flags → `yrest.config.yml` → schema defaults.
|
|
109
|
+
|
|
110
|
+
---
|
|
76
111
|
|
|
77
112
|
## Database format
|
|
78
113
|
|
|
@@ -93,6 +128,8 @@ posts:
|
|
|
93
128
|
|
|
94
129
|
Each top-level key becomes a resource with full CRUD endpoints.
|
|
95
130
|
|
|
131
|
+
---
|
|
132
|
+
|
|
96
133
|
## Generated endpoints
|
|
97
134
|
|
|
98
135
|
For each resource in `db.yml`:
|
|
@@ -108,13 +145,15 @@ DELETE /users/:id Delete
|
|
|
108
145
|
|
|
109
146
|
With `--base /api` all routes are prefixed: `/api/users`, `/api/users/:id`, etc.
|
|
110
147
|
|
|
148
|
+
---
|
|
149
|
+
|
|
111
150
|
## Query params
|
|
112
151
|
|
|
113
152
|
All query params can be combined freely.
|
|
114
153
|
|
|
115
154
|
### Filtering
|
|
116
155
|
|
|
117
|
-
Return only items that match
|
|
156
|
+
Return only items that match one or more field values:
|
|
118
157
|
|
|
119
158
|
```txt
|
|
120
159
|
GET /users?name=Ana
|
|
@@ -128,28 +167,78 @@ Comparison is case-sensitive and converts types to string (`?id=1` matches numer
|
|
|
128
167
|
```txt
|
|
129
168
|
GET /users?_sort=name # ascending (default)
|
|
130
169
|
GET /users?_sort=name&_order=desc # descending
|
|
131
|
-
GET /users?_sort=id&_order=asc
|
|
132
170
|
```
|
|
133
171
|
|
|
134
172
|
String fields are compared case-insensitively. Items missing the sort field are pushed to the end.
|
|
135
173
|
|
|
136
174
|
### Pagination
|
|
137
175
|
|
|
176
|
+
**Without `--pageable`** (default):
|
|
177
|
+
|
|
138
178
|
```txt
|
|
139
179
|
GET /users?_page=1&_limit=10 # page 1, 10 items per page
|
|
140
180
|
GET /users?_limit=5 # first 5 items
|
|
141
|
-
GET /users?_page=2&_limit=5 # items 6–10
|
|
142
181
|
```
|
|
143
182
|
|
|
144
|
-
When
|
|
183
|
+
When `_page` or `_limit` are used, the response includes an `X-Total-Count` header with the total number of items before pagination.
|
|
184
|
+
|
|
185
|
+
**With `--pageable`** (or `pageable: true` in config):
|
|
186
|
+
|
|
187
|
+
Every GET collection response is automatically wrapped in a `{ data, pagination }` envelope:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npx @aggiovato/yrest serve db.yml --pageable # default limit: 10
|
|
191
|
+
npx @aggiovato/yrest serve db.yml --pageable 20 # custom limit: 20
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"data": [
|
|
197
|
+
{ "id": 1, "name": "Ana" },
|
|
198
|
+
{ "id": 2, "name": "Luis" }
|
|
199
|
+
],
|
|
200
|
+
"pagination": {
|
|
201
|
+
"page": 1,
|
|
202
|
+
"limit": 10,
|
|
203
|
+
"totalItems": 23,
|
|
204
|
+
"totalPages": 3,
|
|
205
|
+
"isFirst": true,
|
|
206
|
+
"isLast": false,
|
|
207
|
+
"hasNext": true,
|
|
208
|
+
"hasPrev": false
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The `?_page` and `?_limit` query params still work in pageable mode to navigate pages.
|
|
214
|
+
|
|
215
|
+
### Relation embedding (`?_expand`)
|
|
216
|
+
|
|
217
|
+
Embed a related parent object directly into the response using the `_rel` block (see [Relational data](#relational-data)):
|
|
218
|
+
|
|
219
|
+
```txt
|
|
220
|
+
GET /posts?_expand=user # embed user object in each post
|
|
221
|
+
GET /posts/1?_expand=user # embed in a single item
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Both syntaxes are supported:
|
|
225
|
+
|
|
226
|
+
```txt
|
|
227
|
+
?_expand=author,category # comma-separated
|
|
228
|
+
?_expand=author&_expand=category # repeated param
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Unresolvable keys are silently ignored. Works on all operations: GET, POST, PUT, PATCH, DELETE.
|
|
145
232
|
|
|
146
233
|
### Combined example
|
|
147
234
|
|
|
148
235
|
```txt
|
|
149
|
-
GET /
|
|
236
|
+
GET /posts?userId=1&_sort=title&_order=asc&_page=1&_limit=5&_expand=user
|
|
150
237
|
```
|
|
151
238
|
|
|
152
|
-
Returns the first 5
|
|
239
|
+
Returns the first 5 posts by user 1, sorted alphabetically by title, with the user object embedded.
|
|
240
|
+
|
|
241
|
+
---
|
|
153
242
|
|
|
154
243
|
## Relational data
|
|
155
244
|
|
|
@@ -170,12 +259,66 @@ posts:
|
|
|
170
259
|
userId: 1
|
|
171
260
|
```
|
|
172
261
|
|
|
173
|
-
This enables
|
|
262
|
+
This enables:
|
|
263
|
+
|
|
264
|
+
**Nested routes:**
|
|
174
265
|
|
|
175
266
|
```txt
|
|
176
267
|
GET /users/1/posts # all posts where userId === 1
|
|
177
268
|
```
|
|
178
269
|
|
|
270
|
+
**Relation embedding with `?_expand`:**
|
|
271
|
+
|
|
272
|
+
```txt
|
|
273
|
+
GET /posts/1?_expand=user → { id: 1, title: "First post", userId: 1, user: { id: 1, name: "Ana" } }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Server modes
|
|
279
|
+
|
|
280
|
+
### Watch mode
|
|
281
|
+
|
|
282
|
+
Automatically reloads `db.yml` when it changes on disk — useful when you edit the file manually while the server is running:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npx @aggiovato/yrest serve db.yml --watch
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
> **Note:** Watch mode reloads data in existing collections. Adding or removing entire collections requires a server restart.
|
|
289
|
+
|
|
290
|
+
### Readonly mode
|
|
291
|
+
|
|
292
|
+
Rejects all write operations with `405 Method Not Allowed`:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npx @aggiovato/yrest serve db.yml --readonly
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Useful to expose a stable read-only snapshot for demos or CI environments.
|
|
299
|
+
|
|
300
|
+
### Delay mode
|
|
301
|
+
|
|
302
|
+
Adds a fixed delay (in milliseconds) to every response to simulate real network latency:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
npx @aggiovato/yrest serve db.yml --delay 500 # 500ms on every response
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## API overview page
|
|
311
|
+
|
|
312
|
+
Every running server exposes `GET /_about` — a self-contained HTML page listing all generated endpoints, active modes, query param reference and ready-to-run `curl` examples derived from your actual `db.yml`:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
open http://localhost:3070/_about
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The page reflects the live state of the server, so it updates automatically in watch mode.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
179
322
|
## HTTP responses
|
|
180
323
|
|
|
181
324
|
| Status | When |
|
|
@@ -183,9 +326,12 @@ GET /users/1/posts # all posts where userId === 1
|
|
|
183
326
|
| `200` | Successful GET, PUT, PATCH, DELETE |
|
|
184
327
|
| `201` | Successful POST |
|
|
185
328
|
| `404` | Resource or id not found |
|
|
329
|
+
| `405` | Write operation in readonly mode |
|
|
186
330
|
| `500` | Error reading or writing the YAML file |
|
|
187
331
|
|
|
188
|
-
DELETE returns the deleted item
|
|
332
|
+
DELETE returns the deleted item as confirmation.
|
|
333
|
+
|
|
334
|
+
---
|
|
189
335
|
|
|
190
336
|
## ID generation
|
|
191
337
|
|
|
@@ -199,15 +345,21 @@ All write operations (POST, PUT, PATCH, DELETE) are saved back to `db.yml` immed
|
|
|
199
345
|
|
|
200
346
|
CORS is enabled by default, so you can call the API from any frontend running on a different port without extra configuration.
|
|
201
347
|
|
|
348
|
+
---
|
|
349
|
+
|
|
202
350
|
## Frontend usage
|
|
203
351
|
|
|
204
352
|
```ts
|
|
205
|
-
// List
|
|
353
|
+
// List all
|
|
206
354
|
const users = await fetch("http://localhost:3070/users").then((r) => r.json());
|
|
207
355
|
|
|
208
356
|
// Filter + sort + paginate
|
|
209
357
|
const res = await fetch("http://localhost:3070/users?role=admin&_sort=name&_page=1&_limit=10");
|
|
210
358
|
|
|
359
|
+
// Embed related object
|
|
360
|
+
const post = await fetch("http://localhost:3070/posts/1?_expand=user").then((r) => r.json());
|
|
361
|
+
// → { id: 1, title: "...", userId: 1, user: { id: 1, name: "Ana" } }
|
|
362
|
+
|
|
211
363
|
// Create
|
|
212
364
|
await fetch("http://localhost:3070/users", {
|
|
213
365
|
method: "POST",
|
|
@@ -231,7 +383,8 @@ await fetch("http://localhost:3070/users/1", { method: "DELETE" });
|
|
|
231
383
|
```json
|
|
232
384
|
{
|
|
233
385
|
"scripts": {
|
|
234
|
-
"mock": "yrest serve db.yml"
|
|
386
|
+
"mock": "yrest serve db.yml",
|
|
387
|
+
"mock:watch": "yrest serve db.yml --watch"
|
|
235
388
|
}
|
|
236
389
|
}
|
|
237
390
|
```
|
|
@@ -251,14 +404,17 @@ Run `task --list` to see all available commands.
|
|
|
251
404
|
|
|
252
405
|
#### Development
|
|
253
406
|
|
|
254
|
-
| Command
|
|
255
|
-
|
|
|
256
|
-
| `task test`
|
|
257
|
-
| `task test:watch`
|
|
258
|
-
| `task build`
|
|
259
|
-
| `task dev`
|
|
260
|
-
| `task serve:dist`
|
|
261
|
-
| `task serve:
|
|
407
|
+
| Command | What it does |
|
|
408
|
+
| ----------------------- | ---------------------------------------------------------------------- |
|
|
409
|
+
| `task test` | Runs the full test suite once |
|
|
410
|
+
| `task test:watch` | Runs tests in watch mode — reruns on every file change |
|
|
411
|
+
| `task build` | Compiles TypeScript to `dist/` via tsup |
|
|
412
|
+
| `task dev` | Builds once, then starts watch-build + server from `dist/` in parallel |
|
|
413
|
+
| `task serve:dist` | Builds and starts the server from the local `dist/` |
|
|
414
|
+
| `task serve:dist:watch` | Builds and starts the server with `--watch` (reloads db.yml on change) |
|
|
415
|
+
| `task serve:npx` | Starts the published npm version (useful to compare against local) |
|
|
416
|
+
| `task serve:npx:watch` | Starts the published npm version with `--watch` |
|
|
417
|
+
| `task preflight` | Full pre-push check: format, lint, typecheck and tests in order |
|
|
262
418
|
|
|
263
419
|
#### Release
|
|
264
420
|
|
|
@@ -280,14 +436,12 @@ task dev # keep this running in another terminal
|
|
|
280
436
|
|
|
281
437
|
`test:watch` reruns the suite on every save so you catch regressions immediately. `dev` rebuilds and serves so you can call the endpoints manually while you work.
|
|
282
438
|
|
|
283
|
-
**Before
|
|
439
|
+
**Before pushing:**
|
|
284
440
|
|
|
285
441
|
```bash
|
|
286
|
-
task
|
|
442
|
+
task preflight # format + lint + typecheck + tests in one command
|
|
287
443
|
```
|
|
288
444
|
|
|
289
|
-
The test suite covers all routes, filters, sorting, pagination and CRUD operations. Do not commit with failing tests.
|
|
290
|
-
|
|
291
445
|
**Good practices:**
|
|
292
446
|
|
|
293
447
|
- Write tests for every new feature or bug fix before opening a PR.
|
|
@@ -300,8 +454,8 @@ The test suite covers all routes, filters, sorting, pagination and CRUD operatio
|
|
|
300
454
|
Releases are fully automated via GitHub Actions once a version tag is pushed:
|
|
301
455
|
|
|
302
456
|
```bash
|
|
303
|
-
# 1. Make sure
|
|
304
|
-
task
|
|
457
|
+
# 1. Make sure everything is clean
|
|
458
|
+
task preflight
|
|
305
459
|
|
|
306
460
|
# 2. Bump the version (choose one)
|
|
307
461
|
task release:patch # bug fixes
|
|
@@ -312,10 +466,10 @@ task release:major # breaking changes
|
|
|
312
466
|
git push && git push --tags
|
|
313
467
|
```
|
|
314
468
|
|
|
315
|
-
Step 3 triggers two GitHub Actions pipelines automatically
|
|
469
|
+
Step 3 triggers two GitHub Actions pipelines automatically:
|
|
316
470
|
|
|
317
471
|
- **CI** — runs on every push to `main` and on every PR. Executes typecheck + tests on Node 20 and Node 22.
|
|
318
|
-
- **Publish** — runs only when a `v*` tag is pushed. Runs tests + build and publishes to npm using Trusted Publishing (no tokens stored as secrets).
|
|
472
|
+
- **Publish** — runs only when a `v*` tag is pushed. Runs tests + build and publishes to npm using Trusted Publishing (OIDC — no tokens stored as secrets).
|
|
319
473
|
|
|
320
474
|
### CI/CD pipelines
|
|
321
475
|
|
|
@@ -324,15 +478,15 @@ push to main / PR open
|
|
|
324
478
|
│
|
|
325
479
|
▼
|
|
326
480
|
[CI workflow]
|
|
327
|
-
├── Node 20: typecheck + tests
|
|
328
|
-
└── Node 22: typecheck + tests
|
|
481
|
+
├── Node 20: lint + format check + typecheck + tests
|
|
482
|
+
└── Node 22: lint + format check + typecheck + tests
|
|
329
483
|
|
|
330
484
|
push tag v*
|
|
331
485
|
│
|
|
332
486
|
▼
|
|
333
487
|
[Publish workflow]
|
|
334
488
|
├── tests + build (via prepublishOnly)
|
|
335
|
-
└── npm publish --provenance (via Trusted Publishing / OIDC)
|
|
489
|
+
└── npm publish --provenance (via Trusted Publishing / OIDC, Node 24)
|
|
336
490
|
```
|
|
337
491
|
|
|
338
492
|
---
|