@aggiovato/yrest 0.1.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 ADDED
@@ -0,0 +1,169 @@
1
+ # yrest
2
+
3
+ Zero-config REST API mock server powered by a YAML file.
4
+
5
+ Define your data in a `db.yml` file and get a fully functional CRUD REST API in seconds — no backend required.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -D yrest
11
+ ```
12
+
13
+ Or run directly with npx:
14
+
15
+ ```bash
16
+ npx yrest serve db.yml
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ # Create a sample db.yml in the current directory
23
+ npx yrest init
24
+
25
+ # Start the server
26
+ npx yrest serve db.yml
27
+ ```
28
+
29
+ ```txt
30
+ yrest running at http://localhost:3070
31
+
32
+ Resources (base: /):
33
+ /users
34
+ /posts
35
+ ```
36
+
37
+ ## Commands
38
+
39
+ ### `init`
40
+
41
+ Creates a sample `db.yml` in the current directory.
42
+
43
+ ```bash
44
+ yrest init # basic sample (default)
45
+ yrest init --sample relational # with _rel relations
46
+ yrest init --file api.yml # custom filename
47
+ yrest init --sample relational --file api.yml
48
+ ```
49
+
50
+ | Flag | Default | Description |
51
+ |------|---------|-------------|
52
+ | `--file` | `db.yml` | Output filename |
53
+ | `--sample` | `basic` | Sample data (`basic`, `relational`) |
54
+
55
+ **Samples:**
56
+ - `basic` — two independent collections: `users` and `products`
57
+ - `relational` — three collections with `_rel` relationships: `users`, `posts` and `comments`
58
+
59
+ ### `serve`
60
+
61
+ Starts the mock server.
62
+
63
+ ```bash
64
+ yrest serve db.yml
65
+ yrest serve db.yml --port 3001
66
+ yrest serve db.yml --host 0.0.0.0
67
+ yrest serve db.yml --base /api
68
+ ```
69
+
70
+ | Flag | Default | Description |
71
+ |------|---------|-------------|
72
+ | `--port` | `3070` | Port to listen on |
73
+ | `--host` | `localhost` | Host to bind |
74
+ | `--base` | _(none)_ | Prefix for all routes |
75
+
76
+ ## Database format
77
+
78
+ ```yaml
79
+ users:
80
+ - id: 1
81
+ name: Ana
82
+ email: ana@test.com
83
+ - id: 2
84
+ name: Luis
85
+ email: luis@test.com
86
+
87
+ posts:
88
+ - id: 1
89
+ title: First post
90
+ userId: 1
91
+ ```
92
+
93
+ Each top-level key becomes a resource with full CRUD endpoints.
94
+
95
+ ## Generated endpoints
96
+
97
+ For each resource in `db.yml`:
98
+
99
+ ```txt
100
+ GET /users List all
101
+ GET /users/:id Get one
102
+ POST /users Create
103
+ PUT /users/:id Replace
104
+ PATCH /users/:id Partial update
105
+ DELETE /users/:id Delete
106
+ ```
107
+
108
+ With `--base /api` all routes are prefixed: `/api/users`, `/api/users/:id`, etc.
109
+
110
+ ## HTTP responses
111
+
112
+ | Status | When |
113
+ |--------|------|
114
+ | `200` | Successful GET, PUT, PATCH, DELETE |
115
+ | `201` | Successful POST |
116
+ | `404` | Resource or id not found |
117
+ | `500` | Error reading or writing the YAML file |
118
+
119
+ DELETE returns the deleted item (useful for debugging).
120
+
121
+ ## ID generation
122
+
123
+ If a POST body does not include an `id`, yrest assigns the next incremental integer automatically. If the body includes an `id`, it is respected.
124
+
125
+ ## Persistence
126
+
127
+ All write operations (POST, PUT, PATCH, DELETE) are saved back to `db.yml` immediately using an atomic write strategy (write to temp file → rename), so data is never corrupted even if the process is interrupted.
128
+
129
+ ## CORS
130
+
131
+ CORS is enabled by default, so you can call the API from any frontend running on a different port without extra configuration.
132
+
133
+ ## Frontend usage
134
+
135
+ ```ts
136
+ // List
137
+ const users = await fetch("http://localhost:3070/users").then(r => r.json());
138
+
139
+ // Create
140
+ await fetch("http://localhost:3070/users", {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify({ name: "Carlos", email: "carlos@test.com" }),
144
+ });
145
+
146
+ // Partial update
147
+ await fetch("http://localhost:3070/users/1", {
148
+ method: "PATCH",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify({ name: "Ana Updated" }),
151
+ });
152
+
153
+ // Delete
154
+ await fetch("http://localhost:3070/users/1", { method: "DELETE" });
155
+ ```
156
+
157
+ ## Use in package.json scripts
158
+
159
+ ```json
160
+ {
161
+ "scripts": {
162
+ "mock": "yrest serve db.yml"
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/cli/commands/init.ts
30
+ var import_node_fs = require("fs");
31
+ var import_node_path = require("path");
32
+
33
+ // src/cli/commands/templates/basic.ts
34
+ var basicTemplate = `users:
35
+ - id: 1
36
+ name: Ana
37
+ email: ana@test.com
38
+ - id: 2
39
+ name: Luis
40
+ email: luis@test.com
41
+
42
+ products:
43
+ - id: 1
44
+ name: Laptop
45
+ price: 999
46
+ - id: 2
47
+ name: Phone
48
+ price: 499
49
+ `;
50
+
51
+ // src/cli/commands/templates/relational.ts
52
+ var relationalTemplate = `_rel:
53
+ posts:
54
+ userId: users
55
+ comments:
56
+ postId: posts
57
+
58
+ users:
59
+ - id: 1
60
+ name: Ana
61
+ email: ana@test.com
62
+ - id: 2
63
+ name: Luis
64
+ email: luis@test.com
65
+
66
+ posts:
67
+ - id: 1
68
+ title: First post
69
+ body: Content of the first post
70
+ userId: 1
71
+ - id: 2
72
+ title: Second post
73
+ body: Content of the second post
74
+ userId: 1
75
+
76
+ comments:
77
+ - id: 1
78
+ body: Great post!
79
+ postId: 1
80
+ - id: 2
81
+ body: Thanks for sharing
82
+ postId: 1
83
+ `;
84
+
85
+ // src/cli/commands/templates/index.ts
86
+ var SAMPLES = ["basic", "relational"];
87
+ var templates = {
88
+ basic: basicTemplate,
89
+ relational: relationalTemplate
90
+ };
91
+
92
+ // src/cli/commands/init.ts
93
+ function registerInit(program2) {
94
+ program2.command("init").description("Create a sample db.yml in the current directory").option("-f, --file <name>", "Output filename", "db.yml").option(
95
+ "-s, --sample <name>",
96
+ `Sample data to use (${SAMPLES.join(", ")})`,
97
+ "basic"
98
+ ).action((flags) => {
99
+ if (!SAMPLES.includes(flags.sample)) {
100
+ console.error(
101
+ `Error: unknown sample "${flags.sample}". Available: ${SAMPLES.join(", ")}`
102
+ );
103
+ process.exit(1);
104
+ }
105
+ const target = (0, import_node_path.resolve)(process.cwd(), flags.file);
106
+ if ((0, import_node_fs.existsSync)(target)) {
107
+ console.error(`Error: ${flags.file} already exists.`);
108
+ process.exit(1);
109
+ }
110
+ (0, import_node_fs.writeFileSync)(target, templates[flags.sample], "utf8");
111
+ console.log(`Created ${flags.file} (sample: ${flags.sample})`);
112
+ console.log(`Run: yrest serve ${flags.file}`);
113
+ });
114
+ }
115
+
116
+ // src/storage/yamlStorage.ts
117
+ var import_node_fs2 = require("fs");
118
+ var import_node_path2 = require("path");
119
+ var import_node_crypto = require("crypto");
120
+ var import_yaml = require("yaml");
121
+ function createYamlStorage(filePath) {
122
+ const absPath = (0, import_node_path2.resolve)(filePath);
123
+ const raw = (0, import_yaml.parse)((0, import_node_fs2.readFileSync)(absPath, "utf8")) ?? {};
124
+ const relations = raw["_rel"] ?? {};
125
+ const data = Object.fromEntries(
126
+ Object.entries(raw).filter(([key]) => key !== "_rel")
127
+ );
128
+ return {
129
+ getData() {
130
+ return data;
131
+ },
132
+ getRelations() {
133
+ return relations;
134
+ },
135
+ getCollection(name) {
136
+ return data[name];
137
+ },
138
+ setCollection(name, items) {
139
+ data[name] = items;
140
+ },
141
+ persist() {
142
+ const payload = Object.keys(relations).length > 0 ? { _rel: relations, ...data } : { ...data };
143
+ const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0, import_node_crypto.randomUUID)()}.tmp`);
144
+ (0, import_node_fs2.writeFileSync)(tmp, (0, import_yaml.stringify)(payload), "utf8");
145
+ (0, import_node_fs2.renameSync)(tmp, absPath);
146
+ }
147
+ };
148
+ }
149
+
150
+ // src/server/createServer.ts
151
+ var import_fastify = __toESM(require("fastify"));
152
+ var import_cors = __toESM(require("@fastify/cors"));
153
+
154
+ // src/services/resourceService.ts
155
+ function nextId(items) {
156
+ const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
157
+ return ids.length > 0 ? Math.max(...ids) + 1 : 1;
158
+ }
159
+ function findById(items, id) {
160
+ return items.find((i) => String(i["id"]) === id);
161
+ }
162
+ function findIndexById(items, id) {
163
+ return items.findIndex((i) => String(i["id"]) === id);
164
+ }
165
+ function createItem(storage, resource, body) {
166
+ const collection = storage.getCollection(resource) ?? [];
167
+ const item = {
168
+ id: body["id"] !== void 0 ? body["id"] : nextId(collection),
169
+ ...body
170
+ };
171
+ storage.setCollection(resource, [...collection, item]);
172
+ storage.persist();
173
+ return item;
174
+ }
175
+ function replaceItem(storage, resource, id, body) {
176
+ const collection = storage.getCollection(resource) ?? [];
177
+ const idx = findIndexById(collection, id);
178
+ if (idx === -1) return void 0;
179
+ const updated = { ...body, id: collection[idx]["id"] };
180
+ collection[idx] = updated;
181
+ storage.setCollection(resource, collection);
182
+ storage.persist();
183
+ return updated;
184
+ }
185
+ function patchItem(storage, resource, id, body) {
186
+ const collection = storage.getCollection(resource) ?? [];
187
+ const idx = findIndexById(collection, id);
188
+ if (idx === -1) return void 0;
189
+ const updated = { ...collection[idx], ...body };
190
+ collection[idx] = updated;
191
+ storage.setCollection(resource, collection);
192
+ storage.persist();
193
+ return updated;
194
+ }
195
+ function deleteItem(storage, resource, id) {
196
+ const collection = storage.getCollection(resource) ?? [];
197
+ const idx = findIndexById(collection, id);
198
+ if (idx === -1) return void 0;
199
+ const [deleted] = collection.splice(idx, 1);
200
+ storage.setCollection(resource, collection);
201
+ storage.persist();
202
+ return deleted;
203
+ }
204
+
205
+ // src/router/routes/collection.routes.ts
206
+ function registerCollectionRoutes(server, storage, resource, prefix) {
207
+ server.get(prefix, () => storage.getCollection(resource) ?? []);
208
+ server.post(prefix, (req, reply) => {
209
+ const item = createItem(storage, resource, req.body);
210
+ return reply.status(201).send(item);
211
+ });
212
+ }
213
+
214
+ // src/router/routes/item.routes.ts
215
+ function registerItemRoutes(server, storage, resource, prefix) {
216
+ server.get(`${prefix}/:id`, (req, reply) => {
217
+ const item = findById(storage.getCollection(resource) ?? [], req.params.id);
218
+ if (!item) return reply.status(404).send({ error: "Not found" });
219
+ return item;
220
+ });
221
+ server.put(`${prefix}/:id`, (req, reply) => {
222
+ const item = replaceItem(storage, resource, req.params.id, req.body);
223
+ if (!item) return reply.status(404).send({ error: "Not found" });
224
+ return item;
225
+ });
226
+ server.patch(`${prefix}/:id`, (req, reply) => {
227
+ const item = patchItem(storage, resource, req.params.id, req.body);
228
+ if (!item) return reply.status(404).send({ error: "Not found" });
229
+ return item;
230
+ });
231
+ server.delete(`${prefix}/:id`, (req, reply) => {
232
+ const item = deleteItem(storage, resource, req.params.id);
233
+ if (!item) return reply.status(404).send({ error: "Not found" });
234
+ return item;
235
+ });
236
+ }
237
+
238
+ // src/router/routes/nested.routes.ts
239
+ function registerNestedRoutes(server, storage, relations, base) {
240
+ for (const [child, fields] of Object.entries(relations)) {
241
+ for (const [field, parent] of Object.entries(fields)) {
242
+ server.get(
243
+ `${base}/${parent}/:id/${child}`,
244
+ (req, reply) => {
245
+ const parentCollection = storage.getCollection(parent) ?? [];
246
+ const parentItem = findById(parentCollection, req.params.id);
247
+ if (!parentItem) return reply.status(404).send({ error: "Not found" });
248
+ const children = (storage.getCollection(child) ?? []).filter(
249
+ (item) => String(item[field]) === req.params.id
250
+ );
251
+ return children;
252
+ }
253
+ );
254
+ }
255
+ }
256
+ }
257
+
258
+ // src/router/resource.router.ts
259
+ function registerResourceRoutes(server, storage, base) {
260
+ for (const resource of Object.keys(storage.getData())) {
261
+ const prefix = `${base}/${resource}`;
262
+ registerCollectionRoutes(server, storage, resource, prefix);
263
+ registerItemRoutes(server, storage, resource, prefix);
264
+ }
265
+ registerNestedRoutes(server, storage, storage.getRelations(), base);
266
+ }
267
+
268
+ // src/server/createServer.ts
269
+ async function createServer(storage, options) {
270
+ const server = (0, import_fastify.default)();
271
+ await server.register(import_cors.default);
272
+ registerResourceRoutes(server, storage, options.base);
273
+ return server;
274
+ }
275
+
276
+ // src/config/loadOptions.ts
277
+ var import_zod = require("zod");
278
+ var serverOptionsSchema = import_zod.z.object({
279
+ file: import_zod.z.string().min(1),
280
+ port: import_zod.z.coerce.number().int().positive().default(3070),
281
+ host: import_zod.z.string().default("localhost"),
282
+ base: import_zod.z.string().default("").transform((v) => v && !v.startsWith("/") ? `/${v}` : v)
283
+ });
284
+
285
+ // src/cli/commands/serve.ts
286
+ function registerServe(program2) {
287
+ program2.command("serve").description("Start the mock server using a YAML file as database").argument("[file]", "Path to the YAML database file", "db.yml").option("-p, --port <number>", "Port to listen on", "3070").option("-H, --host <host>", "Host to bind", "localhost").option("-b, --base <path>", "Base path prefix for all routes", "").action(async (file, flags) => {
288
+ const options = serverOptionsSchema.parse({
289
+ file,
290
+ port: flags["port"],
291
+ host: flags["host"],
292
+ base: flags["base"]
293
+ });
294
+ const storage = createYamlStorage(options.file);
295
+ const server = await createServer(storage, options);
296
+ await server.listen({ port: options.port, host: options.host });
297
+ const collections = Object.keys(storage.getData());
298
+ const baseLabel = options.base || "/";
299
+ console.log(`
300
+ yrest running at http://${options.host}:${options.port}`);
301
+ console.log(`
302
+ Resources (base: ${baseLabel}):`);
303
+ for (const name of collections) {
304
+ console.log(` /${name}`);
305
+ }
306
+ console.log("");
307
+ });
308
+ }
309
+
310
+ // src/cli/index.ts
311
+ import_commander.program.name("yrest").description("Zero-config REST API mock server powered by a YAML file").version("0.1.0");
312
+ registerInit(import_commander.program);
313
+ registerServe(import_commander.program);
314
+ import_commander.program.parse();
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { program } from "commander";
5
+
6
+ // src/cli/commands/init.ts
7
+ import { existsSync, writeFileSync } from "fs";
8
+ import { resolve } from "path";
9
+
10
+ // src/cli/commands/templates/basic.ts
11
+ var basicTemplate = `users:
12
+ - id: 1
13
+ name: Ana
14
+ email: ana@test.com
15
+ - id: 2
16
+ name: Luis
17
+ email: luis@test.com
18
+
19
+ products:
20
+ - id: 1
21
+ name: Laptop
22
+ price: 999
23
+ - id: 2
24
+ name: Phone
25
+ price: 499
26
+ `;
27
+
28
+ // src/cli/commands/templates/relational.ts
29
+ var relationalTemplate = `_rel:
30
+ posts:
31
+ userId: users
32
+ comments:
33
+ postId: posts
34
+
35
+ users:
36
+ - id: 1
37
+ name: Ana
38
+ email: ana@test.com
39
+ - id: 2
40
+ name: Luis
41
+ email: luis@test.com
42
+
43
+ posts:
44
+ - id: 1
45
+ title: First post
46
+ body: Content of the first post
47
+ userId: 1
48
+ - id: 2
49
+ title: Second post
50
+ body: Content of the second post
51
+ userId: 1
52
+
53
+ comments:
54
+ - id: 1
55
+ body: Great post!
56
+ postId: 1
57
+ - id: 2
58
+ body: Thanks for sharing
59
+ postId: 1
60
+ `;
61
+
62
+ // src/cli/commands/templates/index.ts
63
+ var SAMPLES = ["basic", "relational"];
64
+ var templates = {
65
+ basic: basicTemplate,
66
+ relational: relationalTemplate
67
+ };
68
+
69
+ // src/cli/commands/init.ts
70
+ function registerInit(program2) {
71
+ program2.command("init").description("Create a sample db.yml in the current directory").option("-f, --file <name>", "Output filename", "db.yml").option(
72
+ "-s, --sample <name>",
73
+ `Sample data to use (${SAMPLES.join(", ")})`,
74
+ "basic"
75
+ ).action((flags) => {
76
+ if (!SAMPLES.includes(flags.sample)) {
77
+ console.error(
78
+ `Error: unknown sample "${flags.sample}". Available: ${SAMPLES.join(", ")}`
79
+ );
80
+ process.exit(1);
81
+ }
82
+ const target = resolve(process.cwd(), flags.file);
83
+ if (existsSync(target)) {
84
+ console.error(`Error: ${flags.file} already exists.`);
85
+ process.exit(1);
86
+ }
87
+ writeFileSync(target, templates[flags.sample], "utf8");
88
+ console.log(`Created ${flags.file} (sample: ${flags.sample})`);
89
+ console.log(`Run: yrest serve ${flags.file}`);
90
+ });
91
+ }
92
+
93
+ // src/storage/yamlStorage.ts
94
+ import { readFileSync, writeFileSync as writeFileSync2, renameSync } from "fs";
95
+ import { resolve as resolve2, dirname } from "path";
96
+ import { randomUUID } from "crypto";
97
+ import { parse, stringify } from "yaml";
98
+ function createYamlStorage(filePath) {
99
+ const absPath = resolve2(filePath);
100
+ const raw = parse(readFileSync(absPath, "utf8")) ?? {};
101
+ const relations = raw["_rel"] ?? {};
102
+ const data = Object.fromEntries(
103
+ Object.entries(raw).filter(([key]) => key !== "_rel")
104
+ );
105
+ return {
106
+ getData() {
107
+ return data;
108
+ },
109
+ getRelations() {
110
+ return relations;
111
+ },
112
+ getCollection(name) {
113
+ return data[name];
114
+ },
115
+ setCollection(name, items) {
116
+ data[name] = items;
117
+ },
118
+ persist() {
119
+ const payload = Object.keys(relations).length > 0 ? { _rel: relations, ...data } : { ...data };
120
+ const tmp = resolve2(dirname(absPath), `.yrest-${randomUUID()}.tmp`);
121
+ writeFileSync2(tmp, stringify(payload), "utf8");
122
+ renameSync(tmp, absPath);
123
+ }
124
+ };
125
+ }
126
+
127
+ // src/server/createServer.ts
128
+ import Fastify from "fastify";
129
+ import cors from "@fastify/cors";
130
+
131
+ // src/services/resourceService.ts
132
+ function nextId(items) {
133
+ const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
134
+ return ids.length > 0 ? Math.max(...ids) + 1 : 1;
135
+ }
136
+ function findById(items, id) {
137
+ return items.find((i) => String(i["id"]) === id);
138
+ }
139
+ function findIndexById(items, id) {
140
+ return items.findIndex((i) => String(i["id"]) === id);
141
+ }
142
+ function createItem(storage, resource, body) {
143
+ const collection = storage.getCollection(resource) ?? [];
144
+ const item = {
145
+ id: body["id"] !== void 0 ? body["id"] : nextId(collection),
146
+ ...body
147
+ };
148
+ storage.setCollection(resource, [...collection, item]);
149
+ storage.persist();
150
+ return item;
151
+ }
152
+ function replaceItem(storage, resource, id, body) {
153
+ const collection = storage.getCollection(resource) ?? [];
154
+ const idx = findIndexById(collection, id);
155
+ if (idx === -1) return void 0;
156
+ const updated = { ...body, id: collection[idx]["id"] };
157
+ collection[idx] = updated;
158
+ storage.setCollection(resource, collection);
159
+ storage.persist();
160
+ return updated;
161
+ }
162
+ function patchItem(storage, resource, id, body) {
163
+ const collection = storage.getCollection(resource) ?? [];
164
+ const idx = findIndexById(collection, id);
165
+ if (idx === -1) return void 0;
166
+ const updated = { ...collection[idx], ...body };
167
+ collection[idx] = updated;
168
+ storage.setCollection(resource, collection);
169
+ storage.persist();
170
+ return updated;
171
+ }
172
+ function deleteItem(storage, resource, id) {
173
+ const collection = storage.getCollection(resource) ?? [];
174
+ const idx = findIndexById(collection, id);
175
+ if (idx === -1) return void 0;
176
+ const [deleted] = collection.splice(idx, 1);
177
+ storage.setCollection(resource, collection);
178
+ storage.persist();
179
+ return deleted;
180
+ }
181
+
182
+ // src/router/routes/collection.routes.ts
183
+ function registerCollectionRoutes(server, storage, resource, prefix) {
184
+ server.get(prefix, () => storage.getCollection(resource) ?? []);
185
+ server.post(prefix, (req, reply) => {
186
+ const item = createItem(storage, resource, req.body);
187
+ return reply.status(201).send(item);
188
+ });
189
+ }
190
+
191
+ // src/router/routes/item.routes.ts
192
+ function registerItemRoutes(server, storage, resource, prefix) {
193
+ server.get(`${prefix}/:id`, (req, reply) => {
194
+ const item = findById(storage.getCollection(resource) ?? [], req.params.id);
195
+ if (!item) return reply.status(404).send({ error: "Not found" });
196
+ return item;
197
+ });
198
+ server.put(`${prefix}/:id`, (req, reply) => {
199
+ const item = replaceItem(storage, resource, req.params.id, req.body);
200
+ if (!item) return reply.status(404).send({ error: "Not found" });
201
+ return item;
202
+ });
203
+ server.patch(`${prefix}/:id`, (req, reply) => {
204
+ const item = patchItem(storage, resource, req.params.id, req.body);
205
+ if (!item) return reply.status(404).send({ error: "Not found" });
206
+ return item;
207
+ });
208
+ server.delete(`${prefix}/:id`, (req, reply) => {
209
+ const item = deleteItem(storage, resource, req.params.id);
210
+ if (!item) return reply.status(404).send({ error: "Not found" });
211
+ return item;
212
+ });
213
+ }
214
+
215
+ // src/router/routes/nested.routes.ts
216
+ function registerNestedRoutes(server, storage, relations, base) {
217
+ for (const [child, fields] of Object.entries(relations)) {
218
+ for (const [field, parent] of Object.entries(fields)) {
219
+ server.get(
220
+ `${base}/${parent}/:id/${child}`,
221
+ (req, reply) => {
222
+ const parentCollection = storage.getCollection(parent) ?? [];
223
+ const parentItem = findById(parentCollection, req.params.id);
224
+ if (!parentItem) return reply.status(404).send({ error: "Not found" });
225
+ const children = (storage.getCollection(child) ?? []).filter(
226
+ (item) => String(item[field]) === req.params.id
227
+ );
228
+ return children;
229
+ }
230
+ );
231
+ }
232
+ }
233
+ }
234
+
235
+ // src/router/resource.router.ts
236
+ function registerResourceRoutes(server, storage, base) {
237
+ for (const resource of Object.keys(storage.getData())) {
238
+ const prefix = `${base}/${resource}`;
239
+ registerCollectionRoutes(server, storage, resource, prefix);
240
+ registerItemRoutes(server, storage, resource, prefix);
241
+ }
242
+ registerNestedRoutes(server, storage, storage.getRelations(), base);
243
+ }
244
+
245
+ // src/server/createServer.ts
246
+ async function createServer(storage, options) {
247
+ const server = Fastify();
248
+ await server.register(cors);
249
+ registerResourceRoutes(server, storage, options.base);
250
+ return server;
251
+ }
252
+
253
+ // src/config/loadOptions.ts
254
+ import { z } from "zod";
255
+ var serverOptionsSchema = z.object({
256
+ file: z.string().min(1),
257
+ port: z.coerce.number().int().positive().default(3070),
258
+ host: z.string().default("localhost"),
259
+ base: z.string().default("").transform((v) => v && !v.startsWith("/") ? `/${v}` : v)
260
+ });
261
+
262
+ // src/cli/commands/serve.ts
263
+ function registerServe(program2) {
264
+ program2.command("serve").description("Start the mock server using a YAML file as database").argument("[file]", "Path to the YAML database file", "db.yml").option("-p, --port <number>", "Port to listen on", "3070").option("-H, --host <host>", "Host to bind", "localhost").option("-b, --base <path>", "Base path prefix for all routes", "").action(async (file, flags) => {
265
+ const options = serverOptionsSchema.parse({
266
+ file,
267
+ port: flags["port"],
268
+ host: flags["host"],
269
+ base: flags["base"]
270
+ });
271
+ const storage = createYamlStorage(options.file);
272
+ const server = await createServer(storage, options);
273
+ await server.listen({ port: options.port, host: options.host });
274
+ const collections = Object.keys(storage.getData());
275
+ const baseLabel = options.base || "/";
276
+ console.log(`
277
+ yrest running at http://${options.host}:${options.port}`);
278
+ console.log(`
279
+ Resources (base: ${baseLabel}):`);
280
+ for (const name of collections) {
281
+ console.log(` /${name}`);
282
+ }
283
+ console.log("");
284
+ });
285
+ }
286
+
287
+ // src/cli/index.ts
288
+ program.name("yrest").description("Zero-config REST API mock server powered by a YAML file").version("0.1.0");
289
+ registerInit(program);
290
+ registerServe(program);
291
+ program.parse();
@@ -0,0 +1,38 @@
1
+ import * as fastify from 'fastify';
2
+ import * as http from 'http';
3
+ import { z } from 'zod';
4
+
5
+ type Resource = Record<string, unknown>;
6
+ type DbData = Record<string, Resource[]>;
7
+ type Relations = Record<string, Record<string, string>>;
8
+
9
+ interface YamlStorage {
10
+ getData(): DbData;
11
+ getRelations(): Relations;
12
+ getCollection(name: string): Resource[] | undefined;
13
+ setCollection(name: string, items: Resource[]): void;
14
+ persist(): void;
15
+ }
16
+ declare function createYamlStorage(filePath: string): YamlStorage;
17
+
18
+ declare const serverOptionsSchema: z.ZodObject<{
19
+ file: z.ZodString;
20
+ port: z.ZodDefault<z.ZodNumber>;
21
+ host: z.ZodDefault<z.ZodString>;
22
+ base: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ file: string;
25
+ port: number;
26
+ host: string;
27
+ base: string;
28
+ }, {
29
+ file: string;
30
+ port?: number | undefined;
31
+ host?: string | undefined;
32
+ base?: string | undefined;
33
+ }>;
34
+ type ServerOptions = z.infer<typeof serverOptionsSchema>;
35
+
36
+ declare function createServer(storage: YamlStorage, options: ServerOptions): Promise<fastify.FastifyInstance<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, http.IncomingMessage, http.ServerResponse<http.IncomingMessage>, fastify.FastifyBaseLogger, fastify.FastifyTypeProviderDefault>>;
37
+
38
+ export { type DbData, type Relations, type Resource, type ServerOptions, createServer, createYamlStorage, serverOptionsSchema };
@@ -0,0 +1,38 @@
1
+ import * as fastify from 'fastify';
2
+ import * as http from 'http';
3
+ import { z } from 'zod';
4
+
5
+ type Resource = Record<string, unknown>;
6
+ type DbData = Record<string, Resource[]>;
7
+ type Relations = Record<string, Record<string, string>>;
8
+
9
+ interface YamlStorage {
10
+ getData(): DbData;
11
+ getRelations(): Relations;
12
+ getCollection(name: string): Resource[] | undefined;
13
+ setCollection(name: string, items: Resource[]): void;
14
+ persist(): void;
15
+ }
16
+ declare function createYamlStorage(filePath: string): YamlStorage;
17
+
18
+ declare const serverOptionsSchema: z.ZodObject<{
19
+ file: z.ZodString;
20
+ port: z.ZodDefault<z.ZodNumber>;
21
+ host: z.ZodDefault<z.ZodString>;
22
+ base: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ file: string;
25
+ port: number;
26
+ host: string;
27
+ base: string;
28
+ }, {
29
+ file: string;
30
+ port?: number | undefined;
31
+ host?: string | undefined;
32
+ base?: string | undefined;
33
+ }>;
34
+ type ServerOptions = z.infer<typeof serverOptionsSchema>;
35
+
36
+ declare function createServer(storage: YamlStorage, options: ServerOptions): Promise<fastify.FastifyInstance<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, http.IncomingMessage, http.ServerResponse<http.IncomingMessage>, fastify.FastifyBaseLogger, fastify.FastifyTypeProviderDefault>>;
37
+
38
+ export { type DbData, type Relations, type Resource, type ServerOptions, createServer, createYamlStorage, serverOptionsSchema };
package/dist/index.js ADDED
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ createServer: () => createServer,
34
+ createYamlStorage: () => createYamlStorage,
35
+ serverOptionsSchema: () => serverOptionsSchema
36
+ });
37
+ module.exports = __toCommonJS(src_exports);
38
+
39
+ // src/storage/yamlStorage.ts
40
+ var import_node_fs = require("fs");
41
+ var import_node_path = require("path");
42
+ var import_node_crypto = require("crypto");
43
+ var import_yaml = require("yaml");
44
+ function createYamlStorage(filePath) {
45
+ const absPath = (0, import_node_path.resolve)(filePath);
46
+ const raw = (0, import_yaml.parse)((0, import_node_fs.readFileSync)(absPath, "utf8")) ?? {};
47
+ const relations = raw["_rel"] ?? {};
48
+ const data = Object.fromEntries(
49
+ Object.entries(raw).filter(([key]) => key !== "_rel")
50
+ );
51
+ return {
52
+ getData() {
53
+ return data;
54
+ },
55
+ getRelations() {
56
+ return relations;
57
+ },
58
+ getCollection(name) {
59
+ return data[name];
60
+ },
61
+ setCollection(name, items) {
62
+ data[name] = items;
63
+ },
64
+ persist() {
65
+ const payload = Object.keys(relations).length > 0 ? { _rel: relations, ...data } : { ...data };
66
+ const tmp = (0, import_node_path.resolve)((0, import_node_path.dirname)(absPath), `.yrest-${(0, import_node_crypto.randomUUID)()}.tmp`);
67
+ (0, import_node_fs.writeFileSync)(tmp, (0, import_yaml.stringify)(payload), "utf8");
68
+ (0, import_node_fs.renameSync)(tmp, absPath);
69
+ }
70
+ };
71
+ }
72
+
73
+ // src/server/createServer.ts
74
+ var import_fastify = __toESM(require("fastify"));
75
+ var import_cors = __toESM(require("@fastify/cors"));
76
+
77
+ // src/services/resourceService.ts
78
+ function nextId(items) {
79
+ const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
80
+ return ids.length > 0 ? Math.max(...ids) + 1 : 1;
81
+ }
82
+ function findById(items, id) {
83
+ return items.find((i) => String(i["id"]) === id);
84
+ }
85
+ function findIndexById(items, id) {
86
+ return items.findIndex((i) => String(i["id"]) === id);
87
+ }
88
+ function createItem(storage, resource, body) {
89
+ const collection = storage.getCollection(resource) ?? [];
90
+ const item = {
91
+ id: body["id"] !== void 0 ? body["id"] : nextId(collection),
92
+ ...body
93
+ };
94
+ storage.setCollection(resource, [...collection, item]);
95
+ storage.persist();
96
+ return item;
97
+ }
98
+ function replaceItem(storage, resource, id, body) {
99
+ const collection = storage.getCollection(resource) ?? [];
100
+ const idx = findIndexById(collection, id);
101
+ if (idx === -1) return void 0;
102
+ const updated = { ...body, id: collection[idx]["id"] };
103
+ collection[idx] = updated;
104
+ storage.setCollection(resource, collection);
105
+ storage.persist();
106
+ return updated;
107
+ }
108
+ function patchItem(storage, resource, id, body) {
109
+ const collection = storage.getCollection(resource) ?? [];
110
+ const idx = findIndexById(collection, id);
111
+ if (idx === -1) return void 0;
112
+ const updated = { ...collection[idx], ...body };
113
+ collection[idx] = updated;
114
+ storage.setCollection(resource, collection);
115
+ storage.persist();
116
+ return updated;
117
+ }
118
+ function deleteItem(storage, resource, id) {
119
+ const collection = storage.getCollection(resource) ?? [];
120
+ const idx = findIndexById(collection, id);
121
+ if (idx === -1) return void 0;
122
+ const [deleted] = collection.splice(idx, 1);
123
+ storage.setCollection(resource, collection);
124
+ storage.persist();
125
+ return deleted;
126
+ }
127
+
128
+ // src/router/routes/collection.routes.ts
129
+ function registerCollectionRoutes(server, storage, resource, prefix) {
130
+ server.get(prefix, () => storage.getCollection(resource) ?? []);
131
+ server.post(prefix, (req, reply) => {
132
+ const item = createItem(storage, resource, req.body);
133
+ return reply.status(201).send(item);
134
+ });
135
+ }
136
+
137
+ // src/router/routes/item.routes.ts
138
+ function registerItemRoutes(server, storage, resource, prefix) {
139
+ server.get(`${prefix}/:id`, (req, reply) => {
140
+ const item = findById(storage.getCollection(resource) ?? [], req.params.id);
141
+ if (!item) return reply.status(404).send({ error: "Not found" });
142
+ return item;
143
+ });
144
+ server.put(`${prefix}/:id`, (req, reply) => {
145
+ const item = replaceItem(storage, resource, req.params.id, req.body);
146
+ if (!item) return reply.status(404).send({ error: "Not found" });
147
+ return item;
148
+ });
149
+ server.patch(`${prefix}/:id`, (req, reply) => {
150
+ const item = patchItem(storage, resource, req.params.id, req.body);
151
+ if (!item) return reply.status(404).send({ error: "Not found" });
152
+ return item;
153
+ });
154
+ server.delete(`${prefix}/:id`, (req, reply) => {
155
+ const item = deleteItem(storage, resource, req.params.id);
156
+ if (!item) return reply.status(404).send({ error: "Not found" });
157
+ return item;
158
+ });
159
+ }
160
+
161
+ // src/router/routes/nested.routes.ts
162
+ function registerNestedRoutes(server, storage, relations, base) {
163
+ for (const [child, fields] of Object.entries(relations)) {
164
+ for (const [field, parent] of Object.entries(fields)) {
165
+ server.get(
166
+ `${base}/${parent}/:id/${child}`,
167
+ (req, reply) => {
168
+ const parentCollection = storage.getCollection(parent) ?? [];
169
+ const parentItem = findById(parentCollection, req.params.id);
170
+ if (!parentItem) return reply.status(404).send({ error: "Not found" });
171
+ const children = (storage.getCollection(child) ?? []).filter(
172
+ (item) => String(item[field]) === req.params.id
173
+ );
174
+ return children;
175
+ }
176
+ );
177
+ }
178
+ }
179
+ }
180
+
181
+ // src/router/resource.router.ts
182
+ function registerResourceRoutes(server, storage, base) {
183
+ for (const resource of Object.keys(storage.getData())) {
184
+ const prefix = `${base}/${resource}`;
185
+ registerCollectionRoutes(server, storage, resource, prefix);
186
+ registerItemRoutes(server, storage, resource, prefix);
187
+ }
188
+ registerNestedRoutes(server, storage, storage.getRelations(), base);
189
+ }
190
+
191
+ // src/server/createServer.ts
192
+ async function createServer(storage, options) {
193
+ const server = (0, import_fastify.default)();
194
+ await server.register(import_cors.default);
195
+ registerResourceRoutes(server, storage, options.base);
196
+ return server;
197
+ }
198
+
199
+ // src/config/loadOptions.ts
200
+ var import_zod = require("zod");
201
+ var serverOptionsSchema = import_zod.z.object({
202
+ file: import_zod.z.string().min(1),
203
+ port: import_zod.z.coerce.number().int().positive().default(3070),
204
+ host: import_zod.z.string().default("localhost"),
205
+ base: import_zod.z.string().default("").transform((v) => v && !v.startsWith("/") ? `/${v}` : v)
206
+ });
207
+ // Annotate the CommonJS export names for ESM import in node:
208
+ 0 && (module.exports = {
209
+ createServer,
210
+ createYamlStorage,
211
+ serverOptionsSchema
212
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,173 @@
1
+ // src/storage/yamlStorage.ts
2
+ import { readFileSync, writeFileSync, renameSync } from "fs";
3
+ import { resolve, dirname } from "path";
4
+ import { randomUUID } from "crypto";
5
+ import { parse, stringify } from "yaml";
6
+ function createYamlStorage(filePath) {
7
+ const absPath = resolve(filePath);
8
+ const raw = parse(readFileSync(absPath, "utf8")) ?? {};
9
+ const relations = raw["_rel"] ?? {};
10
+ const data = Object.fromEntries(
11
+ Object.entries(raw).filter(([key]) => key !== "_rel")
12
+ );
13
+ return {
14
+ getData() {
15
+ return data;
16
+ },
17
+ getRelations() {
18
+ return relations;
19
+ },
20
+ getCollection(name) {
21
+ return data[name];
22
+ },
23
+ setCollection(name, items) {
24
+ data[name] = items;
25
+ },
26
+ persist() {
27
+ const payload = Object.keys(relations).length > 0 ? { _rel: relations, ...data } : { ...data };
28
+ const tmp = resolve(dirname(absPath), `.yrest-${randomUUID()}.tmp`);
29
+ writeFileSync(tmp, stringify(payload), "utf8");
30
+ renameSync(tmp, absPath);
31
+ }
32
+ };
33
+ }
34
+
35
+ // src/server/createServer.ts
36
+ import Fastify from "fastify";
37
+ import cors from "@fastify/cors";
38
+
39
+ // src/services/resourceService.ts
40
+ function nextId(items) {
41
+ const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
42
+ return ids.length > 0 ? Math.max(...ids) + 1 : 1;
43
+ }
44
+ function findById(items, id) {
45
+ return items.find((i) => String(i["id"]) === id);
46
+ }
47
+ function findIndexById(items, id) {
48
+ return items.findIndex((i) => String(i["id"]) === id);
49
+ }
50
+ function createItem(storage, resource, body) {
51
+ const collection = storage.getCollection(resource) ?? [];
52
+ const item = {
53
+ id: body["id"] !== void 0 ? body["id"] : nextId(collection),
54
+ ...body
55
+ };
56
+ storage.setCollection(resource, [...collection, item]);
57
+ storage.persist();
58
+ return item;
59
+ }
60
+ function replaceItem(storage, resource, id, body) {
61
+ const collection = storage.getCollection(resource) ?? [];
62
+ const idx = findIndexById(collection, id);
63
+ if (idx === -1) return void 0;
64
+ const updated = { ...body, id: collection[idx]["id"] };
65
+ collection[idx] = updated;
66
+ storage.setCollection(resource, collection);
67
+ storage.persist();
68
+ return updated;
69
+ }
70
+ function patchItem(storage, resource, id, body) {
71
+ const collection = storage.getCollection(resource) ?? [];
72
+ const idx = findIndexById(collection, id);
73
+ if (idx === -1) return void 0;
74
+ const updated = { ...collection[idx], ...body };
75
+ collection[idx] = updated;
76
+ storage.setCollection(resource, collection);
77
+ storage.persist();
78
+ return updated;
79
+ }
80
+ function deleteItem(storage, resource, id) {
81
+ const collection = storage.getCollection(resource) ?? [];
82
+ const idx = findIndexById(collection, id);
83
+ if (idx === -1) return void 0;
84
+ const [deleted] = collection.splice(idx, 1);
85
+ storage.setCollection(resource, collection);
86
+ storage.persist();
87
+ return deleted;
88
+ }
89
+
90
+ // src/router/routes/collection.routes.ts
91
+ function registerCollectionRoutes(server, storage, resource, prefix) {
92
+ server.get(prefix, () => storage.getCollection(resource) ?? []);
93
+ server.post(prefix, (req, reply) => {
94
+ const item = createItem(storage, resource, req.body);
95
+ return reply.status(201).send(item);
96
+ });
97
+ }
98
+
99
+ // src/router/routes/item.routes.ts
100
+ function registerItemRoutes(server, storage, resource, prefix) {
101
+ server.get(`${prefix}/:id`, (req, reply) => {
102
+ const item = findById(storage.getCollection(resource) ?? [], req.params.id);
103
+ if (!item) return reply.status(404).send({ error: "Not found" });
104
+ return item;
105
+ });
106
+ server.put(`${prefix}/:id`, (req, reply) => {
107
+ const item = replaceItem(storage, resource, req.params.id, req.body);
108
+ if (!item) return reply.status(404).send({ error: "Not found" });
109
+ return item;
110
+ });
111
+ server.patch(`${prefix}/:id`, (req, reply) => {
112
+ const item = patchItem(storage, resource, req.params.id, req.body);
113
+ if (!item) return reply.status(404).send({ error: "Not found" });
114
+ return item;
115
+ });
116
+ server.delete(`${prefix}/:id`, (req, reply) => {
117
+ const item = deleteItem(storage, resource, req.params.id);
118
+ if (!item) return reply.status(404).send({ error: "Not found" });
119
+ return item;
120
+ });
121
+ }
122
+
123
+ // src/router/routes/nested.routes.ts
124
+ function registerNestedRoutes(server, storage, relations, base) {
125
+ for (const [child, fields] of Object.entries(relations)) {
126
+ for (const [field, parent] of Object.entries(fields)) {
127
+ server.get(
128
+ `${base}/${parent}/:id/${child}`,
129
+ (req, reply) => {
130
+ const parentCollection = storage.getCollection(parent) ?? [];
131
+ const parentItem = findById(parentCollection, req.params.id);
132
+ if (!parentItem) return reply.status(404).send({ error: "Not found" });
133
+ const children = (storage.getCollection(child) ?? []).filter(
134
+ (item) => String(item[field]) === req.params.id
135
+ );
136
+ return children;
137
+ }
138
+ );
139
+ }
140
+ }
141
+ }
142
+
143
+ // src/router/resource.router.ts
144
+ function registerResourceRoutes(server, storage, base) {
145
+ for (const resource of Object.keys(storage.getData())) {
146
+ const prefix = `${base}/${resource}`;
147
+ registerCollectionRoutes(server, storage, resource, prefix);
148
+ registerItemRoutes(server, storage, resource, prefix);
149
+ }
150
+ registerNestedRoutes(server, storage, storage.getRelations(), base);
151
+ }
152
+
153
+ // src/server/createServer.ts
154
+ async function createServer(storage, options) {
155
+ const server = Fastify();
156
+ await server.register(cors);
157
+ registerResourceRoutes(server, storage, options.base);
158
+ return server;
159
+ }
160
+
161
+ // src/config/loadOptions.ts
162
+ import { z } from "zod";
163
+ var serverOptionsSchema = z.object({
164
+ file: z.string().min(1),
165
+ port: z.coerce.number().int().positive().default(3070),
166
+ host: z.string().default("localhost"),
167
+ base: z.string().default("").transform((v) => v && !v.startsWith("/") ? `/${v}` : v)
168
+ });
169
+ export {
170
+ createServer,
171
+ createYamlStorage,
172
+ serverOptionsSchema
173
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@aggiovato/yrest",
3
+ "version": "0.1.0",
4
+ "description": "Zero-config REST API mock server powered by a YAML file",
5
+ "keywords": [
6
+ "yaml",
7
+ "rest",
8
+ "mock",
9
+ "api",
10
+ "cli",
11
+ "fastify"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": ""
18
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.js"
27
+ }
28
+ },
29
+ "bin": {
30
+ "yrest": "./dist/cli/index.js"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "test": "vitest",
39
+ "test:run": "vitest run",
40
+ "typecheck": "tsc --noEmit",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "dependencies": {
44
+ "@fastify/cors": "^10.0.0",
45
+ "commander": "^12.1.0",
46
+ "fastify": "^5.0.0",
47
+ "yaml": "^2.4.5",
48
+ "zod": "^3.23.8"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.0.0",
52
+ "tsup": "^8.1.0",
53
+ "typescript": "^5.5.0",
54
+ "vitest": "^4.1.8"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ }
59
+ }