@yrest/cli 0.5.3 → 0.6.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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/cli/index.js +37 -9
- package/dist/cli/index.mjs +37 -9
- package/dist/index.d.mts +132 -14
- package/dist/index.d.ts +132 -14
- package/dist/index.js +264 -25
- package/dist/index.mjs +279 -33
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aggiovato
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ A YAML-first alternative to json-server for frontend development.
|
|
|
63
63
|
| Readonly mode | ✅ | ❌ |
|
|
64
64
|
| Atomic writes | ✅ | ✅ |
|
|
65
65
|
| TypeScript types | ✅ | ❌ |
|
|
66
|
+
| Programmatic API for test frameworks | ✅ | ❌ |
|
|
66
67
|
|
|
67
68
|
---
|
|
68
69
|
|
|
@@ -694,6 +695,139 @@ const session = await fetch("http://localhost:3070/login", {
|
|
|
694
695
|
// → { token: "tok-ana@test.com" }
|
|
695
696
|
```
|
|
696
697
|
|
|
698
|
+
## Programmatic API
|
|
699
|
+
|
|
700
|
+
Use yRest directly inside your test suite — no CLI, no separate process to manage.
|
|
701
|
+
|
|
702
|
+
Install as a dev dependency:
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
npm install -D @yrest/cli
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### `createYrestServer`
|
|
709
|
+
|
|
710
|
+
Creates a server instance that you control with `start()` and `stop()`. Accepts either inline YAML data or a path to a `db.yml` file.
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
import { createYrestServer, yrest } from "@yrest/cli";
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
**Options:**
|
|
717
|
+
|
|
718
|
+
| Option | Type | Default | Description |
|
|
719
|
+
| ---------- | ----------------- | ----------- | ------------------------------------------------------ |
|
|
720
|
+
| `data` | `Data` | — | Inline data object (use with `yrest\`...\``) |
|
|
721
|
+
| `file` | `string` | — | Path to a `db.yml` file (`data` or `file` is required) |
|
|
722
|
+
| `port` | `number` | `3070` | TCP port. Use `0` to get a random available port |
|
|
723
|
+
| `host` | `string` | `localhost` | Host to bind |
|
|
724
|
+
| `base` | `string` | — | URL prefix for all routes (e.g. `"/api"`) |
|
|
725
|
+
| `readonly` | `boolean` | `false` | Reject all write operations with `405` |
|
|
726
|
+
| `delay` | `number` | `0` | Fixed delay in ms added to every response |
|
|
727
|
+
| `pageable` | `boolean\|number` | `false` | Wrap GET responses in `{ data, pagination }` envelope |
|
|
728
|
+
| `snapshot` | `boolean` | `false` | Enable snapshot endpoints (`/_snapshot`) |
|
|
729
|
+
| `handlers` | `string` | — | Path to a JS file exporting handler functions |
|
|
730
|
+
|
|
731
|
+
**Returned handle:**
|
|
732
|
+
|
|
733
|
+
| Member | Description |
|
|
734
|
+
| --------- | -------------------------------------------------------- |
|
|
735
|
+
| `start()` | Starts the server and begins listening |
|
|
736
|
+
| `stop()` | Gracefully closes the server |
|
|
737
|
+
| `port` | The actual port after `start()` (useful when `port: 0`) |
|
|
738
|
+
| `url` | Base URL after `start()` (e.g. `http://localhost:49821`) |
|
|
739
|
+
|
|
740
|
+
### `yrest` tagged template literal
|
|
741
|
+
|
|
742
|
+
Parses inline YAML into a data object. Strips common leading indentation automatically, so you can indent naturally inside your test files. Supports interpolated values.
|
|
743
|
+
|
|
744
|
+
```ts
|
|
745
|
+
const data = yrest`
|
|
746
|
+
users:
|
|
747
|
+
- id: 1
|
|
748
|
+
name: Ana
|
|
749
|
+
posts:
|
|
750
|
+
- id: 1
|
|
751
|
+
title: First post
|
|
752
|
+
userId: 1
|
|
753
|
+
`;
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Vitest example
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
760
|
+
import { createYrestServer, yrest } from "@yrest/cli";
|
|
761
|
+
|
|
762
|
+
const server = createYrestServer({
|
|
763
|
+
data: yrest`
|
|
764
|
+
users:
|
|
765
|
+
- id: 1
|
|
766
|
+
name: Ana
|
|
767
|
+
- id: 2
|
|
768
|
+
name: Luis
|
|
769
|
+
`,
|
|
770
|
+
port: 0, // random port — no conflicts between parallel tests
|
|
771
|
+
readonly: true,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
beforeAll(() => server.start());
|
|
775
|
+
afterAll(() => server.stop());
|
|
776
|
+
|
|
777
|
+
describe("users API", () => {
|
|
778
|
+
it("returns all users", async () => {
|
|
779
|
+
const res = await fetch(`${server.url}/users`);
|
|
780
|
+
expect(res.status).toBe(200);
|
|
781
|
+
expect(await res.json()).toHaveLength(2);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it("returns a single user", async () => {
|
|
785
|
+
const res = await fetch(`${server.url}/users/1`);
|
|
786
|
+
expect(await res.json()).toMatchObject({ name: "Ana" });
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Playwright example
|
|
792
|
+
|
|
793
|
+
```ts
|
|
794
|
+
// tests/api.spec.ts
|
|
795
|
+
import { test, expect, beforeAll, afterAll } from "@playwright/test";
|
|
796
|
+
import { createYrestServer, yrest } from "@yrest/cli";
|
|
797
|
+
|
|
798
|
+
const server = createYrestServer({
|
|
799
|
+
data: yrest`
|
|
800
|
+
users:
|
|
801
|
+
- id: 1
|
|
802
|
+
name: Ana
|
|
803
|
+
`,
|
|
804
|
+
port: 0,
|
|
805
|
+
readonly: true,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
beforeAll(() => server.start());
|
|
809
|
+
afterAll(() => server.stop());
|
|
810
|
+
|
|
811
|
+
test("lists users", async () => {
|
|
812
|
+
const res = await fetch(`${server.url}/users`);
|
|
813
|
+
expect(await res.json()).toHaveLength(1);
|
|
814
|
+
});
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### File-based example
|
|
818
|
+
|
|
819
|
+
When your test data is too large for inline YAML, point to a file:
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
const server = createYrestServer({
|
|
823
|
+
file: "./tests/fixtures/db.yml",
|
|
824
|
+
port: 0,
|
|
825
|
+
readonly: true,
|
|
826
|
+
});
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
697
831
|
## Use in package.json scripts
|
|
698
832
|
|
|
699
833
|
```json
|
|
@@ -708,6 +842,28 @@ const session = await fetch("http://localhost:3070/login", {
|
|
|
708
842
|
|
|
709
843
|
---
|
|
710
844
|
|
|
845
|
+
## Roadmap
|
|
846
|
+
|
|
847
|
+
| Feature | Status |
|
|
848
|
+
| ------------------------------------------------- | ------ |
|
|
849
|
+
| Full CRUD from `db.yml` | ✅ |
|
|
850
|
+
| Field filters, operators, full-text search | ✅ |
|
|
851
|
+
| Relations, `_expand`, `_embed`, nested routes | ✅ |
|
|
852
|
+
| Pagination, sorting, field projection | ✅ |
|
|
853
|
+
| Watch, readonly, delay, snapshot modes | ✅ |
|
|
854
|
+
| Custom routes (`_routes`) with static responses | ✅ |
|
|
855
|
+
| Template variables in responses (`{{params.id}}`) | ✅ |
|
|
856
|
+
| Handler functions (`yrest.handlers.js`) | ✅ |
|
|
857
|
+
| Visual panel (`/_panel`) | 🔜 |
|
|
858
|
+
| Programmatic API for Vitest / Playwright | ✅ |
|
|
859
|
+
| Docker image | 🔜 |
|
|
860
|
+
| OpenAPI export (`yrest openapi db.yml`) | 🔜 |
|
|
861
|
+
| VS Code extension with YAML snippets | 🔜 |
|
|
862
|
+
| Request validation with JSON Schema | 🔜 |
|
|
863
|
+
| Conditional scenarios | 🔜 |
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
711
867
|
## Contributing
|
|
712
868
|
|
|
713
869
|
### Prerequisites
|
package/dist/cli/index.js
CHANGED
|
@@ -135,7 +135,7 @@ function registerInit(program2) {
|
|
|
135
135
|
var import_node_fs5 = require("fs");
|
|
136
136
|
var import_node_path3 = require("path");
|
|
137
137
|
|
|
138
|
-
// src/storage/
|
|
138
|
+
// src/storage/yrestStorage.ts
|
|
139
139
|
var import_node_fs2 = require("fs");
|
|
140
140
|
var import_node_path2 = require("path");
|
|
141
141
|
var import_node_crypto = require("crypto");
|
|
@@ -148,8 +148,8 @@ function deepCopyData(source) {
|
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
// src/storage/
|
|
152
|
-
function
|
|
151
|
+
// src/storage/yrestStorage.ts
|
|
152
|
+
function createYrestStorage(filePath) {
|
|
153
153
|
const absPath = (0, import_node_path2.resolve)(filePath);
|
|
154
154
|
const raw = (0, import_yaml.parse)((0, import_node_fs2.readFileSync)(absPath, "utf8")) ?? {};
|
|
155
155
|
const relations = raw["_rel"] ?? {};
|
|
@@ -1231,9 +1231,37 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
1231
1231
|
return server;
|
|
1232
1232
|
}
|
|
1233
1233
|
|
|
1234
|
+
// src/server/yrestServer.ts
|
|
1235
|
+
function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__ */ new Map()) {
|
|
1236
|
+
let _port = 0;
|
|
1237
|
+
let _started = false;
|
|
1238
|
+
let _fastify = null;
|
|
1239
|
+
return {
|
|
1240
|
+
async start() {
|
|
1241
|
+
if (_started) return;
|
|
1242
|
+
_fastify = await createServer(storage, options, handlers);
|
|
1243
|
+
await _fastify.listen({ port: options.port, host: options.host });
|
|
1244
|
+
const address = _fastify.addresses()[0];
|
|
1245
|
+
_port = typeof address === "object" && "port" in address ? address.port : options.port;
|
|
1246
|
+
_started = true;
|
|
1247
|
+
},
|
|
1248
|
+
async stop() {
|
|
1249
|
+
if (!_started || !_fastify) return;
|
|
1250
|
+
await _fastify.close();
|
|
1251
|
+
_started = false;
|
|
1252
|
+
},
|
|
1253
|
+
get port() {
|
|
1254
|
+
return _port;
|
|
1255
|
+
},
|
|
1256
|
+
get url() {
|
|
1257
|
+
return `http://${options.host}:${_port}${options.base}`;
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1234
1262
|
// src/config/loadOptions.ts
|
|
1235
1263
|
var import_zod = require("zod");
|
|
1236
|
-
var
|
|
1264
|
+
var yrestOptionsSchema = import_zod.z.object({
|
|
1237
1265
|
/** Path to the YAML database file. Must be a non-empty string. */
|
|
1238
1266
|
file: import_zod.z.string().min(1),
|
|
1239
1267
|
/** TCP port the server listens on. Accepts string input and coerces to number. */
|
|
@@ -1324,18 +1352,18 @@ function registerServe(program2) {
|
|
|
1324
1352
|
...cmd.args.length > 0 ? { file } : {},
|
|
1325
1353
|
...cliOverrides
|
|
1326
1354
|
};
|
|
1327
|
-
const options =
|
|
1355
|
+
const options = yrestOptionsSchema.parse(merged);
|
|
1328
1356
|
let storage;
|
|
1329
1357
|
try {
|
|
1330
|
-
storage =
|
|
1358
|
+
storage = createYrestStorage(options.file);
|
|
1331
1359
|
} catch (err) {
|
|
1332
1360
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1333
1361
|
console.error(`Error: cannot load "${options.file}" \u2014 ${msg}`);
|
|
1334
1362
|
process.exit(1);
|
|
1335
1363
|
}
|
|
1336
1364
|
const handlers = options.handlers ? await loadHandlers((0, import_node_path3.resolve)(options.handlers)) : /* @__PURE__ */ new Map();
|
|
1337
|
-
const
|
|
1338
|
-
await
|
|
1365
|
+
const yrestServer = createYrestServerFromStorage(storage, options, handlers);
|
|
1366
|
+
await yrestServer.start();
|
|
1339
1367
|
const collections = Object.keys(storage.getData());
|
|
1340
1368
|
const customRoutes = storage.getRoutes();
|
|
1341
1369
|
const base = options.base || "/";
|
|
@@ -1355,7 +1383,7 @@ function registerServe(program2) {
|
|
|
1355
1383
|
};
|
|
1356
1384
|
console.log(
|
|
1357
1385
|
`
|
|
1358
|
-
${b("yrest")} ${dim("\xB7")} ${green(`http://${options.host}:${
|
|
1386
|
+
${b("yrest")} ${dim("\xB7")} ${green(`http://${options.host}:${yrestServer.port}`)}
|
|
1359
1387
|
`
|
|
1360
1388
|
);
|
|
1361
1389
|
console.log(` ${b("Collections")} ${dim(`(base: ${base})`)}:`);
|
package/dist/cli/index.mjs
CHANGED
|
@@ -108,7 +108,7 @@ function registerInit(program2) {
|
|
|
108
108
|
import { watchFile } from "fs";
|
|
109
109
|
import { join, resolve as resolve3 } from "path";
|
|
110
110
|
|
|
111
|
-
// src/storage/
|
|
111
|
+
// src/storage/yrestStorage.ts
|
|
112
112
|
import { readFileSync, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
113
113
|
import { resolve as resolve2, dirname } from "path";
|
|
114
114
|
import { randomUUID } from "crypto";
|
|
@@ -121,8 +121,8 @@ function deepCopyData(source) {
|
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// src/storage/
|
|
125
|
-
function
|
|
124
|
+
// src/storage/yrestStorage.ts
|
|
125
|
+
function createYrestStorage(filePath) {
|
|
126
126
|
const absPath = resolve2(filePath);
|
|
127
127
|
const raw = parse(readFileSync(absPath, "utf8")) ?? {};
|
|
128
128
|
const relations = raw["_rel"] ?? {};
|
|
@@ -1204,9 +1204,37 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
1204
1204
|
return server;
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
|
+
// src/server/yrestServer.ts
|
|
1208
|
+
function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__ */ new Map()) {
|
|
1209
|
+
let _port = 0;
|
|
1210
|
+
let _started = false;
|
|
1211
|
+
let _fastify = null;
|
|
1212
|
+
return {
|
|
1213
|
+
async start() {
|
|
1214
|
+
if (_started) return;
|
|
1215
|
+
_fastify = await createServer(storage, options, handlers);
|
|
1216
|
+
await _fastify.listen({ port: options.port, host: options.host });
|
|
1217
|
+
const address = _fastify.addresses()[0];
|
|
1218
|
+
_port = typeof address === "object" && "port" in address ? address.port : options.port;
|
|
1219
|
+
_started = true;
|
|
1220
|
+
},
|
|
1221
|
+
async stop() {
|
|
1222
|
+
if (!_started || !_fastify) return;
|
|
1223
|
+
await _fastify.close();
|
|
1224
|
+
_started = false;
|
|
1225
|
+
},
|
|
1226
|
+
get port() {
|
|
1227
|
+
return _port;
|
|
1228
|
+
},
|
|
1229
|
+
get url() {
|
|
1230
|
+
return `http://${options.host}:${_port}${options.base}`;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1207
1235
|
// src/config/loadOptions.ts
|
|
1208
1236
|
import { z } from "zod";
|
|
1209
|
-
var
|
|
1237
|
+
var yrestOptionsSchema = z.object({
|
|
1210
1238
|
/** Path to the YAML database file. Must be a non-empty string. */
|
|
1211
1239
|
file: z.string().min(1),
|
|
1212
1240
|
/** TCP port the server listens on. Accepts string input and coerces to number. */
|
|
@@ -1297,18 +1325,18 @@ function registerServe(program2) {
|
|
|
1297
1325
|
...cmd.args.length > 0 ? { file } : {},
|
|
1298
1326
|
...cliOverrides
|
|
1299
1327
|
};
|
|
1300
|
-
const options =
|
|
1328
|
+
const options = yrestOptionsSchema.parse(merged);
|
|
1301
1329
|
let storage;
|
|
1302
1330
|
try {
|
|
1303
|
-
storage =
|
|
1331
|
+
storage = createYrestStorage(options.file);
|
|
1304
1332
|
} catch (err) {
|
|
1305
1333
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1306
1334
|
console.error(`Error: cannot load "${options.file}" \u2014 ${msg}`);
|
|
1307
1335
|
process.exit(1);
|
|
1308
1336
|
}
|
|
1309
1337
|
const handlers = options.handlers ? await loadHandlers(resolve3(options.handlers)) : /* @__PURE__ */ new Map();
|
|
1310
|
-
const
|
|
1311
|
-
await
|
|
1338
|
+
const yrestServer = createYrestServerFromStorage(storage, options, handlers);
|
|
1339
|
+
await yrestServer.start();
|
|
1312
1340
|
const collections = Object.keys(storage.getData());
|
|
1313
1341
|
const customRoutes = storage.getRoutes();
|
|
1314
1342
|
const base = options.base || "/";
|
|
@@ -1328,7 +1356,7 @@ function registerServe(program2) {
|
|
|
1328
1356
|
};
|
|
1329
1357
|
console.log(
|
|
1330
1358
|
`
|
|
1331
|
-
${b("yrest")} ${dim("\xB7")} ${green(`http://${options.host}:${
|
|
1359
|
+
${b("yrest")} ${dim("\xB7")} ${green(`http://${options.host}:${yrestServer.port}`)}
|
|
1332
1360
|
`
|
|
1333
1361
|
);
|
|
1334
1362
|
console.log(` ${b("Collections")} ${dim(`(base: ${base})`)}:`);
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import * as fastify from 'fastify';
|
|
2
3
|
import * as http from 'http';
|
|
3
|
-
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/** A single REST resource item. Field names and value types are user-defined in the YAML file. */
|
|
6
6
|
type Resource = Record<string, unknown>;
|
|
@@ -63,7 +63,7 @@ type CustomRoute = {
|
|
|
63
63
|
* flushed to disk by calling {@link persist}. Use {@link reload} to pull in
|
|
64
64
|
* changes made to the file externally (e.g. in watch mode).
|
|
65
65
|
*/
|
|
66
|
-
interface
|
|
66
|
+
interface YrestStorage {
|
|
67
67
|
/** Returns the full in-memory dataset (all collections). */
|
|
68
68
|
getData(): Data;
|
|
69
69
|
/** Returns the relational mappings declared under `_rel`. */
|
|
@@ -130,16 +130,27 @@ interface YamlStorage {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
133
|
+
* Tagged template literal that parses inline YAML into a yRest {@link Data} object.
|
|
134
134
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* all other top-level keys become collections.
|
|
135
|
+
* Strips common leading indentation automatically, so the template can be
|
|
136
|
+
* indented naturally inside the calling function without affecting YAML parsing.
|
|
138
137
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
138
|
+
* Supports interpolated values — they are stringified and inserted inline.
|
|
139
|
+
*
|
|
140
|
+
* @throws {Error} If the template is not valid YAML or does not resolve to a plain object.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* const data = yrest`
|
|
144
|
+
* users:
|
|
145
|
+
* - id: 1
|
|
146
|
+
* name: Ana
|
|
147
|
+
* posts:
|
|
148
|
+
* - id: 1
|
|
149
|
+
* title: First post
|
|
150
|
+
* userId: 1
|
|
151
|
+
* `;
|
|
141
152
|
*/
|
|
142
|
-
declare function
|
|
153
|
+
declare function yrest(strings: TemplateStringsArray, ...values: unknown[]): Data;
|
|
143
154
|
|
|
144
155
|
/**
|
|
145
156
|
* Zod schema for all server runtime options.
|
|
@@ -147,7 +158,7 @@ declare function createYamlStorage(filePath: string): YamlStorage;
|
|
|
147
158
|
* Validates and normalises options from three sources in ascending priority:
|
|
148
159
|
* schema defaults → `yrest.config.yml` → explicit CLI flags.
|
|
149
160
|
*/
|
|
150
|
-
declare const
|
|
161
|
+
declare const yrestOptionsSchema: z.ZodObject<{
|
|
151
162
|
/** Path to the YAML database file. Must be a non-empty string. */
|
|
152
163
|
file: z.ZodString;
|
|
153
164
|
/** TCP port the server listens on. Accepts string input and coerces to number. */
|
|
@@ -211,9 +222,9 @@ declare const serverOptionsSchema: z.ZodObject<{
|
|
|
211
222
|
}>;
|
|
212
223
|
/**
|
|
213
224
|
* Resolved server configuration after Zod validation and transformation.
|
|
214
|
-
* Inferred from {@link
|
|
225
|
+
* Inferred from {@link yrestOptionsSchema}.
|
|
215
226
|
*/
|
|
216
|
-
type
|
|
227
|
+
type YrestOptions = z.infer<typeof yrestOptionsSchema>;
|
|
217
228
|
|
|
218
229
|
/** Incoming request data passed to every handler function. */
|
|
219
230
|
type HandlerRequest = {
|
|
@@ -252,6 +263,113 @@ type HandlerMap = Map<string, Handler>;
|
|
|
252
263
|
* @param options - Validated server options.
|
|
253
264
|
* @param handlers - Map of named handler functions loaded from yrest.handlers.js.
|
|
254
265
|
*/
|
|
255
|
-
declare function createServer(storage:
|
|
266
|
+
declare function createServer(storage: YrestStorage, options: YrestOptions, handlers?: HandlerMap): Promise<fastify.FastifyInstance<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, http.IncomingMessage, http.ServerResponse<http.IncomingMessage>, fastify.FastifyBaseLogger, fastify.FastifyTypeProviderDefault>>;
|
|
267
|
+
|
|
268
|
+
/** Handle returned by {@link createYrestServerFromStorage} and {@link createYrestServer}. */
|
|
269
|
+
interface YrestServer {
|
|
270
|
+
/** Starts the server and begins listening on the configured port. */
|
|
271
|
+
start(): Promise<void>;
|
|
272
|
+
/** Gracefully closes the server. */
|
|
273
|
+
stop(): Promise<void>;
|
|
274
|
+
/** The port the server is listening on. Only valid after `start()`. */
|
|
275
|
+
readonly port: number;
|
|
276
|
+
/** The base URL of the server (e.g. `http://localhost:3070`). Only valid after `start()`. */
|
|
277
|
+
readonly url: string;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates a {@link YrestServer} from an already-initialised storage and parsed options.
|
|
281
|
+
*
|
|
282
|
+
* This is the shared Fastify lifecycle owner used by both the CLI (`serve` command)
|
|
283
|
+
* and the programmatic API (`createYrestServer`). It is the only place where
|
|
284
|
+
* `createServer → listen → close` lives.
|
|
285
|
+
*
|
|
286
|
+
* Each consumer is responsible for building storage and resolving options before
|
|
287
|
+
* calling this function:
|
|
288
|
+
* - CLI (`serve.ts`): Zod parsing, config-file merging, `process.exit` error handling
|
|
289
|
+
* - Programmatic API (`createYrestServer`): raw → parsed options conversion, inline data support
|
|
290
|
+
*
|
|
291
|
+
* @param storage - Initialised storage instance (file-based or in-memory).
|
|
292
|
+
* @param options - Fully resolved and validated server options.
|
|
293
|
+
* @param handlers - Pre-loaded handler map. Defaults to an empty map.
|
|
294
|
+
*/
|
|
295
|
+
declare function createYrestServerFromStorage(storage: YrestStorage, options: YrestOptions, handlers?: HandlerMap): YrestServer;
|
|
296
|
+
|
|
297
|
+
/** Base options shared between file-based and data-based server instances. */
|
|
298
|
+
type YrestServerBaseOptions = {
|
|
299
|
+
/** TCP port to listen on. Defaults to `3070`. Use `0` to get a random available port. */
|
|
300
|
+
port?: number;
|
|
301
|
+
/** Host to bind. Defaults to `"localhost"`. */
|
|
302
|
+
host?: string;
|
|
303
|
+
/** URL prefix prepended to every route (e.g. `"/api"`). */
|
|
304
|
+
base?: string;
|
|
305
|
+
/** Reject all mutating requests (POST, PUT, PATCH, DELETE) with `405`. */
|
|
306
|
+
readonly?: boolean;
|
|
307
|
+
/** Milliseconds to delay every response. */
|
|
308
|
+
delay?: number;
|
|
309
|
+
/** Wrap GET collection responses in `{ data, pagination }`. Pass `true` (limit 10) or a number. */
|
|
310
|
+
pageable?: boolean | number;
|
|
311
|
+
/** Save a snapshot at startup and expose `/_snapshot` endpoints. */
|
|
312
|
+
snapshot?: boolean;
|
|
313
|
+
/** Path to a JS file exporting handler functions for custom `_routes` entries. */
|
|
314
|
+
handlers?: string;
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Options for {@link createYrestServer}.
|
|
318
|
+
* Either `file` (path to a `db.yml`) or `data` (inline object, e.g. from `yrest\`...\``) is required.
|
|
319
|
+
*/
|
|
320
|
+
type YrestServerOptions = YrestServerBaseOptions & ({
|
|
321
|
+
file: string;
|
|
322
|
+
data?: never;
|
|
323
|
+
} | {
|
|
324
|
+
data: Data;
|
|
325
|
+
file?: never;
|
|
326
|
+
});
|
|
327
|
+
/**
|
|
328
|
+
* Creates a yRest server instance for programmatic use.
|
|
329
|
+
*
|
|
330
|
+
* Accepts either a `file` path to a `db.yml` or an inline `data` object
|
|
331
|
+
* (typically produced by the {@link yrest} tagged template literal).
|
|
332
|
+
*
|
|
333
|
+
* The server is not started until you call `start()`.
|
|
334
|
+
*
|
|
335
|
+
* @example — file-based (e.g. in Playwright globalSetup)
|
|
336
|
+
* ```ts
|
|
337
|
+
* const server = createYrestServer({ file: "./tests/db.yml", readonly: true });
|
|
338
|
+
* await server.start();
|
|
339
|
+
* // → http://localhost:3070
|
|
340
|
+
* await server.stop();
|
|
341
|
+
* ```
|
|
342
|
+
*
|
|
343
|
+
* @example — inline data (e.g. in Vitest)
|
|
344
|
+
* ```ts
|
|
345
|
+
* import { createYrestServer, yrest } from "@yrest/cli";
|
|
346
|
+
*
|
|
347
|
+
* const server = createYrestServer({
|
|
348
|
+
* data: yrest`
|
|
349
|
+
* users:
|
|
350
|
+
* - id: 1
|
|
351
|
+
* name: Ana
|
|
352
|
+
* `,
|
|
353
|
+
* port: 0,
|
|
354
|
+
* readonly: true,
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* beforeAll(() => server.start());
|
|
358
|
+
* afterAll(() => server.stop());
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
declare function createYrestServer(options: YrestServerOptions): YrestServer;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Creates a {@link YrestStorage} instance backed by the given YAML file.
|
|
365
|
+
*
|
|
366
|
+
* The file is read and parsed eagerly on construction. The `_rel` key is
|
|
367
|
+
* extracted as relational metadata; `_routes` as custom route declarations;
|
|
368
|
+
* all other top-level keys become collections.
|
|
369
|
+
*
|
|
370
|
+
* @param filePath - Relative or absolute path to the YAML database file.
|
|
371
|
+
* @throws {Error} If the file cannot be read or its YAML is invalid.
|
|
372
|
+
*/
|
|
373
|
+
declare function createYrestStorage(filePath: string): YrestStorage;
|
|
256
374
|
|
|
257
|
-
export { type CustomRoute, type Data, type Handler, type HandlerMap, type HandlerRequest, type HandlerResponse, type Relations, type Resource, type
|
|
375
|
+
export { type CustomRoute, type Data, type Handler, type HandlerMap, type HandlerRequest, type HandlerResponse, type Relations, type Resource, type YrestOptions, type YrestServer, type YrestServerBaseOptions, type YrestServerOptions, type YrestStorage, createServer, createYrestServer, createYrestServerFromStorage, createYrestStorage, yrest, yrestOptionsSchema };
|