@yrest/cli 0.13.0 → 0.14.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 +235 -37
- package/dist/cli/index.js +424 -35
- package/dist/cli/index.mjs +424 -35
- package/dist/index.d.mts +140 -8
- package/dist/index.d.ts +140 -8
- package/dist/index.js +399 -72
- package/dist/index.mjs +396 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/@yrest/cli)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://github.com/aggiovato/yRest/actions)
|
|
11
|
+
[](https://app.netlify.com/projects/cca9b352-b564-4027-a05d-af26728c8ac3/deploys)
|
|
11
12
|
[](https://www.npmjs.com/package/@yrest/cli)
|
|
12
13
|
[](https://www.typescriptlang.org/)
|
|
13
|
-
[](https://socket.dev/npm/package/@yrest/cli)
|
|
14
15
|
|
|
15
16
|
YAML-powered json-server alternative. Zero-config REST API mock server with full CRUD, relations, filters and snapshots from a `db.yml` file.
|
|
16
17
|
|
|
17
18
|
> Think `json-server`, but powered by YAML — with relations, filters, pagination, nested routes, snapshots and custom handlers.
|
|
18
19
|
|
|
20
|
+
**[📖 Full documentation](https://yrest-docs.netlify.app)** — Getting Started, CLI reference, YAML format, relations, custom routes, SSE streams, playground and more.
|
|
21
|
+
|
|
19
22
|
```yaml
|
|
20
23
|
# db.yml
|
|
21
24
|
users:
|
|
@@ -62,7 +65,7 @@ A YAML-first alternative to json-server for frontend development.
|
|
|
62
65
|
| Pageable mode (envelope response) | ✅ | ❌ |
|
|
63
66
|
| Custom static routes (`_routes`) | ✅ | ❌ |
|
|
64
67
|
| Template variables in responses | ✅ | ❌ |
|
|
65
|
-
| Handler functions (JS logic)
|
|
68
|
+
| Handler functions (JS / TS logic) | ✅ | ❌ |
|
|
66
69
|
| Conditional scenarios (`scenarios:`) | ✅ | ❌ |
|
|
67
70
|
| Snapshot endpoints | ✅ | ❌ |
|
|
68
71
|
| Config file | ✅ | ⚠️ |
|
|
@@ -70,7 +73,7 @@ A YAML-first alternative to json-server for frontend development.
|
|
|
70
73
|
| Watch mode | ✅ | ✅ |
|
|
71
74
|
| Readonly mode | ✅ | ❌ |
|
|
72
75
|
| Atomic writes | ✅ | ✅ |
|
|
73
|
-
| TypeScript types
|
|
76
|
+
| TypeScript types + codegen (`yrest types`) | ✅ | ❌ |
|
|
74
77
|
| Programmatic API for test frameworks | ✅ | ❌ |
|
|
75
78
|
| OpenAPI 3.0 spec (`GET /_openapi`, `yrest openapi`) | ✅ | ❌ |
|
|
76
79
|
| Field annotations (`_schema`) | ✅ | ❌ |
|
|
@@ -169,6 +172,104 @@ The spec is also available live at `GET /_openapi` (YAML) and `GET /_openapi.jso
|
|
|
169
172
|
|
|
170
173
|
---
|
|
171
174
|
|
|
175
|
+
### `types`
|
|
176
|
+
|
|
177
|
+
Generates TypeScript interfaces from a `db.yml` file — one per collection, plus a `CollectionName` union and a `Collections` map.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx @yrest/cli types db.yml # prints to stdout
|
|
181
|
+
npx @yrest/cli types db.yml --output db.types.ts # writes a file
|
|
182
|
+
npx @yrest/cli types db.yml --output db.types.ts --watch # regenerates on every change
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Flag | Default | Description |
|
|
186
|
+
| ----------------- | ---------- | --------------------------------------------------------- |
|
|
187
|
+
| `--output <file>` | _(stdout)_ | Write to a file instead of printing to stdout |
|
|
188
|
+
| `--watch` | `false` | Regenerate on every change of the file (needs `--output`) |
|
|
189
|
+
|
|
190
|
+
Given this `db.yml`:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
_schema:
|
|
194
|
+
users:
|
|
195
|
+
email:
|
|
196
|
+
_required: true
|
|
197
|
+
_format: email
|
|
198
|
+
role:
|
|
199
|
+
_enum: [admin, editor]
|
|
200
|
+
_rel:
|
|
201
|
+
posts:
|
|
202
|
+
userId: users
|
|
203
|
+
users:
|
|
204
|
+
- id: 1
|
|
205
|
+
name: Ana
|
|
206
|
+
email: ana@test.com
|
|
207
|
+
role: admin
|
|
208
|
+
- id: 2
|
|
209
|
+
name: Luis
|
|
210
|
+
address: { city: Lima }
|
|
211
|
+
posts:
|
|
212
|
+
- id: 1
|
|
213
|
+
title: Hello
|
|
214
|
+
userId: 1
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`yrest types db.yml` prints:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// Auto-generated by yrest types — do not edit manually.
|
|
221
|
+
// Source: db.yml
|
|
222
|
+
|
|
223
|
+
/** Item of the `users` collection. */
|
|
224
|
+
export interface User {
|
|
225
|
+
id: number;
|
|
226
|
+
name: string;
|
|
227
|
+
/**
|
|
228
|
+
* @format email
|
|
229
|
+
*/
|
|
230
|
+
email: string;
|
|
231
|
+
role?: "admin" | "editor";
|
|
232
|
+
address?: { city: string };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Item of the `posts` collection. */
|
|
236
|
+
export interface Post {
|
|
237
|
+
id: number;
|
|
238
|
+
title: string;
|
|
239
|
+
/** FK → users.id */
|
|
240
|
+
userId: number;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Union of all collection names in the source file. */
|
|
244
|
+
export type CollectionName = "users" | "posts";
|
|
245
|
+
|
|
246
|
+
/** Maps each collection name to its item array type. */
|
|
247
|
+
export interface Collections {
|
|
248
|
+
users: User[];
|
|
249
|
+
posts: Post[];
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**How types are derived:**
|
|
254
|
+
|
|
255
|
+
- Interface names are the collection name in singular PascalCase (`users` → `User`, `post_tags` → `PostTag`, `categories` → `Category`). If two names collide, the plural form is kept.
|
|
256
|
+
- Types are inferred from **every** item. A field present on all items is required; a field missing on any item is optional (`?`).
|
|
257
|
+
- Different types across items become a union (`number | string`); `null` values add `| null`. Nested objects are merged into one inline shape and array elements are pooled (`{ k?: string; v?: number }[]`).
|
|
258
|
+
- `_schema` overrides the inference: `_type` (`integer` → `number`), `_required` (or the `required` / `optional` shorthand), `_enum` (literal union), `_nullable` and `_items`. `_description`, `_format` and `_deprecated` are emitted as JSDoc. Fields declared only in `_schema` are included.
|
|
259
|
+
- `many2one` / `one2one` foreign keys from `_rel` are annotated with `FK → target.id`.
|
|
260
|
+
|
|
261
|
+
The command only reads the file: `__uuid_gen` / `__fk` directives are **not** resolved and `db.yml` is never rewritten (unresolved directive values are typed as `string`).
|
|
262
|
+
|
|
263
|
+
The generator is also available programmatically:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { generateTypes } from "@yrest/cli";
|
|
267
|
+
|
|
268
|
+
const source = generateTypes({ data: { users: [{ id: 1, name: "Ana" }] } });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
172
273
|
### `serve`
|
|
173
274
|
|
|
174
275
|
Starts the mock server.
|
|
@@ -193,7 +294,7 @@ npx @yrest/cli serve db.yml --handlers yrest.handlers.js
|
|
|
193
294
|
| `--delay <ms>` | `0` | Add a fixed delay to all responses (simulates network latency) |
|
|
194
295
|
| `--pageable [n]` | `false` | Wrap GET collection responses in `{ data, pagination }`. Optional limit |
|
|
195
296
|
| `--snapshot` | `false` | Save initial state snapshot and expose `/_snapshot` endpoints |
|
|
196
|
-
| `--handlers <file>` | _(none)_ | Path to a JS file exporting handler functions for custom routes
|
|
297
|
+
| `--handlers <file>` | _(none)_ | Path to a JS or TS file exporting handler functions for custom routes |
|
|
197
298
|
| `--id-strategy` | `increment` | Id generation for new items: `increment` (auto-int) or `uuid` |
|
|
198
299
|
|
|
199
300
|
All flags can also be set in `yrest.config.yml` (see below). CLI flags always take priority over the config file.
|
|
@@ -903,41 +1004,57 @@ _routes:
|
|
|
903
1004
|
|
|
904
1005
|
### Handler functions
|
|
905
1006
|
|
|
906
|
-
For routes that need real logic (conditional responses, stateful mocks, request inspection),
|
|
1007
|
+
For routes that need real logic (conditional responses, stateful mocks, request inspection), point a `_routes` entry at a function with `_handler`:
|
|
907
1008
|
|
|
908
1009
|
```yaml
|
|
909
1010
|
_routes:
|
|
910
|
-
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
response: # optional fallback if handler throws
|
|
914
|
-
status: 200
|
|
915
|
-
body: { token: fake }
|
|
1011
|
+
- _method: POST
|
|
1012
|
+
_path: /login
|
|
1013
|
+
_handler: login
|
|
916
1014
|
|
|
917
|
-
-
|
|
918
|
-
|
|
919
|
-
|
|
1015
|
+
- _method: GET
|
|
1016
|
+
_path: /auth/me
|
|
1017
|
+
_handler: getCurrentUser
|
|
920
1018
|
```
|
|
921
1019
|
|
|
922
|
-
|
|
1020
|
+
Export the functions from a handlers file — JavaScript (`.js`, `.mjs`, `.cjs`) or TypeScript (`.ts`, `.mts`):
|
|
923
1021
|
|
|
924
|
-
```
|
|
925
|
-
// yrest.handlers.
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
}
|
|
1022
|
+
```ts
|
|
1023
|
+
// yrest.handlers.ts
|
|
1024
|
+
import { defineHandler } from "@yrest/cli";
|
|
1025
|
+
import type { User } from "./db.types"; // generated with `yrest types`
|
|
1026
|
+
|
|
1027
|
+
export const login = defineHandler((req, { collection }) => {
|
|
1028
|
+
const { email, password } = (req.body ?? {}) as { email?: string; password?: string };
|
|
1029
|
+
const user = collection<User>("users").findWhere((u) => u.email === email)[0];
|
|
1030
|
+
if (!user || password !== "secret") {
|
|
1031
|
+
return { status: 401, body: { error: "Invalid credentials" } };
|
|
1032
|
+
}
|
|
1033
|
+
return { body: { token: `tok-${user.id}` } };
|
|
1034
|
+
});
|
|
931
1035
|
|
|
932
|
-
export
|
|
933
|
-
|
|
934
|
-
}
|
|
1036
|
+
export const getCurrentUser = defineHandler(() => ({
|
|
1037
|
+
body: { id: 1, name: "Ana", role: "admin" },
|
|
1038
|
+
}));
|
|
935
1039
|
```
|
|
936
1040
|
|
|
937
|
-
Pass the file to the server with `--handlers
|
|
1041
|
+
Pass the file to the server with `--handlers` (or `handlers:` in `yrest.config.yml`):
|
|
938
1042
|
|
|
939
1043
|
```bash
|
|
940
|
-
npx @yrest/cli serve db.yml --handlers yrest.handlers.
|
|
1044
|
+
npx @yrest/cli serve db.yml --handlers yrest.handlers.ts
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
TypeScript handler files are loaded by Node's built-in type stripping (Node ≥ 22.18). On older versions load [`tsx`](https://tsx.is) into Node (`NODE_OPTIONS="--import tsx" npx @yrest/cli serve …`) or compile the file to `.js`. With type stripping, type-only imports must use `import type`.
|
|
1048
|
+
|
|
1049
|
+
In plain JavaScript the same functions work without `defineHandler`:
|
|
1050
|
+
|
|
1051
|
+
```js
|
|
1052
|
+
// yrest.handlers.js
|
|
1053
|
+
export async function login(req, ctx) {
|
|
1054
|
+
const { email } = req.body ?? {};
|
|
1055
|
+
const user = ctx.collection("users").findWhere((u) => u.email === email)[0];
|
|
1056
|
+
return user ? { body: { token: `tok-${user.id}` } } : { status: 401 };
|
|
1057
|
+
}
|
|
941
1058
|
```
|
|
942
1059
|
|
|
943
1060
|
**Handler signature:**
|
|
@@ -956,10 +1073,24 @@ type HandlerResponse = {
|
|
|
956
1073
|
headers?: Record<string, string>;
|
|
957
1074
|
};
|
|
958
1075
|
|
|
959
|
-
type
|
|
1076
|
+
type HandlerContext = {
|
|
1077
|
+
// Typed CRUD access to the live data — the same handle as server.collection()
|
|
1078
|
+
collection<T>(name: string): CollectionHandle<T>;
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
type Handler = (
|
|
1082
|
+
req: HandlerRequest,
|
|
1083
|
+
ctx: HandlerContext
|
|
1084
|
+
) => HandlerResponse | Promise<HandlerResponse>;
|
|
960
1085
|
```
|
|
961
1086
|
|
|
962
|
-
|
|
1087
|
+
`defineHandler` (and `defineSseHandler` for [SSE streams](#sse-streams)) are identity wrappers exported by `@yrest/cli`: they only add types.
|
|
1088
|
+
|
|
1089
|
+
**`ctx.collection(name)`** reads and writes the same data the REST routes serve: a record a handler creates is returned by `GET /users` straight away and persisted to `db.yml` like a `POST`. See [Typed collection access](#typed-collection-access) for every method. It throws if the collection does not exist.
|
|
1090
|
+
|
|
1091
|
+
**Errors:** if a named handler is not exported by the file, the route answers `501`; if the handler throws, it answers `500` with the error message.
|
|
1092
|
+
|
|
1093
|
+
**Scaffolding:** `yrest handler <name>` adds a stub to the handlers file (`handlers:` in `yrest.config.yml`, or `yrest.handlers.js`). For a `.ts` / `.mts` file the stub uses `defineHandler`. Add `--register --method POST --path /login` to also write the `_routes` entry to `db.yml`.
|
|
963
1094
|
|
|
964
1095
|
### SSE streams
|
|
965
1096
|
|
|
@@ -1009,6 +1140,41 @@ _events:
|
|
|
1009
1140
|
filter: "{{query.filter}}" # from query string
|
|
1010
1141
|
```
|
|
1011
1142
|
|
|
1143
|
+
**Handler-driven streams** — for streams that depend on state (a chat, a job queue), let a function drive the stream with `_handler` instead of `_events`:
|
|
1144
|
+
|
|
1145
|
+
```yaml
|
|
1146
|
+
_routes:
|
|
1147
|
+
- _method: SSE
|
|
1148
|
+
_path: /events/chat
|
|
1149
|
+
_sse:
|
|
1150
|
+
_handler: chatStream
|
|
1151
|
+
- _method: POST
|
|
1152
|
+
_path: /chat/messages
|
|
1153
|
+
_handler: postMessage
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
```ts
|
|
1157
|
+
// yrest.handlers.ts
|
|
1158
|
+
import { EventEmitter } from "node:events";
|
|
1159
|
+
import { defineHandler, defineSseHandler } from "@yrest/cli";
|
|
1160
|
+
|
|
1161
|
+
const bus = new EventEmitter();
|
|
1162
|
+
|
|
1163
|
+
export const chatStream = defineSseHandler((req, raw) => {
|
|
1164
|
+
const send = (msg: unknown) => raw.write(`event: message\ndata: ${JSON.stringify(msg)}\n\n`);
|
|
1165
|
+
bus.on("message", send);
|
|
1166
|
+
raw.on("close", () => bus.off("message", send)); // client disconnected
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
export const postMessage = defineHandler((req, { collection }) => {
|
|
1170
|
+
const message = collection("messages").create(req.body as { text: string });
|
|
1171
|
+
bus.emit("message", message);
|
|
1172
|
+
return { status: 201, body: message };
|
|
1173
|
+
});
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
The SSE handler receives `(req, raw, ctx)`, where `raw` is the Node.js `ServerResponse`. yRest sends the SSE headers and the keep-alive comments; the handler writes frames and calls `raw.end()` when it is done. A handler missing from the handlers file answers `501`; one that throws sends an `event: error` frame and the stream is closed.
|
|
1177
|
+
|
|
1012
1178
|
**Consuming from the browser:**
|
|
1013
1179
|
|
|
1014
1180
|
```js
|
|
@@ -1207,16 +1373,47 @@ import { createYrestServer, yrest } from "@yrest/cli";
|
|
|
1207
1373
|
| `delay` | `number` | `0` | Fixed delay in ms added to every response |
|
|
1208
1374
|
| `pageable` | `boolean\|number` | `false` | Wrap GET responses in `{ data, pagination }` envelope |
|
|
1209
1375
|
| `snapshot` | `boolean` | `false` | Enable snapshot endpoints (`/_snapshot`) |
|
|
1210
|
-
| `handlers` | `string` | — | Path to a JS file exporting handler functions
|
|
1376
|
+
| `handlers` | `string` | — | Path to a JS or TS file exporting handler functions |
|
|
1211
1377
|
|
|
1212
1378
|
**Returned handle:**
|
|
1213
1379
|
|
|
1214
|
-
| Member
|
|
1215
|
-
|
|
|
1216
|
-
| `start()`
|
|
1217
|
-
| `stop()`
|
|
1218
|
-
| `port`
|
|
1219
|
-
| `url`
|
|
1380
|
+
| Member | Description |
|
|
1381
|
+
| --------------------- | ---------------------------------------------------------------- |
|
|
1382
|
+
| `start()` | Starts the server and begins listening |
|
|
1383
|
+
| `stop()` | Gracefully closes the server |
|
|
1384
|
+
| `port` | The actual port after `start()` (useful when `port: 0`) |
|
|
1385
|
+
| `url` | Base URL after `start()` (e.g. `http://localhost:49821`) |
|
|
1386
|
+
| `collection<T>(name)` | Typed CRUD handle for a collection — see below (after `start()`) |
|
|
1387
|
+
|
|
1388
|
+
### Typed collection access
|
|
1389
|
+
|
|
1390
|
+
`server.collection<T>(name)` returns a typed handle over the running data. Use it to seed or inspect state in a test without going through HTTP. Combine it with [`yrest types`](#types) to get the item types:
|
|
1391
|
+
|
|
1392
|
+
```ts
|
|
1393
|
+
import { createYrestServer } from "@yrest/cli";
|
|
1394
|
+
import type { User } from "./db.types"; // npx @yrest/cli types db.yml -o db.types.ts
|
|
1395
|
+
|
|
1396
|
+
const server = createYrestServer({ file: "./tests/db.yml", port: 0 });
|
|
1397
|
+
await server.start();
|
|
1398
|
+
|
|
1399
|
+
const users = server.collection<User>("users");
|
|
1400
|
+
users.create({ name: "Ana", email: "ana@test.com" }); // id assigned by the server
|
|
1401
|
+
|
|
1402
|
+
const res = await fetch(`${server.url}/users`);
|
|
1403
|
+
expect(await res.json()).toHaveLength(users.count());
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1406
|
+
| Method | Returns | Description |
|
|
1407
|
+
| ------------------- | ---------------- | --------------------------------------------------------------- |
|
|
1408
|
+
| `findAll()` | `T[]` | Every item, in stored order |
|
|
1409
|
+
| `findById(id)` | `T \| undefined` | Item by id — `1` and `"1"` both match |
|
|
1410
|
+
| `findWhere(fn)` | `T[]` | Items for which the predicate returns `true` |
|
|
1411
|
+
| `count()` | `number` | Number of items |
|
|
1412
|
+
| `create(data)` | `T` | Adds an item; the id is generated with the server's id strategy |
|
|
1413
|
+
| `update(id, patch)` | `T \| undefined` | Merges `patch` into the item (the id cannot change) |
|
|
1414
|
+
| `delete(id)` | `T \| undefined` | Removes the item and returns it |
|
|
1415
|
+
|
|
1416
|
+
Changes are visible to HTTP clients immediately and are persisted to `db.yml` exactly like REST writes (in-memory for inline data). Returned items are copies: changing one does not change the stored data — use `update()`. `collection()` throws for an unknown collection, and before `start()`. The same handle is available to handlers as [`ctx.collection`](#handler-functions).
|
|
1220
1417
|
|
|
1221
1418
|
### `yrest` tagged template literal
|
|
1222
1419
|
|
|
@@ -1350,7 +1547,8 @@ const server = createYrestServer({
|
|
|
1350
1547
|
| Auto-embedding (`nested: true` in `_rel`) | ✅ |
|
|
1351
1548
|
| SSE stream routes (`_method: SSE`) | ✅ |
|
|
1352
1549
|
| WebSocket mock channels (`_method: WS`) | 🔜 |
|
|
1353
|
-
| TypeScript
|
|
1550
|
+
| TypeScript codegen (`yrest types db.yml`) | ✅ |
|
|
1551
|
+
| TypeScript handlers + typed collections | ✅ |
|
|
1354
1552
|
|
|
1355
1553
|
---
|
|
1356
1554
|
|