@yrest/cli 0.9.0 → 0.11.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
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="assets/yRest-banner.png" alt="yRest" width="100%" />
2
+ <img src="assets/yrest-banner.png" alt="yRest" width="100%" />
3
3
  </div>
4
4
 
5
5
  <br/>
@@ -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.9.0)](https://socket.dev/npm/package/@yrest/cli)
13
+ [![Socket](https://badge.socket.dev/npm/package/@yrest/cli/0.11.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,30 +48,33 @@ 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: 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 | ✅ | ❌ |
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`) | ✅ | ❌ |
77
+ | Compact relation DSL (`m2o:target@fk[card]+nested`) | ✅ | ❌ |
75
78
 
76
79
  ---
77
80
 
@@ -140,6 +143,31 @@ npx @yrest/cli init --sample relational --file api.yml
140
143
 
141
144
  ---
142
145
 
146
+ ### `openapi`
147
+
148
+ Generates an OpenAPI 3.0.3 spec from a `db.yml` file without starting a server.
149
+
150
+ ```bash
151
+ npx @yrest/cli openapi db.yml # writes openapi.yaml
152
+ npx @yrest/cli openapi db.yml --format json # writes openapi.json
153
+ npx @yrest/cli openapi db.yml --stdout # prints to stdout
154
+ npx @yrest/cli openapi db.yml --output api-spec.yaml # custom output path
155
+ ```
156
+
157
+ | Flag | Default | Description |
158
+ | ----------------- | ----------- | ----------------------------------------------------------- |
159
+ | `--output <file>` | _(auto)_ | Output file path (default: `openapi.yaml` / `openapi.json`) |
160
+ | `--format <fmt>` | `yaml` | Output format: `yaml` or `json` |
161
+ | `--stdout` | `false` | Print to stdout instead of writing a file |
162
+ | `--base <base>` | _(none)_ | Base path prefix applied to all routes |
163
+ | `--port <port>` | `3070` | Server port shown in the `servers` block |
164
+ | `--host <host>` | `localhost` | Server host shown in the `servers` block |
165
+ | `--title <title>` | `yRest API` | API title for the `info` block |
166
+
167
+ 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.
168
+
169
+ ---
170
+
143
171
  ### `serve`
144
172
 
145
173
  Starts the mock server.
@@ -215,6 +243,46 @@ Each top-level key becomes a resource with full CRUD endpoints.
215
243
 
216
244
  ---
217
245
 
246
+ ## Field annotations (`_schema`)
247
+
248
+ 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.
249
+
250
+ ```yaml
251
+ _schema:
252
+ users:
253
+ name: required # shorthand: marks field as required
254
+ email:
255
+ required: true
256
+ format: email
257
+ age:
258
+ type: integer
259
+ role:
260
+ type: string
261
+ enum: [admin, editor, viewer]
262
+ default: viewer
263
+ description: User role
264
+
265
+ users:
266
+ - id: 1
267
+ name: Ana
268
+ email: ana@test.com
269
+ age: 28
270
+ role: admin
271
+ ```
272
+
273
+ | Key | Type | Description |
274
+ | ------------- | --------- | ----------------------------------------------------------------------------------------- |
275
+ | `required` | `boolean` | Marks the field as required in the OpenAPI schema |
276
+ | `type` | `string` | Overrides the inferred type (`string`, `integer`, `number`, `boolean`, `array`, `object`) |
277
+ | `format` | `string` | OpenAPI format hint (`email`, `date`, `date-time`, `uuid`, `uri`, …) |
278
+ | `enum` | `array` | Restricts the field to a fixed set of values |
279
+ | `description` | `string` | Field description included in the OpenAPI schema |
280
+ | `default` | `any` | Default value shown in the OpenAPI schema |
281
+
282
+ 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.
283
+
284
+ ---
285
+
218
286
  ## Generated endpoints
219
287
 
220
288
  For each resource in `db.yml`:
@@ -405,10 +473,6 @@ A child record holds a foreign key pointing to a parent. The string shorthand im
405
473
  _rel:
406
474
  posts:
407
475
  userId: users # shorthand: many2one
408
- # equivalent to:
409
- # userId:
410
- # type: many2one
411
- # target: users
412
476
  ```
413
477
 
414
478
  ### `one2one`
@@ -419,8 +483,8 @@ One child record belongs to exactly one parent, and a parent has at most one chi
419
483
  _rel:
420
484
  profiles:
421
485
  userId:
422
- type: one2one
423
- target: users
486
+ _type: one2one
487
+ _target: users
424
488
  ```
425
489
 
426
490
  ### `many2many`
@@ -431,11 +495,11 @@ Two collections are linked through a pivot (join) table:
431
495
  _rel:
432
496
  posts:
433
497
  tags:
434
- type: many2many
435
- target: tags
436
- through: post_tags # pivot collection
437
- foreignKey: postId
438
- otherKey: tagId
498
+ _type: many2many
499
+ _target: tags
500
+ _through: post_tags # pivot collection
501
+ _foreignKey: postId
502
+ _otherKey: tagId
439
503
 
440
504
  post_tags:
441
505
  - id: 1
@@ -443,31 +507,65 @@ post_tags:
443
507
  tagId: 2
444
508
  ```
445
509
 
446
- ### Auto-embedding with `nested: true`
510
+ ### Auto-embedding with `_nested: true`
447
511
 
448
- Add `nested: true` to any relation to embed the related data automatically in every GET response, without needing `?_expand` or `?_embed`:
512
+ Add `_nested: true` to any relation to embed the related data automatically in every GET response, without needing `?_expand` or `?_embed`:
449
513
 
450
514
  ```yaml
451
515
  _rel:
452
516
  posts:
453
517
  userId:
454
- type: many2one
455
- target: users
456
- nested: true # user object embedded in every /posts response
518
+ _type: many2one
519
+ _target: users
520
+ _nested: true # user object embedded in every /posts response
457
521
  tags:
458
- type: many2many
459
- target: tags
460
- through: post_tags
461
- foreignKey: postId
462
- otherKey: tagId
463
- nested: true # tags array embedded in every /posts response
522
+ _type: many2many
523
+ _target: tags
524
+ _through: post_tags
525
+ _foreignKey: postId
526
+ _otherKey: tagId
527
+ _nested: true # tags array embedded in every /posts response
464
528
  ```
465
529
 
466
530
  ```
467
531
  GET /posts/1 → { id: 1, title: "...", userId: 1, user: { ... }, tags: [...] }
468
532
  ```
469
533
 
470
- `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.
534
+ `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.
535
+
536
+ ### Compact DSL syntax
537
+
538
+ Relations can also be declared using a compact string format — useful when you want to express type, target, foreign key and cardinality in a single line:
539
+
540
+ ```yaml
541
+ _rel:
542
+ payments:
543
+ bookingId: "m2o:bookings[1..1->0..n]" # type:target[carDirect->carInverse]
544
+ userId: "m2o:users[1..1->0..n]+nested" # +nested flag auto-embeds on every GET
545
+
546
+ profiles:
547
+ userId: "o2o:users[1..1->1..1]"
548
+
549
+ posts:
550
+ authorId: "m2o:users@userId[1..1->0..n]" # @foreignKey when field name ≠ FK column
551
+
552
+ tags:
553
+ posts: "m2m:posts@post_tags(postId,tagId)[0..n->0..n]" # m2m with pivot
554
+ ```
555
+
556
+ **Type aliases:**
557
+
558
+ | Alias | Full name |
559
+ | ----- | ----------- |
560
+ | `m2o` | `many2one` |
561
+ | `o2o` | `one2one` |
562
+ | `m2m` | `many2many` |
563
+
564
+ **Cardinality notation:** `[carDirect->carInverse]` — format `min..max` where min is `0` or `1` and max is `1` or `n`. Both segments are optional: omitting them applies defaults (`1..1->1..n` for `m2o`, `1..1->1..1` for `o2o`, `0..n->0..n` for `m2m`).
565
+
566
+ **`+nested` flag:** appended at the end of any DSL string, equivalent to `_nested: true`.
567
+
568
+ The three forms — shorthand string, compact DSL, and verbose object — are fully interchangeable and can be mixed freely within the same `_rel` block.
471
569
 
472
570
  ### What relations enable
473
571
 
@@ -815,15 +913,22 @@ Useful for test suites that need a clean reset between runs or demos that need a
815
913
 
816
914
  ---
817
915
 
818
- ## API overview page
916
+ ## Meta endpoints
917
+
918
+ Every running server exposes the following meta endpoints without any extra configuration:
819
919
 
820
- 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`:
920
+ | Endpoint | Description |
921
+ | -------------------- | ---------------------------------------------------------------------------------------------------- |
922
+ | `GET /_about` | Self-contained HTML page listing all endpoints, modes, query params and ready-to-run `curl` examples |
923
+ | `GET /_openapi` | OpenAPI 3.0.3 spec in YAML format — regenerated on every request |
924
+ | `GET /_openapi.json` | OpenAPI 3.0.3 spec in JSON format |
821
925
 
822
926
  ```bash
823
927
  open http://localhost:3070/_about
928
+ curl http://localhost:3070/_openapi.json | npx swagger-ui-watcher -
824
929
  ```
825
930
 
826
- The page reflects the live state of the server, so it updates automatically in watch mode.
931
+ Both `/_about` and the OpenAPI spec reflect the live storage state and update automatically in watch mode.
827
932
 
828
933
  ---
829
934
 
@@ -1065,7 +1170,7 @@ const server = createYrestServer({
1065
1170
  | Visual panel (`/_panel`) | 🔜 |
1066
1171
  | Programmatic API for Vitest / Playwright | ✅ |
1067
1172
  | Docker image | 🔜 |
1068
- | OpenAPI export (`yrest openapi db.yml`) | 🔜 |
1173
+ | OpenAPI export (`yrest openapi db.yml`) | ✅ |
1069
1174
  | VS Code extension with YAML snippets | 🔜 |
1070
1175
  | Request validation with JSON Schema | 🔜 |
1071
1176
  | Conditional scenarios (`scenarios:`, `otherwise:`) | ✅ |
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ <svg width="314" height="123" viewBox="0 0 314 123" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 33.1934H18.9219L41.8701 76.3274L56.5276 47.3956C56.5276 47.3956 59.2983 42.4043 61.6658 38.9502C65.6116 33.1934 69.0931 33.1934 70.0205 33.1934H89.3125C89.3125 33.1934 85.3404 33.1934 81.3936 38.2593C78.8415 41.0227 73.7342 50.4639 73.7342 50.4639L51.122 92.2745C51.122 92.2745 41.6427 111.825 30.5083 118.269C19.0562 124.896 4.49836 122.624 4.49836 122.624L4.58083 107.992C4.58083 107.992 16.0023 109.128 21.5375 105.151C27.3964 100.942 32.8293 92.2745 32.8293 92.2745L0 33.1934Z" fill="white"/>
3
+ <path d="M130.727 47.206C130.727 47.206 140.011 48.1271 140.011 32.9291C140.011 17.731 130.727 17.731 130.727 17.731H87.3238L67.595 17.731V0L130.727 0C130.727 0 159.74 -1.90735e-05 159.74 32.9291C159.74 65.8581 130.727 64.4765 130.727 64.4765L157.651 110.761H132.12L105.892 64.4765L83.8898 64.2463C83.8898 64.2463 87.3997 54.8052 89.9518 52.0418C93.8986 46.9759 97.4065 46.9759 97.4065 46.9759L130.727 47.206Z" fill="url(#paint0_linear_393_1348)"/>
4
+ <path d="M188.11 113.342C183.382 113.342 179.197 112.351 175.555 110.371C171.976 108.326 169.165 105.483 167.12 101.841C165.139 98.1345 164.149 93.7896 164.149 88.8057V87.6556C164.149 82.6717 165.139 78.3587 167.12 74.7166C169.101 71.0107 171.88 68.1673 175.459 66.1865C179.037 64.1418 183.19 63.1195 187.918 63.1195C192.583 63.1195 196.64 64.1738 200.091 66.2823C203.541 68.327 206.225 71.2023 208.142 74.9083C210.058 78.5504 211.017 82.7995 211.017 87.6556V91.7769H176.417C176.545 95.0356 177.759 97.6873 180.059 99.7319C182.359 101.777 185.171 102.799 188.493 102.799C191.88 102.799 194.372 102.064 195.969 100.595C197.567 99.1249 198.781 97.4956 199.611 95.7065L209.483 100.882C208.589 102.543 207.279 104.364 205.554 106.345C203.892 108.262 201.656 109.923 198.845 111.329C196.033 112.671 192.455 113.342 188.11 113.342ZM176.513 82.7675H198.749C198.493 80.02 197.375 77.8156 195.394 76.1543C193.477 74.493 190.953 73.6623 187.823 73.6623C184.564 73.6623 181.976 74.493 180.059 76.1543C178.142 77.8156 176.96 80.02 176.513 82.7675ZM241.51 113.342C235.312 113.342 230.232 112 226.27 109.316C222.309 106.633 219.913 102.799 219.082 97.8151L230.2 94.9397C230.647 97.1761 231.382 98.9332 232.404 100.211C233.491 101.489 234.8 102.416 236.334 102.991C237.931 103.502 239.657 103.757 241.51 103.757C244.321 103.757 246.398 103.278 247.739 102.32C249.081 101.297 249.752 100.051 249.752 98.5818C249.752 97.1122 249.113 95.994 247.835 95.2273C246.557 94.3966 244.513 93.7257 241.701 93.2145L239.018 92.7353C235.695 92.0964 232.66 91.2338 229.912 90.1475C227.165 88.9974 224.96 87.4319 223.299 85.4512C221.638 83.4704 220.807 80.9145 220.807 77.7836C220.807 73.0553 222.532 69.4452 225.983 66.9532C229.433 64.3974 233.97 63.1195 239.593 63.1195C244.896 63.1195 249.305 64.3016 252.819 66.6657C256.333 69.0299 258.634 72.1288 259.72 75.9626L248.506 79.413C247.995 76.9849 246.941 75.2597 245.343 74.2374C243.81 73.2151 241.893 72.7039 239.593 72.7039C237.292 72.7039 235.535 73.1192 234.321 73.9499C233.107 74.7166 232.5 75.8029 232.5 77.2086C232.5 78.7421 233.139 79.8922 234.417 80.659C235.695 81.3618 237.42 81.9049 239.593 82.2883L242.276 82.7675C245.854 83.4065 249.081 84.2691 251.957 85.3553C254.896 86.3777 257.196 87.8792 258.857 89.86C260.583 91.7769 261.445 94.3966 261.445 97.7192C261.445 102.703 259.624 106.569 255.982 109.316C252.404 112 247.58 113.342 241.51 113.342ZM289.302 112C286.171 112 283.615 111.042 281.635 109.125C279.718 107.144 278.759 104.524 278.759 101.265V74.4291H266.875V64.4613H278.759V49.7013H290.836V64.4613H303.87V74.4291H290.836V99.1569C290.836 101.074 291.73 102.032 293.519 102.032H302.72V112H289.302Z" fill="#1F72F5"/>
5
+ <path d="M71.2803 33.5454C70.3529 33.5454 66.8714 33.5454 62.9256 39.3022C60.5581 42.7563 57.7874 47.7476 57.7874 47.7476L43.1299 76.6794M71.2803 33.5454C82.6534 33.5454 119.805 33.5454 119.805 33.5454H71.2803Z" stroke="url(#paint1_linear_393_1348)" stroke-width="3.99351" stroke-linecap="round"/>
6
+ <circle cx="86.26" cy="33.5456" r="4.79221" fill="#3480F6"/>
7
+ <path d="M46.3246 78.2727C46.3246 80.9193 44.1791 83.0649 41.5324 83.0649C38.8858 83.0649 36.7402 80.9193 36.7402 78.2727C36.7402 75.626 38.8858 73.4805 41.5324 73.4805C44.1791 73.4805 46.3246 75.626 46.3246 78.2727Z" fill="#12438F"/>
8
+ <circle cx="119.805" cy="33.5456" r="4.79221" fill="#C9DCFB"/>
9
+ <defs>
10
+ <linearGradient id="paint0_linear_393_1348" x1="79.8701" y1="0" x2="79.8701" y2="123" gradientUnits="userSpaceOnUse">
11
+ <stop stop-color="#0A4EBA"/>
12
+ <stop offset="0.399038" stop-color="#1F72F5"/>
13
+ </linearGradient>
14
+ <linearGradient id="paint1_linear_393_1348" x1="125.396" y1="32.7467" x2="44.7273" y2="75.0779" gradientUnits="userSpaceOnUse">
15
+ <stop stop-color="#C9DCFB"/>
16
+ <stop offset="0.5" stop-color="#1F72F5"/>
17
+ <stop offset="1" stop-color="#12438F"/>
18
+ </linearGradient>
19
+ </defs>
20
+ </svg>
Binary file
Binary file