@yrest/cli 0.8.1 → 0.10.0

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 CHANGED
@@ -10,7 +10,7 @@
10
10
  [![CI](https://github.com/aggiovato/yRest/actions/workflows/ci.yml/badge.svg)](https://github.com/aggiovato/yRest/actions)
11
11
  [![Node](https://img.shields.io/node/v/@yrest/cli)](https://www.npmjs.com/package/@yrest/cli)
12
12
  [![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue)](https://www.typescriptlang.org/)
13
- [![Socket](https://badge.socket.dev/npm/package/@yrest/cli/0.8.1)](https://socket.dev/npm/package/@yrest/cli)
13
+ [![Socket](https://badge.socket.dev/npm/package/@yrest/cli/0.10.0)](https://socket.dev/npm/package/@yrest/cli)
14
14
 
15
15
  YAML-powered json-server alternative. Zero-config REST API mock server with full CRUD, relations, filters and snapshots from a `db.yml` file.
16
16
 
@@ -48,28 +48,32 @@ DELETE /users/1 → 200 OK
48
48
 
49
49
  A YAML-first alternative to json-server for frontend development.
50
50
 
51
- | Feature | yrest | json-server |
52
- | -------------------------------------------- | :---: | :---------: |
53
- | YAML database | ✅ | ❌ |
54
- | Zero config | ✅ | ✅ |
55
- | Full CRUD | ✅ | ✅ |
56
- | Field operators (`_gte`, `_like`, `_regex`…) | ✅ | ⚠️ |
57
- | Full-text search | ✅ | ✅ |
58
- | Relations + nested routes | ✅ | |
59
- | Field projection (`_fields`) | ✅ | |
60
- | Pageable mode (envelope response) | ✅ | ❌ |
61
- | Custom static routes (`_routes`) | ✅ | ❌ |
62
- | Template variables in responses | ✅ | ❌ |
63
- | Handler functions (JS logic) | ✅ | ❌ |
64
- | Conditional scenarios (`scenarios:`) | ✅ | ❌ |
65
- | Snapshot endpoints | ✅ | ❌ |
66
- | Config file | ✅ | ⚠️ |
67
- | API overview page (`/_about`) | ✅ | ❌ |
68
- | Watch mode | ✅ | |
69
- | Readonly mode | ✅ | ❌ |
70
- | Atomic writes | ✅ | ✅ |
71
- | TypeScript types | ✅ | ❌ |
72
- | Programmatic API for test frameworks | ✅ | |
51
+ | Feature | yrest | json-server |
52
+ | --------------------------------------------------- | :---: | :---------: |
53
+ | YAML database | ✅ | ❌ |
54
+ | Zero config | ✅ | ✅ |
55
+ | Full CRUD | ✅ | ✅ |
56
+ | Field operators (`_gte`, `_like`, `_regex`…) | ✅ | ⚠️ |
57
+ | Full-text search | ✅ | ✅ |
58
+ | Relations: many2one, one2one, many2many | ✅ | ⚠️ |
59
+ | Nested routes + bidirectional many2many | ✅ | |
60
+ | Auto-embedding (`nested: true`) | ✅ | ❌ |
61
+ | Field projection (`_fields`) | ✅ | ❌ |
62
+ | Pageable mode (envelope response) | ✅ | ❌ |
63
+ | Custom static routes (`_routes`) | ✅ | ❌ |
64
+ | Template variables in responses | ✅ | ❌ |
65
+ | Handler functions (JS logic) | ✅ | ❌ |
66
+ | Conditional scenarios (`scenarios:`) | ✅ | |
67
+ | Snapshot endpoints | ✅ | ❌ |
68
+ | Config file | ✅ | ⚠️ |
69
+ | API overview page (`/_about`) | ✅ | ❌ |
70
+ | Watch mode | ✅ | ✅ |
71
+ | Readonly mode | ✅ | ❌ |
72
+ | Atomic writes | ✅ | |
73
+ | TypeScript types | ✅ | ❌ |
74
+ | Programmatic API for test frameworks | ✅ | ❌ |
75
+ | OpenAPI 3.0 spec (`GET /_openapi`, `yrest openapi`) | ✅ | ❌ |
76
+ | Field annotations (`_schema`) | ✅ | ❌ |
73
77
 
74
78
  ---
75
79
 
@@ -125,15 +129,41 @@ npx @yrest/cli init --file api.yml # custom filename
125
129
  npx @yrest/cli init --sample relational --file api.yml
126
130
  ```
127
131
 
128
- | Flag | Default | Description |
129
- | ---------- | -------- | ----------------------------------- |
130
- | `--file` | `db.yml` | Output filename |
131
- | `--sample` | `basic` | Sample data (`basic`, `relational`) |
132
+ | Flag | Default | Description |
133
+ | ---------- | -------- | ------------------------------------------------ |
134
+ | `--file` | `db.yml` | Output filename |
135
+ | `--sample` | `basic` | Sample data (`basic`, `relational`, `ecommerce`) |
132
136
 
133
137
  **Samples:**
134
138
 
135
- - `basic` — two independent collections: `users` and `products`
136
- - `relational` — three collections with `_rel` relationships: `users`, `posts` and `comments`
139
+ - `basic` — three independent collections: `users`, `products` and `categories`
140
+ - `relational` — blog domain with all three relation types: `users`, `posts`, `comments`, `tags` and a pivot table
141
+ - `ecommerce` — e-commerce domain with products, orders, reviews and custom `_routes` (scenarios, template vars, error injection)
142
+
143
+ ---
144
+
145
+ ### `openapi`
146
+
147
+ Generates an OpenAPI 3.0.3 spec from a `db.yml` file without starting a server.
148
+
149
+ ```bash
150
+ npx @yrest/cli openapi db.yml # writes openapi.yaml
151
+ npx @yrest/cli openapi db.yml --format json # writes openapi.json
152
+ npx @yrest/cli openapi db.yml --stdout # prints to stdout
153
+ npx @yrest/cli openapi db.yml --output api-spec.yaml # custom output path
154
+ ```
155
+
156
+ | Flag | Default | Description |
157
+ | ----------------- | ----------- | ----------------------------------------------------------- |
158
+ | `--output <file>` | _(auto)_ | Output file path (default: `openapi.yaml` / `openapi.json`) |
159
+ | `--format <fmt>` | `yaml` | Output format: `yaml` or `json` |
160
+ | `--stdout` | `false` | Print to stdout instead of writing a file |
161
+ | `--base <base>` | _(none)_ | Base path prefix applied to all routes |
162
+ | `--port <port>` | `3070` | Server port shown in the `servers` block |
163
+ | `--host <host>` | `localhost` | Server host shown in the `servers` block |
164
+ | `--title <title>` | `yRest API` | API title for the `info` block |
165
+
166
+ The spec is also available live at `GET /_openapi` (YAML) and `GET /_openapi.json` (JSON) while the server is running — no extra flag or config needed.
137
167
 
138
168
  ---
139
169
 
@@ -212,6 +242,46 @@ Each top-level key becomes a resource with full CRUD endpoints.
212
242
 
213
243
  ---
214
244
 
245
+ ## Field annotations (`_schema`)
246
+
247
+ Add a `_schema` block to `db.yml` to declare field-level metadata per collection. Used by the OpenAPI generator to produce accurate schemas — has no effect on runtime CRUD behavior.
248
+
249
+ ```yaml
250
+ _schema:
251
+ users:
252
+ name: required # shorthand: marks field as required
253
+ email:
254
+ required: true
255
+ format: email
256
+ age:
257
+ type: integer
258
+ role:
259
+ type: string
260
+ enum: [admin, editor, viewer]
261
+ default: viewer
262
+ description: User role
263
+
264
+ users:
265
+ - id: 1
266
+ name: Ana
267
+ email: ana@test.com
268
+ age: 28
269
+ role: admin
270
+ ```
271
+
272
+ | Key | Type | Description |
273
+ | ------------- | --------- | ----------------------------------------------------------------------------------------- |
274
+ | `required` | `boolean` | Marks the field as required in the OpenAPI schema |
275
+ | `type` | `string` | Overrides the inferred type (`string`, `integer`, `number`, `boolean`, `array`, `object`) |
276
+ | `format` | `string` | OpenAPI format hint (`email`, `date`, `date-time`, `uuid`, `uri`, …) |
277
+ | `enum` | `array` | Restricts the field to a fixed set of values |
278
+ | `description` | `string` | Field description included in the OpenAPI schema |
279
+ | `default` | `any` | Default value shown in the OpenAPI schema |
280
+
281
+ Fields not listed in `_schema` are inferred from the first items in the collection and treated as optional. `_schema` itself is excluded from the generated REST resources.
282
+
283
+ ---
284
+
215
285
  ## Generated endpoints
216
286
 
217
287
  For each resource in `db.yml`:
@@ -392,29 +462,97 @@ Returns the first 5 posts by user 1, sorted alphabetically by title, with the us
392
462
 
393
463
  ## Relational data
394
464
 
395
- Use `_rel` to declare foreign key relationships between collections:
465
+ Use `_rel` to declare relationships between collections. Three relation types are supported.
466
+
467
+ ### `many2one` (default)
468
+
469
+ A child record holds a foreign key pointing to a parent. The string shorthand implicitly declares `many2one`:
396
470
 
397
471
  ```yaml
398
472
  _rel:
399
473
  posts:
400
- userId: users
474
+ userId: users # shorthand: many2one
475
+ # equivalent to:
476
+ # userId:
477
+ # type: many2one
478
+ # target: users
479
+ ```
401
480
 
402
- users:
403
- - id: 1
404
- name: Ana
481
+ ### `one2one`
405
482
 
406
- posts:
483
+ One child record belongs to exactly one parent, and a parent has at most one child:
484
+
485
+ ```yaml
486
+ _rel:
487
+ profiles:
488
+ userId:
489
+ type: one2one
490
+ target: users
491
+ ```
492
+
493
+ ### `many2many`
494
+
495
+ Two collections are linked through a pivot (join) table:
496
+
497
+ ```yaml
498
+ _rel:
499
+ posts:
500
+ tags:
501
+ type: many2many
502
+ target: tags
503
+ through: post_tags # pivot collection
504
+ foreignKey: postId
505
+ otherKey: tagId
506
+
507
+ post_tags:
407
508
  - id: 1
408
- title: First post
409
- userId: 1
509
+ postId: 1
510
+ tagId: 2
511
+ ```
512
+
513
+ ### Auto-embedding with `nested: true`
514
+
515
+ Add `nested: true` to any relation to embed the related data automatically in every GET response, without needing `?_expand` or `?_embed`:
516
+
517
+ ```yaml
518
+ _rel:
519
+ posts:
520
+ userId:
521
+ type: many2one
522
+ target: users
523
+ nested: true # user object embedded in every /posts response
524
+ tags:
525
+ type: many2many
526
+ target: tags
527
+ through: post_tags
528
+ foreignKey: postId
529
+ otherKey: tagId
530
+ nested: true # tags array embedded in every /posts response
531
+ ```
532
+
533
+ ```
534
+ GET /posts/1 → { id: 1, title: "...", userId: 1, user: { ... }, tags: [...] }
410
535
  ```
411
536
 
412
- This enables:
537
+ `many2one` / `one2one` with `nested: true` embed the parent object under the derived key (`userId` → `user`). The original FK field is preserved. `many2many` with `nested: true` embeds the resolved target array under the alias key.
413
538
 
414
- **Nested routes:**
539
+ ### What relations enable
540
+
541
+ **Nested routes (many2one / one2one):**
415
542
 
416
543
  ```
417
- GET /users/1/posts # all posts where userId === 1
544
+ GET /users/1/posts # all posts where userId === 1
545
+ GET /users/1/posts/3 # post 3 only if it belongs to user 1
546
+ GET /users/1/profiles # profile for user 1 (one2one: returns object, not array)
547
+ ```
548
+
549
+ **Nested routes (many2many — bidirectional):**
550
+
551
+ Both directions are registered automatically from a single declaration:
552
+
553
+ ```
554
+ GET /posts/1/tags # all tags linked to post 1 via pivot
555
+ GET /tags/2/posts # all posts linked to tag 2 via pivot (inverse — auto-created)
418
556
  ```
419
557
 
420
558
  **Embed parent with `?_expand`:**
@@ -426,7 +564,9 @@ GET /posts/1?_expand=user → { id: 1, title: "First post", userId: 1, user:
426
564
  **Embed children with `?_embed`:**
427
565
 
428
566
  ```
429
- GET /users/1?_embed=posts → { id: 1, name: "Ana", posts: [{ id: 1, title: "First post", userId: 1 }] }
567
+ GET /users/1?_embed=posts → { id: 1, name: "Ana", posts: [...] }
568
+ GET /posts/1?_embed=tags → { id: 1, title: "...", tags: [...] } # many2many
569
+ GET /users/1?_embed=profiles → { id: 1, name: "Ana", profiles: { ... } } # one2one: object
430
570
  ```
431
571
 
432
572
  ---
@@ -742,15 +882,22 @@ Useful for test suites that need a clean reset between runs or demos that need a
742
882
 
743
883
  ---
744
884
 
745
- ## API overview page
885
+ ## Meta endpoints
886
+
887
+ Every running server exposes the following meta endpoints without any extra configuration:
746
888
 
747
- Every running server exposes `GET /_about` — a self-contained HTML page listing all generated endpoints, custom routes, active modes, query param reference and ready-to-run `curl` examples derived from your actual `db.yml`:
889
+ | Endpoint | Description |
890
+ | -------------------- | ---------------------------------------------------------------------------------------------------- |
891
+ | `GET /_about` | Self-contained HTML page listing all endpoints, modes, query params and ready-to-run `curl` examples |
892
+ | `GET /_openapi` | OpenAPI 3.0.3 spec in YAML format — regenerated on every request |
893
+ | `GET /_openapi.json` | OpenAPI 3.0.3 spec in JSON format |
748
894
 
749
895
  ```bash
750
896
  open http://localhost:3070/_about
897
+ curl http://localhost:3070/_openapi.json | npx swagger-ui-watcher -
751
898
  ```
752
899
 
753
- The page reflects the live state of the server, so it updates automatically in watch mode.
900
+ Both `/_about` and the OpenAPI spec reflect the live storage state and update automatically in watch mode.
754
901
 
755
902
  ---
756
903
 
@@ -992,13 +1139,16 @@ const server = createYrestServer({
992
1139
  | Visual panel (`/_panel`) | 🔜 |
993
1140
  | Programmatic API for Vitest / Playwright | ✅ |
994
1141
  | Docker image | 🔜 |
995
- | OpenAPI export (`yrest openapi db.yml`) | 🔜 |
1142
+ | OpenAPI export (`yrest openapi db.yml`) | |
996
1143
  | VS Code extension with YAML snippets | 🔜 |
997
1144
  | Request validation with JSON Schema | 🔜 |
998
1145
  | Conditional scenarios (`scenarios:`, `otherwise:`) | ✅ |
999
1146
  | Per-route delay (`delay:`) | ✅ |
1000
1147
  | Error injection (`error:` in `_routes`) | ✅ |
1001
1148
  | Configurable ID strategy (`idStrategy`) | ✅ |
1149
+ | Explicit relation types (one2one, many2many) | ✅ |
1150
+ | Bidirectional many2many nested routes | ✅ |
1151
+ | Auto-embedding (`nested: true` in `_rel`) | ✅ |
1002
1152
 
1003
1153
  ---
1004
1154