rest_api_faker 0.0.5 → 0.0.6

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.
Files changed (50) hide show
  1. package/bin/rest_api_faker.js +1 -1
  2. package/dist/cli.d.ts +2 -2
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +346 -1992
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +20 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +133 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/database.d.ts +29 -0
  11. package/dist/database.d.ts.map +1 -0
  12. package/dist/database.js +216 -0
  13. package/dist/database.js.map +1 -0
  14. package/dist/index.d.ts +6 -7
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +9 -46
  17. package/dist/index.js.map +1 -1
  18. package/dist/loader.d.ts +4 -0
  19. package/dist/loader.d.ts.map +1 -0
  20. package/dist/loader.js +42 -0
  21. package/dist/loader.js.map +1 -0
  22. package/dist/logger.d.ts +14 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +61 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/query.d.ts +19 -0
  27. package/dist/query.d.ts.map +1 -0
  28. package/dist/query.js +289 -0
  29. package/dist/query.js.map +1 -0
  30. package/dist/relationships.d.ts +10 -0
  31. package/dist/relationships.d.ts.map +1 -0
  32. package/dist/relationships.js +95 -0
  33. package/dist/relationships.js.map +1 -0
  34. package/dist/rewriter.d.ts +5 -0
  35. package/dist/rewriter.d.ts.map +1 -0
  36. package/dist/rewriter.js +135 -0
  37. package/dist/rewriter.js.map +1 -0
  38. package/dist/router.d.ts +9 -0
  39. package/dist/router.d.ts.map +1 -0
  40. package/dist/router.js +275 -0
  41. package/dist/router.js.map +1 -0
  42. package/dist/server.d.ts +17 -0
  43. package/dist/server.d.ts.map +1 -0
  44. package/dist/server.js +90 -0
  45. package/dist/server.js.map +1 -0
  46. package/dist/static.d.ts +8 -0
  47. package/dist/static.d.ts.map +1 -0
  48. package/dist/static.js +148 -0
  49. package/dist/static.js.map +1 -0
  50. package/package.json +13 -14
package/dist/cli.js CHANGED
@@ -1,2021 +1,375 @@
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 __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
-
25
- // src/cli.ts
26
- var import_yargs = __toESM(require("yargs"));
27
- var import_helpers = require("yargs/helpers");
28
- var import_fs3 = require("fs");
29
- var import_path3 = require("path");
30
- var import_chokidar = require("chokidar");
31
-
32
- // src/database.ts
33
- var import_lowdb = require("lowdb");
34
- var import_node = require("lowdb/node");
35
- var import_fs = require("fs");
36
- var import_path = require("path");
37
- var import_url = require("url");
38
- var Database = class {
39
- db;
40
- filePath;
41
- options;
42
- /**
43
- * Creates a new Database instance
44
- *
45
- * @param source - Path to JSON file or object with data
46
- * @param options - Database configuration options
47
- *
48
- * @example
49
- * ```typescript
50
- * const db = new Database('db.json', { idField: 'id', autoSave: false });
51
- * await db.init();
52
- * ```
53
- */
54
- constructor(source, options = {}) {
55
- this.options = {
56
- idField: options.idField || "id",
57
- foreignKeySuffix: options.foreignKeySuffix || "Id",
58
- autoSave: options.autoSave !== void 0 ? options.autoSave : true
59
- };
60
- if (typeof source === "string") {
61
- this.filePath = (0, import_path.resolve)(source);
62
- const adapter = new import_node.JSONFile(this.filePath);
63
- this.db = new import_lowdb.Low(adapter, {});
64
- } else {
65
- this.filePath = "";
66
- const adapter = new import_node.JSONFile(":memory:");
67
- this.db = new import_lowdb.Low(adapter, source);
68
- }
69
- }
70
- /**
71
- * Initialize the database by reading from file or using provided data
72
- * Supports .json, .js, .ts, and .mjs files
73
- *
74
- * @throws Error if file doesn't exist or contains invalid data
75
- */
76
- async init() {
77
- if (this.filePath && !(0, import_fs.existsSync)(this.filePath)) {
78
- const ext = this.filePath.endsWith(".js") ? ".js" : ".json";
79
- throw new Error(
80
- `Database file not found: ${this.filePath}
81
-
82
- Make sure the file exists and the path is correct.
83
- Expected format: ${ext === ".js" ? "JavaScript module exporting data" : "JSON file with data structure"}`
84
- );
85
- }
86
- if (this.filePath) {
87
- const ext = (0, import_path.extname)(this.filePath).toLowerCase();
88
- if (ext === ".js" || ext === ".mjs" || ext === ".cjs" || ext === ".ts") {
89
- try {
90
- const fileUrl = (0, import_url.pathToFileURL)(this.filePath).href;
91
- const module2 = await import(fileUrl);
92
- const data = module2.default ?? module2;
93
- if (typeof data === "function") {
94
- const result = await Promise.resolve(data());
95
- if (typeof result !== "object" || result === null) {
96
- throw new Error("JavaScript module function must return an object");
97
- }
98
- this.db.data = result;
99
- } else if (typeof data === "object" && data !== null) {
100
- this.db.data = data;
101
- } else {
102
- throw new Error("JavaScript module must export an object or function");
103
- }
104
- } catch (error) {
105
- throw new Error(
106
- `Failed to load JavaScript module: ${error instanceof Error ? error.message : "Unknown error"}`
107
- );
108
- }
109
- } else {
110
- await this.db.read();
111
- }
112
- } else {
113
- await this.db.read();
114
- }
115
- if (typeof this.db.data !== "object") {
116
- this.db.data = {};
117
- }
118
- for (const key in this.db.data) {
119
- const value = this.db.data[key];
120
- if (value !== null && typeof value !== "object") {
121
- throw new Error(`Invalid data structure: ${key} must be an array or object`);
122
- }
123
- }
124
- }
125
- /**
126
- * Get all data from the database
127
- *
128
- * @returns Complete database data
129
- */
130
- getData() {
131
- return this.db.data;
132
- }
133
- /**
134
- * Get a specific collection (array) or resource (object)
135
- *
136
- * @param name - Name of the collection/resource
137
- * @returns Collection array, resource object, or undefined
138
- */
139
- getCollection(name) {
140
- return this.db.data[name];
141
- }
142
- /**
143
- * Get a single item from a collection by ID
144
- *
145
- * @param collectionName - Name of the collection
146
- * @param id - ID of the item
147
- * @returns The item or undefined
148
- */
149
- getById(collectionName, id) {
150
- const collection = this.db.data[collectionName];
151
- if (!Array.isArray(collection)) {
152
- return void 0;
153
- }
154
- return collection.find((item) => {
155
- if (typeof item !== "object" || item === null) {
156
- return false;
157
- }
158
- const record = item;
159
- return record[this.options.idField] === id || record[this.options.idField] === Number(id);
160
- });
161
- }
162
- /**
163
- * Generate next ID for a collection
164
- *
165
- * @param collectionName - Name of the collection
166
- * @returns Next available ID
167
- */
168
- generateId(collectionName) {
169
- const collection = this.db.data[collectionName];
170
- if (!Array.isArray(collection)) {
171
- return 1;
172
- }
173
- let maxId = 0;
174
- for (const item of collection) {
175
- if (typeof item === "object" && item !== null) {
176
- const record = item;
177
- const idValue = record[this.options.idField];
178
- if (typeof idValue === "number" && idValue > maxId) {
179
- maxId = idValue;
180
- }
181
- }
182
- }
183
- return maxId + 1;
184
- }
185
- /**
186
- * Create a new item in a collection
187
- *
188
- * @param collectionName - Name of the collection
189
- * @param data - Data to insert
190
- * @returns Created item with ID
191
- * @throws Error if collection is not an array
192
- */
193
- async create(collectionName, data) {
194
- let collection = this.db.data[collectionName];
195
- if (!collection) {
196
- collection = [];
197
- this.db.data[collectionName] = collection;
198
- }
199
- if (!Array.isArray(collection)) {
200
- throw new Error(`Cannot create in ${collectionName}: not a collection`);
201
- }
202
- const idValue = data[this.options.idField] !== void 0 ? data[this.options.idField] : this.generateId(collectionName);
203
- const existingItem = this.getById(collectionName, idValue);
204
- if (existingItem) {
205
- throw new Error(`Item with ${this.options.idField}=${String(idValue)} already exists`);
206
- }
207
- const newItem = { ...data, [this.options.idField]: idValue };
208
- collection.push(newItem);
209
- await this.save();
210
- return newItem;
211
- }
212
- /**
213
- * Update an item in a collection (full replacement)
214
- *
215
- * @param collectionName - Name of the collection
216
- * @param id - ID of the item
217
- * @param data - New data (ID field will be preserved)
218
- * @returns Updated item or undefined if not found
219
- */
220
- async update(collectionName, id, data) {
221
- const collection = this.db.data[collectionName];
222
- if (!Array.isArray(collection)) {
223
- return void 0;
224
- }
225
- const index = collection.findIndex((item) => {
226
- if (typeof item !== "object" || item === null) {
227
- return false;
228
- }
229
- const record = item;
230
- return record[this.options.idField] === id || record[this.options.idField] === Number(id);
231
- });
232
- if (index === -1) {
233
- return void 0;
234
- }
235
- const originalItem = collection[index];
236
- const originalId = originalItem[this.options.idField];
237
- const updatedItem = { ...data, [this.options.idField]: originalId };
238
- collection[index] = updatedItem;
239
- await this.save();
240
- return updatedItem;
241
- }
242
- /**
243
- * Patch an item in a collection (partial update)
244
- *
245
- * @param collectionName - Name of the collection
246
- * @param id - ID of the item
247
- * @param data - Partial data to merge (ID field will be ignored)
248
- * @returns Updated item or undefined if not found
249
- */
250
- async patch(collectionName, id, data) {
251
- const collection = this.db.data[collectionName];
252
- if (!Array.isArray(collection)) {
253
- return void 0;
254
- }
255
- const index = collection.findIndex((item) => {
256
- if (typeof item !== "object" || item === null) {
257
- return false;
258
- }
259
- const record = item;
260
- return record[this.options.idField] === id || record[this.options.idField] === Number(id);
261
- });
262
- if (index === -1) {
263
- return void 0;
264
- }
265
- const currentItem = collection[index];
266
- const { [this.options.idField]: _ignoredId, ...patchData } = data;
267
- const patchedItem = { ...currentItem, ...patchData };
268
- collection[index] = patchedItem;
269
- await this.save();
270
- return patchedItem;
271
- }
272
- /**
273
- * Delete an item from a collection
274
- *
275
- * @param collectionName - Name of the collection
276
- * @param id - ID of the item
277
- * @returns true if deleted, false if not found
278
- */
279
- async delete(collectionName, id) {
280
- const collection = this.db.data[collectionName];
281
- if (!Array.isArray(collection)) {
282
- return false;
283
- }
284
- const index = collection.findIndex((item) => {
285
- if (typeof item !== "object" || item === null) {
286
- return false;
287
- }
288
- const record = item;
289
- return record[this.options.idField] === id || record[this.options.idField] === Number(id);
290
- });
291
- if (index === -1) {
292
- return false;
293
- }
294
- collection.splice(index, 1);
295
- await this.save();
296
- return true;
297
- }
298
- /**
299
- * Update or create a singular resource
300
- *
301
- * @param resourceName - Name of the resource
302
- * @param data - Resource data
303
- * @returns Updated resource
304
- */
305
- async updateSingular(resourceName, data) {
306
- this.db.data[resourceName] = data;
307
- await this.save();
308
- return data;
309
- }
310
- /**
311
- * Save the database to file
312
- */
313
- async save() {
314
- if (this.options.autoSave) {
315
- await this.db.write();
316
- }
317
- }
318
- /**
319
- * Check if a resource is a collection (array) or singular (object)
320
- *
321
- * @param name - Name of the resource
322
- * @returns true if collection, false if singular or doesn't exist
323
- */
324
- isCollection(name) {
325
- return Array.isArray(this.db.data[name]);
326
- }
327
- /**
328
- * Get ID field name
329
- */
330
- getIdField() {
331
- return this.options.idField;
332
- }
333
- /**
334
- * Get foreign key suffix
335
- */
336
- getForeignKeySuffix() {
337
- return this.options.foreignKeySuffix;
338
- }
339
- };
340
-
341
- // src/server.ts
342
- var import_express3 = __toESM(require("express"));
343
- var import_cors = __toESM(require("cors"));
344
- var import_compression = __toESM(require("compression"));
345
-
346
- // src/router.ts
347
- var import_express = require("express");
348
-
349
- // src/logger.ts
350
- var import_picocolors = __toESM(require("picocolors"));
351
- var currentLogLevel = "info";
352
- var LOG_LEVELS = {
353
- trace: 0,
354
- debug: 1,
355
- info: 2
356
- };
357
- function setLogLevel(level) {
358
- currentLogLevel = level;
359
- }
360
- function shouldLog(messageLevel) {
361
- return LOG_LEVELS[messageLevel] >= LOG_LEVELS[currentLogLevel];
362
- }
363
- var logger = {
364
- /**
365
- * Log success message in green
366
- *
367
- * @param message - Success message
368
- *
369
- * @example
370
- * ```typescript
371
- * logger.success('Server started successfully');
372
- * // Output: ✓ Server started successfully (in green)
373
- * ```
374
- */
375
- success(message) {
376
- console.log(import_picocolors.default.green(`\u2713 ${message}`));
377
- },
378
- /**
379
- * Log error message in red
380
- *
381
- * @param message - Error message
382
- *
383
- * @example
384
- * ```typescript
385
- * logger.error('Failed to load file');
386
- * // Output: ✗ Failed to load file (in red)
387
- * ```
388
- */
389
- error(message) {
390
- console.error(import_picocolors.default.red(`\u2717 ${message}`));
391
- },
392
- /**
393
- * Log warning message in yellow
394
- *
395
- * @param message - Warning message
396
- *
397
- * @example
398
- * ```typescript
399
- * logger.warn('Using default port');
400
- * // Output: ⚠ Using default port (in yellow)
401
- * ```
402
- */
403
- warn(message) {
404
- console.warn(import_picocolors.default.yellow(`\u26A0 ${message}`));
405
- },
406
- /**
407
- * Log info message in cyan
408
- *
409
- * @param message - Info message
410
- *
411
- * @example
412
- * ```typescript
413
- * logger.info('Watching for changes...');
414
- * // Output: ℹ Watching for changes... (in cyan)
415
- * ```
416
- */
417
- info(message) {
418
- console.log(import_picocolors.default.cyan(`\u2139 ${message}`));
419
- },
420
- /**
421
- * Log debug message in dim gray (for verbose/debug mode)
422
- *
423
- * @param message - Debug message
424
- *
425
- * @example
426
- * ```typescript
427
- * logger.debug('Loading configuration from file');
428
- * // Output: 🐛 Loading configuration from file (in dim gray)
429
- * ```
430
- */
431
- debug(message) {
432
- if (shouldLog("debug")) {
433
- console.log(import_picocolors.default.dim(import_picocolors.default.gray(`\u{1F41B} ${message}`)));
434
- }
435
- },
436
- /**
437
- * Log trace message in magenta (for very detailed tracing)
438
- *
439
- * @param message - Trace message
440
- *
441
- * @example
442
- * ```typescript
443
- * logger.trace('Entering function xyz');
444
- * // Output: 🔍 Entering function xyz (in magenta)
445
- * ```
446
- */
447
- trace(message) {
448
- if (shouldLog("trace")) {
449
- console.log(import_picocolors.default.magenta(`\u{1F50D} ${message}`));
450
- }
451
- },
452
- /**
453
- * Log plain message without color
454
- *
455
- * @param message - Message to log
456
- *
457
- * @example
458
- * ```typescript
459
- * logger.log('http://localhost:3000');
460
- * // Output: http://localhost:3000
461
- * ```
462
- */
463
- log(message) {
464
- console.log(message);
465
- },
466
- /**
467
- * Log request in gray (for non-quiet mode)
468
- *
469
- * @param method - HTTP method
470
- * @param url - Request URL
471
- *
472
- * @example
473
- * ```typescript
474
- * logger.request('GET', '/api/users');
475
- * // Output: [timestamp] GET /api/users (in gray)
476
- * ```
477
- */
478
- request(method, url) {
479
- const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
480
- console.log(import_picocolors.default.gray(`[${timestamp}] ${method} ${url}`));
481
- },
482
- /**
483
- * Log formatted banner
484
- *
485
- * @param lines - Array of lines to display
486
- *
487
- * @example
488
- * ```typescript
489
- * logger.banner([
490
- * '🚀 API Faker is running!',
491
- * '',
492
- * ' Resources:',
493
- * ' http://localhost:3000/'
494
- * ]);
495
- * ```
496
- */
497
- banner(lines) {
498
- console.log();
499
- for (const line of lines) {
500
- if (line.includes("http://") || line.includes("https://")) {
501
- const colored = line.replace(/(https?:\/\/[^\s]+)/g, (url) => import_picocolors.default.cyan(url));
502
- console.log(colored);
503
- } else if (line.startsWith("\u{1F680}")) {
504
- console.log(import_picocolors.default.bold(line));
505
- } else {
506
- console.log(line);
507
- }
508
- }
509
- console.log();
510
- }
511
- };
512
-
513
- // src/query.ts
514
- function parseQuery(req) {
515
- const query = req.query;
516
- const filters = {};
517
- const operators = {};
518
- const sort = [];
519
- const order = [];
520
- let page;
521
- let limit;
522
- let start;
523
- let end;
524
- let q;
525
- for (const [key, value] of Object.entries(query)) {
526
- const stringValue = typeof value === "string" ? value : Array.isArray(value) && value.length > 0 && typeof value[0] === "string" ? value[0] : null;
527
- if (stringValue === null && !Array.isArray(value)) {
528
- continue;
529
- }
530
- if (key === "_sort") {
531
- if (typeof value === "string") {
532
- sort.push(...value.split(","));
533
- } else if (Array.isArray(value)) {
534
- for (const v of value) {
535
- if (typeof v === "string") {
536
- sort.push(v);
537
- }
538
- }
539
- }
540
- continue;
541
- }
542
- if (key === "_order") {
543
- if (typeof value === "string") {
544
- order.push(...value.split(","));
545
- } else if (Array.isArray(value)) {
546
- for (const v of value) {
547
- if (typeof v === "string") {
548
- order.push(v);
549
- }
550
- }
551
- }
552
- continue;
553
- }
554
- if (key === "_page" && stringValue) {
555
- const parsed = parseInt(stringValue, 10);
556
- if (!isNaN(parsed) && parsed > 0) {
557
- page = parsed;
558
- }
559
- continue;
560
- }
561
- if (key === "_limit" && stringValue) {
562
- const parsed = parseInt(stringValue, 10);
563
- if (!isNaN(parsed) && parsed > 0) {
564
- limit = parsed;
565
- }
566
- continue;
567
- }
568
- if (key === "_start" && stringValue) {
569
- const parsed = parseInt(stringValue, 10);
570
- if (!isNaN(parsed) && parsed >= 0) {
571
- start = parsed;
572
- }
573
- continue;
574
- }
575
- if (key === "_end" && stringValue) {
576
- const parsed = parseInt(stringValue, 10);
577
- if (!isNaN(parsed) && parsed >= 0) {
578
- end = parsed;
579
- }
580
- continue;
581
- }
582
- if (key === "q" && stringValue && stringValue.trim() !== "") {
583
- q = stringValue;
584
- continue;
585
- }
586
- const operatorMatch = key.match(/^(.+)_(gte|lte|ne|like)$/);
587
- if (operatorMatch && stringValue) {
588
- const field = operatorMatch[1];
589
- const operator = operatorMatch[2];
590
- if (field && operator) {
591
- if (!operators[field]) {
592
- operators[field] = {};
593
- }
594
- operators[field][operator] = stringValue;
595
- }
596
- continue;
597
- }
598
- if (typeof value === "string") {
599
- filters[key] = value;
600
- } else if (Array.isArray(value)) {
601
- const stringValues = value.filter((v) => typeof v === "string");
602
- if (stringValues.length > 0) {
603
- filters[key] = stringValues;
604
- }
605
- }
606
- }
607
- const result = {
608
- filters,
609
- operators,
610
- sort,
611
- order
612
- };
613
- if (page !== void 0) result.page = page;
614
- if (limit !== void 0) result.limit = limit;
615
- if (start !== void 0) result.start = start;
616
- if (end !== void 0) result.end = end;
617
- if (q !== void 0) result.q = q;
618
- logger.debug(`Parsed query options: ${JSON.stringify(result)}`);
619
- return result;
620
- }
621
- function getNestedValue(obj, path) {
622
- if (typeof obj !== "object" || obj === null) {
623
- return void 0;
624
- }
625
- const keys = path.split(".");
626
- let current = obj;
627
- for (const key of keys) {
628
- if (typeof current !== "object" || current === null || !(key in current)) {
629
- return void 0;
630
- }
631
- current = current[key];
632
- }
633
- return current;
634
- }
635
- function matchesFilters(item, filters) {
636
- if (typeof item !== "object" || item === null) {
637
- return false;
638
- }
639
- for (const [key, filterValue] of Object.entries(filters)) {
640
- if (key === "q") continue;
641
- const itemValue = getNestedValue(item, key);
642
- if (Array.isArray(filterValue)) {
643
- const matched = filterValue.some((val) => String(itemValue) === val);
644
- if (!matched) {
645
- return false;
646
- }
647
- } else {
648
- if (String(itemValue) !== filterValue) {
649
- return false;
650
- }
651
- }
652
- }
653
- return true;
654
- }
655
- function matchesOperators(item, operators) {
656
- if (typeof item !== "object" || item === null) {
657
- return false;
658
- }
659
- for (const [key, ops] of Object.entries(operators)) {
660
- const itemValue = getNestedValue(item, key);
661
- for (const [operator, filterValue] of Object.entries(ops)) {
662
- if (operator === "gte") {
663
- if (Number(itemValue) < Number(filterValue)) {
664
- return false;
665
- }
666
- } else if (operator === "lte") {
667
- if (Number(itemValue) > Number(filterValue)) {
668
- return false;
669
- }
670
- } else if (operator === "ne") {
671
- if (String(itemValue) === filterValue) {
672
- return false;
673
- }
674
- } else if (operator === "like") {
675
- const itemStr = String(itemValue).toLowerCase();
676
- const filterStr = filterValue.toLowerCase();
677
- if (!itemStr.includes(filterStr)) {
678
- return false;
679
- }
680
- }
681
- }
682
- }
683
- return true;
684
- }
685
- function matchesSearch(item, searchText) {
686
- if (typeof item !== "object" || item === null) {
687
- return false;
688
- }
689
- const lowerSearch = searchText.toLowerCase();
690
- function searchObject(obj) {
691
- if (typeof obj === "string") {
692
- return obj.toLowerCase().includes(lowerSearch);
693
- }
694
- if (typeof obj === "object" && obj !== null) {
695
- return Object.values(obj).some(searchObject);
696
- }
697
- return false;
698
- }
699
- return searchObject(item);
700
- }
701
- function applyQuery(data, options) {
702
- let result = [...data];
703
- logger.trace(`Applying query: ${JSON.stringify(options)} on ${data.length.toString()} items`);
704
- if (options.q) {
705
- logger.trace(`Applying full-text search for: '${options.q}'`);
706
- const searchText = options.q;
707
- result = result.filter((item) => matchesSearch(item, searchText));
708
- }
709
- logger.trace(`After full-text search, ${result.length.toString()} items remain`);
710
- if (Object.keys(options.filters).length > 0) {
711
- result = result.filter((item) => matchesFilters(item, options.filters));
712
- }
713
- logger.trace(`After filtering, ${result.length.toString()} items remain`);
714
- if (Object.keys(options.operators).length > 0) {
715
- result = result.filter((item) => matchesOperators(item, options.operators));
716
- }
717
- logger.trace(`After applying operators, ${result.length.toString()} items remain`);
718
- const total = result.length;
719
- if (options.sort.length > 0) {
720
- const sortFields = options.sort;
721
- const sortOrders = options.order;
722
- result.sort((a, b) => {
723
- for (let i = 0; i < sortFields.length; i++) {
724
- const field = sortFields[i];
725
- if (!field) continue;
726
- const order = sortOrders[i] === "desc" ? -1 : 1;
727
- const aVal = getNestedValue(a, field);
728
- const bVal = getNestedValue(b, field);
729
- if (aVal === bVal) continue;
730
- if (aVal === void 0 || aVal === null) return 1 * order;
731
- if (bVal === void 0 || bVal === null) return -1 * order;
732
- if (aVal < bVal) return -1 * order;
733
- if (aVal > bVal) return 1 * order;
734
- }
735
- return 0;
736
- });
737
- }
738
- logger.trace(`After sorting, ${result.length.toString()} items remain`);
739
- if (options.start !== void 0 || options.end !== void 0) {
740
- logger.trace(`Applying slicing from ${options.start?.toString() ?? ""} to ${options.end?.toString() ?? "end"}`);
741
- const start = options.start ?? 0;
742
- const end = options.end ?? result.length;
743
- result = result.slice(start, end);
744
- } else if (options.page !== void 0 && options.limit !== void 0) {
745
- logger.trace(`Applying pagination: page ${options.page.toString()}, limit ${options.limit.toString()}`);
746
- const start = (options.page - 1) * options.limit;
747
- result = result.slice(start, start + options.limit);
748
- } else if (options.limit !== void 0) {
749
- logger.trace(`Applying limit: ${options.limit.toString()}`);
750
- result = result.slice(0, options.limit);
751
- }
752
- logger.trace(`After pagination/slicing, ${result.length.toString()} items remain`);
753
- return { data: result, total };
754
- }
755
- function generateLinkHeader(req, page, limit, total) {
756
- const host = req.get("host") ?? "localhost";
757
- const baseUrl = `${req.protocol}://${host}${req.path}`;
758
- const query = new URLSearchParams(req.query);
759
- const lastPage = Math.ceil(total / limit);
760
- const links = [];
761
- query.set("_page", "1");
762
- query.set("_limit", String(limit));
763
- links.push(`<${baseUrl}?${query.toString()}>; rel="first"`);
764
- if (page > 1) {
765
- query.set("_page", String(page - 1));
766
- links.push(`<${baseUrl}?${query.toString()}>; rel="prev"`);
767
- }
768
- if (page < lastPage) {
769
- query.set("_page", String(page + 1));
770
- links.push(`<${baseUrl}?${query.toString()}>; rel="next"`);
771
- }
772
- query.set("_page", String(lastPage));
773
- links.push(`<${baseUrl}?${query.toString()}>; rel="last"`);
774
- return links.join(", ");
775
- }
776
-
777
- // src/relationships.ts
778
- function getForeignKey(collectionName, foreignKeySuffix) {
779
- const singular = collectionName.endsWith("s") ? collectionName.slice(0, -1) : collectionName;
780
- return `${singular}${foreignKeySuffix}`;
781
- }
782
- function embedChildren(items, parentCollection, childCollection, db, idField, foreignKeySuffix) {
783
- const children = db.getCollection(childCollection);
784
- if (!Array.isArray(children)) {
785
- return items;
786
- }
787
- const foreignKey = getForeignKey(parentCollection, foreignKeySuffix);
788
- return items.map((item) => {
789
- const matchingChildren = children.filter((child) => {
790
- if (typeof child !== "object" || child === null) return false;
791
- const childFk = child[foreignKey];
792
- const parentId = item[idField];
793
- return childFk === parentId || String(childFk) === String(parentId);
794
- });
795
- return {
796
- ...item,
797
- [childCollection]: matchingChildren
798
- };
799
- });
800
- }
801
- function expandParent(items, _childCollection, parentCollection, db, idField, foreignKeySuffix) {
802
- const parents = db.getCollection(parentCollection);
803
- if (!Array.isArray(parents)) {
804
- return items;
805
- }
806
- const foreignKey = getForeignKey(parentCollection, foreignKeySuffix);
807
- return items.map((item) => {
808
- const foreignKeyValue = item[foreignKey];
809
- if (foreignKeyValue === void 0) {
810
- return item;
811
- }
812
- const parent = parents.find((p) => {
813
- if (typeof p !== "object" || p === null) return false;
814
- const parentRecord = p;
815
- const parentId = parentRecord[idField];
816
- if (typeof foreignKeyValue === "number" || typeof foreignKeyValue === "string") {
817
- return parentId === foreignKeyValue || String(parentId) === String(foreignKeyValue);
818
- }
819
- return false;
820
- });
821
- if (!parent || typeof parent !== "object") {
822
- return item;
823
- }
824
- const parentPropName = parentCollection.endsWith("s") ? parentCollection.slice(0, -1) : parentCollection;
825
- return {
826
- ...item,
827
- [parentPropName]: parent
828
- };
829
- });
830
- }
831
- function applyRelationships(data, resource, embed, expand, db, idField, foreignKeySuffix) {
832
- let result = data;
833
- for (const childCollection of embed) {
834
- result = embedChildren(result, resource, childCollection, db, idField, foreignKeySuffix);
835
- }
836
- for (const parentCollection of expand) {
837
- result = expandParent(result, resource, parentCollection, db, idField, foreignKeySuffix);
838
- }
839
- return result;
840
- }
841
- function parseRelationships(query) {
842
- const embed = [];
843
- const expand = [];
844
- const embedParam = query._embed;
845
- if (typeof embedParam === "string") {
846
- embed.push(...embedParam.split(","));
847
- } else if (Array.isArray(embedParam)) {
848
- for (const e of embedParam) {
849
- if (typeof e === "string") {
850
- embed.push(e);
851
- }
852
- }
853
- }
854
- const expandParam = query._expand;
855
- if (typeof expandParam === "string") {
856
- expand.push(...expandParam.split(","));
857
- } else if (Array.isArray(expandParam)) {
858
- for (const e of expandParam) {
859
- if (typeof e === "string") {
860
- expand.push(e);
861
- }
862
- }
863
- }
864
- return { embed, expand };
865
- }
866
-
867
- // src/router.ts
868
- function getParam(req, name) {
869
- const value = req.params[name];
870
- if (value === void 0) {
871
- throw new Error(`Route parameter '${name}' is missing`);
872
- }
873
- return value;
874
- }
875
- function createRouter(db, options = {}) {
876
- const router = (0, import_express.Router)();
877
- const readOnly = options.readOnly || false;
878
- const idField = options.idField || "id";
879
- const foreignKeySuffix = options.foreignKeySuffix || "Id";
880
- const validateContentType = (req, _res, next) => {
881
- const contentType = req.get("Content-Type");
882
- if (!contentType || !contentType.includes("application/json")) {
883
- logger.warn("Content-Type should be application/json");
884
- }
885
- next();
886
- };
887
- router.get("/db", (_req, res) => {
888
- res.json(db.getData());
889
- });
890
- router.get("/:resource", (req, res) => {
891
- const resource = getParam(req, "resource");
892
- const data = db.getCollection(resource);
893
- if (data === void 0) {
894
- res.status(404).json({ error: `Resource '${resource}' not found` });
895
- return;
896
- }
897
- if (Array.isArray(data)) {
898
- const queryOptions = parseQuery(req);
899
- const { data: filtered, total } = applyQuery(data, queryOptions);
900
- const { embed, expand } = parseRelationships(req.query);
901
- const withRelationships = applyRelationships(
902
- filtered,
903
- resource,
904
- embed,
905
- expand,
906
- db,
907
- idField,
908
- foreignKeySuffix
909
- );
910
- res.set("X-Total-Count", String(total));
911
- if (queryOptions.page !== void 0 && queryOptions.limit !== void 0) {
912
- const linkHeader = generateLinkHeader(req, queryOptions.page, queryOptions.limit, total);
913
- res.set("Link", linkHeader);
914
- }
915
- res.json(withRelationships);
916
- return;
917
- }
918
- res.json(data);
919
- });
920
- router.get("/:resource/:id", (req, res) => {
921
- const resource = getParam(req, "resource");
922
- const id = getParam(req, "id");
923
- if (!db.isCollection(resource)) {
924
- res.status(404).json({ error: `Collection '${resource}' not found` });
925
- return;
926
- }
927
- const item = db.getById(resource, id);
928
- if (!item) {
929
- res.status(404).json({ error: `Item with id '${id}' not found in '${resource}'` });
930
- return;
931
- }
932
- const { embed, expand } = parseRelationships(req.query);
933
- if (embed.length > 0 || expand.length > 0) {
934
- const withRelationships = applyRelationships(
935
- [item],
936
- resource,
937
- embed,
938
- expand,
939
- db,
940
- idField,
941
- foreignKeySuffix
942
- );
943
- res.json(withRelationships[0]);
944
- return;
945
- }
946
- res.json(item);
947
- });
948
- router.get("/:parent/:parentId/:children", (req, res) => {
949
- const parent = getParam(req, "parent");
950
- const parentId = getParam(req, "parentId");
951
- const children = getParam(req, "children");
952
- if (!db.isCollection(parent)) {
953
- res.status(404).json({ error: `Collection '${parent}' not found` });
954
- return;
955
- }
956
- const parentItem = db.getById(parent, parentId);
957
- if (!parentItem) {
958
- res.status(404).json({ error: `Parent item with id '${parentId}' not found in '${parent}'` });
959
- return;
960
- }
961
- const childrenData = db.getCollection(children);
962
- if (!Array.isArray(childrenData)) {
963
- res.status(404).json({ error: `Collection '${children}' not found` });
964
- return;
965
- }
966
- const foreignKey = getForeignKey(parent, foreignKeySuffix);
967
- const filtered = childrenData.filter((child) => {
968
- if (typeof child !== "object" || child === null) return false;
969
- const childFk = child[foreignKey];
970
- return childFk === parentId || (typeof childFk === "number" || typeof childFk === "string" ? String(childFk) === parentId : false);
971
- });
972
- const queryOptions = parseQuery(req);
973
- const { data: result, total } = applyQuery(filtered, queryOptions);
974
- res.set("X-Total-Count", String(total));
975
- res.json(result);
976
- });
977
- router.post(
978
- "/:parent/:parentId/:children",
979
- validateContentType,
980
- async (req, res) => {
981
- if (readOnly) {
982
- return res.status(403).json({ error: "Read-only mode enabled" });
983
- }
984
- const parent = getParam(req, "parent");
985
- const parentId = getParam(req, "parentId");
986
- const children = getParam(req, "children");
987
- const data = req.body;
988
- if (typeof data !== "object") {
989
- return res.status(400).json({ error: "Request body must be a JSON object" });
990
- }
991
- if (!db.isCollection(parent)) {
992
- return res.status(404).json({ error: `Collection '${parent}' not found` });
993
- }
994
- const parentItem = db.getById(parent, parentId);
995
- if (!parentItem) {
996
- return res.status(404).json({ error: `Parent item with id '${parentId}' not found in '${parent}'` });
997
- }
998
- const foreignKey = getForeignKey(parent, foreignKeySuffix);
999
- data[foreignKey] = parentId;
1000
- try {
1001
- const created = await db.create(children, data);
1002
- return res.status(201).json(created);
1003
- } catch (error) {
1004
- const message = error instanceof Error ? error.message : "Unknown error";
1005
- return res.status(400).json({ error: message });
1006
- }
1007
- }
1008
- );
1009
- router.post("/:resource", validateContentType, async (req, res) => {
1010
- if (readOnly) {
1011
- return res.status(403).json({ error: "Read-only mode enabled" });
1012
- }
1013
- const resource = getParam(req, "resource");
1014
- const data = req.body;
1015
- if (typeof data !== "object") {
1016
- return res.status(400).json({ error: "Request body must be a JSON object" });
1017
- }
1018
- try {
1019
- if (!db.isCollection(resource) && db.getCollection(resource) !== void 0) {
1020
- const updated = await db.updateSingular(resource, data);
1021
- return res.status(200).json(updated);
1022
- }
1023
- const created = await db.create(resource, data);
1024
- return res.status(201).json(created);
1025
- } catch (error) {
1026
- const message = error instanceof Error ? error.message : "Unknown error";
1027
- return res.status(400).json({ error: message });
1028
- }
1029
- });
1030
- router.put("/:resource/:id", validateContentType, async (req, res) => {
1031
- if (readOnly) {
1032
- return res.status(403).json({ error: "Read-only mode enabled" });
1033
- }
1034
- const resource = getParam(req, "resource");
1035
- const id = getParam(req, "id");
1036
- const data = req.body;
1037
- if (typeof data !== "object") {
1038
- return res.status(400).json({ error: "Request body must be a JSON object" });
1039
- }
1040
- if (!db.isCollection(resource)) {
1041
- return res.status(404).json({ error: `Collection '${resource}' not found` });
1042
- }
1043
- try {
1044
- const updated = await db.update(resource, id, data);
1045
- if (!updated) {
1046
- return res.status(404).json({ error: `Item with id '${id}' not found in '${resource}'` });
1047
- }
1048
- return res.json(updated);
1049
- } catch (error) {
1050
- const message = error instanceof Error ? error.message : "Unknown error";
1051
- return res.status(400).json({ error: message });
1052
- }
1053
- });
1054
- router.patch("/:resource/:id", validateContentType, async (req, res) => {
1055
- if (readOnly) {
1056
- return res.status(403).json({ error: "Read-only mode enabled" });
1057
- }
1058
- const resource = getParam(req, "resource");
1059
- const id = getParam(req, "id");
1060
- const data = req.body;
1061
- if (typeof data !== "object") {
1062
- return res.status(400).json({ error: "Request body must be a JSON object" });
1063
- }
1064
- if (!db.isCollection(resource)) {
1065
- return res.status(404).json({ error: `Collection '${resource}' not found` });
1066
- }
1067
- try {
1068
- const patched = await db.patch(resource, id, data);
1069
- if (!patched) {
1070
- return res.status(404).json({ error: `Item with id '${id}' not found in '${resource}'` });
1071
- }
1072
- return res.json(patched);
1073
- } catch (error) {
1074
- const message = error instanceof Error ? error.message : "Unknown error";
1075
- return res.status(400).json({ error: message });
1076
- }
1077
- });
1078
- router.put("/:resource", validateContentType, async (req, res) => {
1079
- if (readOnly) {
1080
- return res.status(403).json({ error: "Read-only mode enabled" });
1081
- }
1082
- const resource = getParam(req, "resource");
1083
- const data = req.body;
1084
- if (typeof data !== "object") {
1085
- return res.status(400).json({ error: "Request body must be a JSON object" });
1086
- }
1087
- if (db.isCollection(resource)) {
1088
- return res.status(400).json({
1089
- error: `Cannot PUT to collection '${resource}'. Use POST or PUT /${resource}/:id`
1090
- });
1091
- }
1092
- try {
1093
- const updated = await db.updateSingular(resource, data);
1094
- return res.json(updated);
1095
- } catch (error) {
1096
- const message = error instanceof Error ? error.message : "Unknown error";
1097
- return res.status(400).json({ error: message });
1098
- }
1099
- });
1100
- router.patch("/:resource", validateContentType, async (req, res) => {
1101
- if (readOnly) {
1102
- return res.status(403).json({ error: "Read-only mode enabled" });
1103
- }
1104
- const resource = getParam(req, "resource");
1105
- const data = req.body;
1106
- if (typeof data !== "object") {
1107
- return res.status(400).json({ error: "Request body must be a JSON object" });
1108
- }
1109
- if (db.isCollection(resource)) {
1110
- return res.status(400).json({ error: `Cannot PATCH collection '${resource}'. Use PATCH /${resource}/:id` });
1111
- }
1112
- const current = db.getCollection(resource);
1113
- if (!current || typeof current !== "object") {
1114
- return res.status(404).json({ error: `Resource '${resource}' not found` });
1115
- }
1116
- try {
1117
- const merged = { ...current, ...data };
1118
- const updated = await db.updateSingular(resource, merged);
1119
- return res.json(updated);
1120
- } catch (error) {
1121
- const message = error instanceof Error ? error.message : "Unknown error";
1122
- return res.status(400).json({ error: message });
1123
- }
1124
- });
1125
- router.delete("/:resource/:id", async (req, res) => {
1126
- if (readOnly) {
1127
- return res.status(403).json({ error: "Read-only mode enabled" });
1128
- }
1129
- const resource = getParam(req, "resource");
1130
- const id = getParam(req, "id");
1131
- if (!db.isCollection(resource)) {
1132
- return res.status(404).json({ error: `Collection '${resource}' not found` });
1133
- }
1134
- const deleted = await db.delete(resource, id);
1135
- if (!deleted) {
1136
- return res.status(404).json({ error: `Item with id '${id}' not found in '${resource}'` });
1137
- }
1138
- return res.status(204).send();
1139
- });
1140
- return router;
1141
- }
1142
-
1143
- // src/static.ts
1144
- var import_express2 = __toESM(require("express"));
1145
- var import_fs2 = require("fs");
1146
- var import_path2 = require("path");
1147
- function createStaticMiddleware(options = {}) {
1148
- const directory = options.directory || "./public";
1149
- const enabled = options.enabled !== false;
1150
- const staticPath = (0, import_path2.resolve)(process.cwd(), directory);
1151
- if (!enabled || !(0, import_fs2.existsSync)(staticPath)) {
1152
- return (_req, _res, next) => {
1153
- next();
1154
- };
1155
- }
1156
- return import_express2.default.static(staticPath, {
1157
- index: "index.html",
1158
- dotfiles: "ignore",
1159
- redirect: true
1160
- });
1161
- }
1162
- function createHomepageMiddleware(options = {}) {
1163
- const directory = options.directory || "./public";
1164
- const enabled = options.enabled !== false;
1165
- const staticPath = (0, import_path2.resolve)(process.cwd(), directory);
1166
- const indexPath = (0, import_path2.resolve)(staticPath, "index.html");
1167
- return (req, res, next) => {
1168
- if (req.path !== "/") {
1169
- next();
1170
- return;
1171
- }
1172
- if (enabled && (0, import_fs2.existsSync)(indexPath)) {
1173
- next();
1174
- return;
1175
- }
1176
- const host = req.get("host") || "localhost:3000";
1177
- const protocol = req.protocol;
1178
- const baseUrl = `${protocol}://${host}`;
1179
- res.setHeader("Content-Type", "text/html; charset=utf-8");
1180
- res.send(`<!DOCTYPE html>
1181
- <html lang="en">
1182
- <head>
1183
- <meta charset="UTF-8">
1184
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1185
- <title>API Faker</title>
1186
- <style>
1187
- * { margin: 0; padding: 0; box-sizing: border-box; }
1188
- body {
1189
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1190
- line-height: 1.6;
1191
- color: #333;
1192
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1193
- min-height: 100vh;
1194
- padding: 2rem;
1195
- }
1196
- .container {
1197
- max-width: 800px;
1198
- margin: 0 auto;
1199
- background: white;
1200
- border-radius: 12px;
1201
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
1202
- padding: 3rem;
1203
- }
1204
- h1 {
1205
- color: #667eea;
1206
- font-size: 2.5rem;
1207
- margin-bottom: 1rem;
1208
- display: flex;
1209
- align-items: center;
1210
- gap: 0.5rem;
1211
- }
1212
- h2 {
1213
- color: #555;
1214
- font-size: 1.5rem;
1215
- margin-top: 2rem;
1216
- margin-bottom: 1rem;
1217
- border-bottom: 2px solid #667eea;
1218
- padding-bottom: 0.5rem;
1219
- }
1220
- .intro {
1221
- color: #666;
1222
- font-size: 1.1rem;
1223
- margin-bottom: 2rem;
1224
- }
1225
- .endpoint {
1226
- background: #f8f9fa;
1227
- border-left: 4px solid #667eea;
1228
- padding: 1rem;
1229
- margin-bottom: 1rem;
1230
- border-radius: 4px;
1231
- }
1232
- .endpoint a {
1233
- color: #667eea;
1234
- text-decoration: none;
1235
- font-family: 'Monaco', 'Courier New', monospace;
1236
- font-weight: bold;
1237
- }
1238
- .endpoint a:hover {
1239
- text-decoration: underline;
1240
- }
1241
- .endpoint p {
1242
- color: #666;
1243
- margin-top: 0.5rem;
1244
- font-size: 0.95rem;
1245
- }
1246
- .info-box {
1247
- background: #e3f2fd;
1248
- border-left: 4px solid #2196f3;
1249
- padding: 1rem;
1250
- margin-top: 2rem;
1251
- border-radius: 4px;
1252
- }
1253
- .info-box p {
1254
- color: #1565c0;
1255
- margin: 0;
1256
- }
1257
- code {
1258
- background: #f5f5f5;
1259
- padding: 0.2rem 0.4rem;
1260
- border-radius: 3px;
1261
- font-family: 'Monaco', 'Courier New', monospace;
1262
- font-size: 0.9em;
1263
- }
1264
- </style>
1265
- </head>
1266
- <body>
1267
- <div class="container">
1268
- <h1>\u{1F680} API Faker</h1>
1269
- <p class="intro">
1270
- Your JSON REST API is up and running! Use the endpoints below to interact with your data.
1271
- </p>
1272
-
1273
- <h2>\u{1F4E1} Endpoints</h2>
1274
- <div class="endpoint">
1275
- <a href="${baseUrl}/db" target="_blank">${baseUrl}/db</a>
1276
- <p>View the full database</p>
1277
- </div>
1278
-
1279
- <h2>\u{1F4A1} Tips</h2>
1280
- <div class="info-box">
1281
- <p>
1282
- Use query parameters to filter, sort, and paginate your data.
1283
- Examples: <code>?_sort=name&_order=asc</code>, <code>?_page=1&_limit=10</code>
1284
- </p>
1285
- </div>
1286
- </div>
1287
- </body>
1288
- </html>`);
1289
- };
1290
- }
1291
-
1292
- // src/loader.ts
1293
- var import_node_url = require("url");
1294
- async function loadModule(filePath) {
1295
- try {
1296
- const fileUrl = (0, import_node_url.pathToFileURL)(filePath).href;
1297
- const module2 = await import(fileUrl);
1298
- return module2;
1299
- } catch (error) {
1300
- const message = error instanceof Error ? error.message : String(error);
1301
- throw new Error(
1302
- `Failed to load module '${filePath}': ${message}
1303
-
1304
- Make sure:
1305
- - The file exists and is a valid JavaScript file
1306
- - The file doesn't have syntax errors
1307
- - All dependencies are installed`
1308
- );
1309
- }
1310
- }
1311
- async function loadMiddlewares(filePath) {
1312
- const module2 = await loadModule(filePath);
1313
- let middlewareExport;
1314
- if (typeof module2 === "object" && module2 !== null && "default" in module2) {
1315
- const defaultExport = module2.default;
1316
- if (typeof defaultExport === "object" && defaultExport !== null && "default" in defaultExport) {
1317
- middlewareExport = defaultExport.default;
1318
- } else {
1319
- middlewareExport = defaultExport;
1320
- }
1321
- } else {
1322
- middlewareExport = module2;
1323
- }
1324
- const middlewares = Array.isArray(middlewareExport) ? middlewareExport : [middlewareExport];
1325
- for (let i = 0; i < middlewares.length; i++) {
1326
- const mw = middlewares[i];
1327
- if (typeof mw !== "function") {
1328
- const indexStr = String(i);
1329
- throw new Error(
1330
- `Middleware file '${filePath}' at index ${indexStr} is not a function, got ${typeof mw}`
1331
- );
1332
- }
1333
- }
1334
- return middlewares;
1335
- }
1336
-
1337
- // src/rewriter.ts
1338
- var import_promises = require("fs/promises");
1339
- async function loadRewriteRules(filePath) {
1340
- try {
1341
- const content = await (0, import_promises.readFile)(filePath, "utf-8");
1342
- const rules = JSON.parse(content);
1343
- if (typeof rules !== "object" || rules === null || Array.isArray(rules)) {
1344
- throw new Error("Routes file must contain a JSON object with route mappings");
1345
- }
1346
- for (const [key, value] of Object.entries(rules)) {
1347
- if (typeof value !== "string") {
1348
- throw new Error(`Route mapping for '${key}' must be a string, got ${typeof value}`);
1349
- }
1350
- }
1351
- return rules;
1352
- } catch (error) {
1353
- if (error instanceof Error && error.message.includes("Routes file must contain")) {
1354
- throw error;
1355
- }
1356
- const message = error instanceof Error ? error.message : String(error);
1357
- throw new Error(
1358
- `Failed to load routes from '${filePath}': ${message}
1359
-
1360
- Expected format:
1361
- {
1362
- "/api/*": "/$1",
1363
- "/users/:id": "/api/users/:id"
1364
- }
1365
-
1366
- See examples/routes.json for more examples.`
1367
- );
1368
- }
1369
- }
1370
- function patternToRegex(pattern) {
1371
- const params = [];
1372
- let wildcardCount = 0;
1373
- let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1374
- regexPattern = regexPattern.replace(/:([^/]+)/g, (_match, param) => {
1375
- params.push(param);
1376
- return "([^/]+)";
1377
- });
1378
- regexPattern = regexPattern.replace(/\*/g, () => {
1379
- wildcardCount++;
1380
- params.push(`$${String(wildcardCount)}`);
1381
- return "(.*?)";
1382
- });
1383
- return {
1384
- regex: new RegExp(`^${regexPattern}$`),
1385
- params
1386
- };
1387
- }
1388
- function rewriteUrl(url, fromPattern, toPattern) {
1389
- const splitResult = url.split("?", 2);
1390
- const urlPath = splitResult[0] ?? "";
1391
- const urlQuery = splitResult[1];
1392
- const patternSplit = fromPattern.split("?", 2);
1393
- const patternPath = patternSplit[0] ?? "";
1394
- const patternQuery = patternSplit[1];
1395
- const { regex, params } = patternToRegex(patternPath);
1396
- const match = urlPath.match(regex);
1397
- if (!match) {
1398
- return null;
1399
- }
1400
- const captures = match.slice(1);
1401
- if (patternQuery) {
1402
- const patternParams = new URLSearchParams(patternQuery);
1403
- const urlParams = new URLSearchParams(urlQuery || "");
1404
- for (const [key, patternValue] of patternParams.entries()) {
1405
- if (!urlParams.has(key)) {
1406
- return null;
1407
- }
1408
- if (patternValue === "*") {
1409
- captures.push(urlParams.get(key) || "");
1410
- } else if (urlParams.get(key) !== patternValue) {
1411
- return null;
1412
- }
1413
- }
1414
- }
1415
- let result = toPattern;
1416
- for (let i = 0; i < captures.length; i++) {
1417
- const value = captures[i];
1418
- if (value !== void 0) {
1419
- result = result.replace(`$${String(i + 1)}`, value);
1420
- }
1421
- }
1422
- for (let i = 0; i < params.length; i++) {
1423
- const param = params[i];
1424
- const value = captures[i];
1425
- if (param && !param.startsWith("$") && value !== void 0) {
1426
- result = result.replace(`:${param}`, value);
1427
- }
1428
- }
1429
- if (!patternQuery && urlQuery) {
1430
- result += "?" + urlQuery;
1431
- }
1432
- logger.info(`Rewriting URL: '${url}' \u2192 '${result}' using pattern '${fromPattern}'`);
1433
- return result;
1434
- }
1435
- function getPatternSpecificity(pattern) {
1436
- let score = 0;
1437
- const [path, query] = pattern.split("?", 2);
1438
- const segments = path?.split("/").filter((s) => s.length > 0) ?? [];
1439
- for (const segment of segments) {
1440
- if (segment === "*") {
1441
- score += 1;
1442
- } else if (segment.startsWith(":")) {
1443
- score += 10;
1444
- } else {
1445
- score += 100;
1446
- }
1447
- }
1448
- if (query) {
1449
- score += 1e3;
1450
- }
1451
- score += segments.length;
1452
- return score;
1453
- }
1454
- function createRewriterMiddleware(rules) {
1455
- const sortedRules = Object.entries(rules).sort((a, b) => {
1456
- const scoreA = getPatternSpecificity(a[0]);
1457
- const scoreB = getPatternSpecificity(b[0]);
1458
- return scoreB - scoreA;
1459
- });
1460
- return (req, _res, next) => {
1461
- for (const [from, to] of sortedRules) {
1462
- const rewritten = rewriteUrl(req.url, from, to);
1463
- if (rewritten !== null) {
1464
- req.url = rewritten;
1465
- break;
1466
- }
1467
- }
1468
- next();
1469
- };
1470
- }
1471
-
1472
- // src/server.ts
1473
- async function createServer(db, options = {}) {
1474
- const app = (0, import_express3.default)();
1475
- if (!options.noCors) {
1476
- app.use((0, import_cors.default)());
1477
- }
1478
- if (!options.noGzip) {
1479
- app.use((0, import_compression.default)());
1480
- }
1481
- app.use(import_express3.default.json());
1482
- if (options.delay && options.delay > 0) {
1483
- const delay = options.delay;
1484
- app.use((_req, _res, next) => {
1485
- setTimeout(() => {
1486
- next();
1487
- }, delay);
1488
- });
1489
- }
1490
- if (!options.quiet) {
1491
- app.use((req, _res, next) => {
1492
- logger.request(req.method, req.url);
1493
- next();
1494
- });
1495
- }
1496
- if (options.middlewares) {
1497
- try {
1498
- const middlewares = await loadMiddlewares(options.middlewares);
1499
- for (const middleware of middlewares) {
1500
- app.use(middleware);
1501
- }
1502
- if (!options.quiet) {
1503
- logger.success(`Loaded custom middlewares from ${options.middlewares}`);
1504
- }
1505
- } catch (error) {
1506
- logger.error(
1507
- `Failed to load middlewares: ${error instanceof Error ? error.message : String(error)}`
1508
- );
1509
- throw error;
1510
- }
1511
- }
1512
- app.use(createHomepageMiddleware(options));
1513
- app.use(createStaticMiddleware(options));
1514
- if (options.routes) {
1
+ import yargs from 'yargs';
2
+ import { hideBin } from 'yargs/helpers';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { resolve, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { watch } from 'chokidar';
7
+ import { Database } from './database.js';
8
+ import { createServer, startServer } from './server.js';
9
+ import { loadConfig, mergeConfig } from './config.js';
10
+ import { logger, setLogLevel } from './logger.js';
11
+ function getVersion() {
1515
12
  try {
1516
- const rules = await loadRewriteRules(options.routes);
1517
- const rewriter = createRewriterMiddleware(rules);
1518
- app.use(rewriter);
1519
- if (!options.quiet) {
1520
- logger.success(`Loaded route rewrite rules from ${options.routes}`);
1521
- }
1522
- } catch (error) {
1523
- logger.error(
1524
- `Failed to load routes: ${error instanceof Error ? error.message : String(error)}`
1525
- );
1526
- throw error;
1527
- }
1528
- }
1529
- app.get("/db", (_req, res) => {
1530
- res.json(db.getData());
1531
- });
1532
- const router = createRouter(db, options);
1533
- app.use(router);
1534
- app.use((_req, res) => {
1535
- res.status(404).json({ error: "Not Found" });
1536
- });
1537
- return app;
1538
- }
1539
- function startServer(app, options = {}) {
1540
- const port = options.port || 3e3;
1541
- const host = options.host || "localhost";
1542
- return app.listen(port, host, () => {
1543
- if (!options.quiet) {
1544
- logger.banner([
1545
- "\u{1F680} API Faker is running!",
1546
- "",
1547
- " Resources:",
1548
- ` http://${host}:${String(port)}/`,
1549
- "",
1550
- " Home:",
1551
- ` http://${host}:${String(port)}`
1552
- ]);
1553
- }
1554
- });
1555
- }
1556
-
1557
- // src/config.ts
1558
- var import_node_fs = require("fs");
1559
- var import_node_path = require("path");
1560
- function findClosestMatch(input, candidates) {
1561
- const levenshtein = (a, b) => {
1562
- const matrix = [];
1563
- for (let i = 0; i <= b.length; i++) {
1564
- matrix[i] = [i];
1565
- }
1566
- for (let j = 0; j <= a.length; j++) {
1567
- matrix[0][j] = j;
1568
- }
1569
- for (let i = 1; i <= b.length; i++) {
1570
- for (let j = 1; j <= a.length; j++) {
1571
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
1572
- matrix[i][j] = matrix[i - 1][j - 1];
1573
- } else {
1574
- matrix[i][j] = Math.min(
1575
- matrix[i - 1][j - 1] + 1,
1576
- matrix[i][j - 1] + 1,
1577
- matrix[i - 1][j] + 1
1578
- );
1579
- }
1580
- }
1581
- }
1582
- return matrix[b.length][a.length];
1583
- };
1584
- let closestMatch = null;
1585
- let minDistance = Infinity;
1586
- for (const candidate of candidates) {
1587
- const distance = levenshtein(input.toLowerCase(), candidate.toLowerCase());
1588
- if (distance < minDistance && distance <= 3) {
1589
- minDistance = distance;
1590
- closestMatch = candidate;
1591
- }
1592
- }
1593
- return closestMatch;
1594
- }
1595
- function loadConfig(configPath) {
1596
- const resolvedPath = (0, import_node_path.resolve)(configPath);
1597
- if (!(0, import_node_fs.existsSync)(resolvedPath)) {
1598
- return null;
1599
- }
1600
- try {
1601
- const content = (0, import_node_fs.readFileSync)(resolvedPath, "utf-8");
1602
- const config = JSON.parse(content);
1603
- if (typeof config !== "object" || config === null || Array.isArray(config)) {
1604
- throw new Error("Config file must contain a JSON object");
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
15
+ return packageJson.version;
1605
16
  }
1606
- validateConfig(config);
1607
- return config;
1608
- } catch (error) {
1609
- if (error instanceof Error && error.message.includes("Config file must contain")) {
1610
- throw error;
17
+ catch {
18
+ return '0.0.0';
1611
19
  }
1612
- throw new Error(
1613
- `Failed to load config from '${configPath}': ${error instanceof Error ? error.message : String(error)}`
1614
- );
1615
- }
1616
- }
1617
- function validateConfig(config) {
1618
- const validOptions = /* @__PURE__ */ new Set([
1619
- "port",
1620
- "host",
1621
- "watch",
1622
- "routes",
1623
- "middlewares",
1624
- "static",
1625
- "readOnly",
1626
- "noCors",
1627
- "noGzip",
1628
- "snapshots",
1629
- "delay",
1630
- "id",
1631
- "foreignKeySuffix",
1632
- "quiet"
1633
- ]);
1634
- for (const key of Object.keys(config)) {
1635
- if (!validOptions.has(key)) {
1636
- const suggestion = findClosestMatch(key, Array.from(validOptions));
1637
- const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : "";
1638
- throw new Error(
1639
- `Unknown config option: '${key}'.${didYouMean} Valid options are: ${Array.from(validOptions).join(", ")}`
1640
- );
1641
- }
1642
- }
1643
- if ("port" in config && (typeof config.port !== "number" || config.port < 0 || config.port > 65535)) {
1644
- throw new Error("Config option 'port' must be a number between 0 and 65535");
1645
- }
1646
- if ("host" in config && typeof config.host !== "string") {
1647
- throw new Error("Config option 'host' must be a string");
1648
- }
1649
- if ("watch" in config && typeof config.watch !== "boolean") {
1650
- throw new Error("Config option 'watch' must be a boolean");
1651
- }
1652
- if ("routes" in config && typeof config.routes !== "string") {
1653
- throw new Error("Config option 'routes' must be a string");
1654
- }
1655
- if ("middlewares" in config && typeof config.middlewares !== "string") {
1656
- throw new Error("Config option 'middlewares' must be a string");
1657
- }
1658
- if ("static" in config && typeof config.static !== "string") {
1659
- throw new Error("Config option 'static' must be a string");
1660
- }
1661
- if ("readOnly" in config && typeof config.readOnly !== "boolean") {
1662
- throw new Error("Config option 'readOnly' must be a boolean");
1663
- }
1664
- if ("noCors" in config && typeof config.noCors !== "boolean") {
1665
- throw new Error("Config option 'noCors' must be a boolean");
1666
- }
1667
- if ("noGzip" in config && typeof config.noGzip !== "boolean") {
1668
- throw new Error("Config option 'noGzip' must be a boolean");
1669
- }
1670
- if ("snapshots" in config && typeof config.snapshots !== "string") {
1671
- throw new Error("Config option 'snapshots' must be a string");
1672
- }
1673
- if ("delay" in config && (typeof config.delay !== "number" || config.delay < 0)) {
1674
- throw new Error("Config option 'delay' must be a non-negative number");
1675
- }
1676
- if ("id" in config && typeof config.id !== "string") {
1677
- throw new Error("Config option 'id' must be a string");
1678
- }
1679
- if ("foreignKeySuffix" in config && typeof config.foreignKeySuffix !== "string") {
1680
- throw new Error("Config option 'foreignKeySuffix' must be a string");
1681
- }
1682
- if ("quiet" in config && typeof config.quiet !== "boolean") {
1683
- throw new Error("Config option 'quiet' must be a boolean");
1684
- }
1685
- }
1686
- function mergeConfig(cliConfig, fileConfig) {
1687
- if (!fileConfig) {
1688
- return cliConfig;
1689
- }
1690
- return {
1691
- ...fileConfig,
1692
- ...cliConfig
1693
- };
1694
- }
1695
-
1696
- // src/cli.ts
1697
- function getVersion() {
1698
- try {
1699
- const packageJson = JSON.parse(
1700
- (0, import_fs3.readFileSync)((0, import_path3.resolve)(__dirname, "../package.json"), "utf-8")
1701
- );
1702
- return packageJson.version;
1703
- } catch {
1704
- return "0.0.0";
1705
- }
1706
20
  }
1707
21
  function parseCli() {
1708
- const argv = (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("rest_api_faker").usage("Usage: $0 [options] <source>").example("$0 db.json", "Start API Faker with db.json").example("$0 file.js", "Start API Faker with a JS file").example("$0 http://example.com/db.json", "Start API Faker with a remote schema").option("config", {
1709
- alias: "c",
1710
- type: "string",
1711
- description: "Path to config file",
1712
- default: "rest_api_faker.json"
1713
- }).option("port", {
1714
- alias: "p",
1715
- type: "number",
1716
- description: "Set port",
1717
- default: 3e3
1718
- }).option("host", {
1719
- alias: "H",
1720
- type: "string",
1721
- description: "Set host",
1722
- default: "localhost"
1723
- }).option("watch", {
1724
- alias: "w",
1725
- type: "boolean",
1726
- description: "Watch file(s)",
1727
- default: false
1728
- }).option("routes", {
1729
- alias: "r",
1730
- type: "string",
1731
- description: "Path to routes file"
1732
- }).option("middlewares", {
1733
- alias: "m",
1734
- type: "string",
1735
- description: "Path to middleware file"
1736
- }).option("static", {
1737
- alias: "s",
1738
- type: "string",
1739
- description: "Set static files directory",
1740
- default: "./public"
1741
- }).option("no-static", {
1742
- type: "boolean",
1743
- description: "Disable static file serving",
1744
- default: false
1745
- }).option("read-only", {
1746
- alias: "ro",
1747
- type: "boolean",
1748
- description: "Allow only GET requests",
1749
- default: false
1750
- }).option("no-cors", {
1751
- alias: "nc",
1752
- type: "boolean",
1753
- description: "Disable Cross-Origin Resource Sharing",
1754
- default: false
1755
- }).option("no-gzip", {
1756
- alias: "ng",
1757
- type: "boolean",
1758
- description: "Disable GZIP Content-Encoding",
1759
- default: false
1760
- }).option("snapshots", {
1761
- alias: "S",
1762
- type: "string",
1763
- description: "Set snapshots directory",
1764
- default: "."
1765
- }).option("delay", {
1766
- alias: "d",
1767
- type: "number",
1768
- description: "Add delay to responses (ms)"
1769
- }).option("id", {
1770
- alias: "i",
1771
- type: "string",
1772
- description: "Set database id property",
1773
- default: "id"
1774
- }).option("foreignKeySuffix", {
1775
- alias: "fks",
1776
- type: "string",
1777
- description: "Set foreign key suffix",
1778
- default: "Id"
1779
- }).option("quiet", {
1780
- alias: "q",
1781
- type: "boolean",
1782
- description: "Suppress log messages from output",
1783
- default: false
1784
- }).option("log-level", {
1785
- alias: "l",
1786
- type: "string",
1787
- description: "Set log level (trace | debug | info)",
1788
- default: "info",
1789
- choices: ["trace", "debug", "info"]
1790
- }).help("help", "Show help").alias("h", "help").version(getVersion()).alias("v", "version").epilogue("For more information, visit https://github.com/hamidmayeli/rest_api_faker").parseSync();
1791
- let fileConfig = null;
1792
- try {
1793
- fileConfig = loadConfig(argv.config);
1794
- } catch (error) {
1795
- logger.error(
1796
- `Failed to load config file: ${error instanceof Error ? error.message : String(error)}`
1797
- );
1798
- process.exit(1);
1799
- }
1800
- const cliConfig = {};
1801
- if (argv.port !== 3e3) cliConfig.port = argv.port;
1802
- if (argv.host !== "localhost") cliConfig.host = argv.host;
1803
- if (argv.watch) cliConfig.watch = argv.watch;
1804
- if (argv.routes) cliConfig.routes = argv.routes;
1805
- if (argv.middlewares) cliConfig.middlewares = argv.middlewares;
1806
- if (argv.static !== "./public") cliConfig.static = argv.static;
1807
- if (argv["read-only"]) cliConfig.readOnly = argv["read-only"];
1808
- if (argv["no-cors"]) cliConfig.noCors = argv["no-cors"];
1809
- if (argv["no-gzip"]) cliConfig.noGzip = argv["no-gzip"];
1810
- if (argv.snapshots !== ".") cliConfig.snapshots = argv.snapshots;
1811
- if (argv.delay !== void 0) cliConfig.delay = argv.delay;
1812
- if (argv.id !== "id") cliConfig.id = argv.id;
1813
- if (argv.foreignKeySuffix !== "Id") cliConfig.foreignKeySuffix = argv.foreignKeySuffix;
1814
- if (argv.quiet) cliConfig.quiet = argv.quiet;
1815
- if (argv["log-level"] !== "info") cliConfig.logLevel = argv["log-level"];
1816
- const merged = mergeConfig(cliConfig, fileConfig);
1817
- return {
1818
- source: argv._[0],
1819
- port: merged.port ?? 3e3,
1820
- host: merged.host ?? "localhost",
1821
- watch: merged.watch ?? false,
1822
- routes: merged.routes,
1823
- middlewares: merged.middlewares,
1824
- static: merged.static ?? "./public",
1825
- noStatic: argv["no-static"],
1826
- readOnly: merged.readOnly ?? false,
1827
- noCors: merged.noCors ?? false,
1828
- noGzip: merged.noGzip ?? false,
1829
- snapshots: merged.snapshots ?? ".",
1830
- delay: merged.delay,
1831
- id: merged.id ?? "id",
1832
- foreignKeySuffix: merged.foreignKeySuffix ?? "Id",
1833
- quiet: merged.quiet ?? false,
1834
- logLevel: merged.logLevel ?? "info",
1835
- config: argv.config
1836
- };
22
+ const argv = yargs(hideBin(process.argv))
23
+ .scriptName('rest_api_faker')
24
+ .usage('Usage: $0 [options] <source>')
25
+ .example('$0 db.json', 'Start API Faker with db.json')
26
+ .example('$0 file.js', 'Start API Faker with a JS file')
27
+ .example('$0 http://example.com/db.json', 'Start API Faker with a remote schema')
28
+ .option('config', {
29
+ alias: 'c',
30
+ type: 'string',
31
+ description: 'Path to config file',
32
+ default: 'rest_api_faker.json',
33
+ })
34
+ .option('port', {
35
+ alias: 'p',
36
+ type: 'number',
37
+ description: 'Set port',
38
+ default: 3000,
39
+ })
40
+ .option('host', {
41
+ alias: 'H',
42
+ type: 'string',
43
+ description: 'Set host',
44
+ default: 'localhost',
45
+ })
46
+ .option('watch', {
47
+ alias: 'w',
48
+ type: 'boolean',
49
+ description: 'Watch file(s)',
50
+ default: false,
51
+ })
52
+ .option('routes', {
53
+ alias: 'r',
54
+ type: 'string',
55
+ description: 'Path to routes file',
56
+ })
57
+ .option('middlewares', {
58
+ alias: 'm',
59
+ type: 'string',
60
+ description: 'Path to middleware file',
61
+ })
62
+ .option('static', {
63
+ alias: 's',
64
+ type: 'string',
65
+ description: 'Set static files directory',
66
+ default: './public',
67
+ })
68
+ .option('no-static', {
69
+ type: 'boolean',
70
+ description: 'Disable static file serving',
71
+ default: false,
72
+ })
73
+ .option('read-only', {
74
+ alias: 'ro',
75
+ type: 'boolean',
76
+ description: 'Allow only GET requests',
77
+ default: false,
78
+ })
79
+ .option('no-cors', {
80
+ alias: 'nc',
81
+ type: 'boolean',
82
+ description: 'Disable Cross-Origin Resource Sharing',
83
+ default: false,
84
+ })
85
+ .option('no-gzip', {
86
+ alias: 'ng',
87
+ type: 'boolean',
88
+ description: 'Disable GZIP Content-Encoding',
89
+ default: false,
90
+ })
91
+ .option('snapshots', {
92
+ alias: 'S',
93
+ type: 'string',
94
+ description: 'Set snapshots directory',
95
+ default: '.',
96
+ })
97
+ .option('delay', {
98
+ alias: 'd',
99
+ type: 'number',
100
+ description: 'Add delay to responses (ms)',
101
+ })
102
+ .option('id', {
103
+ alias: 'i',
104
+ type: 'string',
105
+ description: 'Set database id property',
106
+ default: 'id',
107
+ })
108
+ .option('foreignKeySuffix', {
109
+ alias: 'fks',
110
+ type: 'string',
111
+ description: 'Set foreign key suffix',
112
+ default: 'Id',
113
+ })
114
+ .option('quiet', {
115
+ alias: 'q',
116
+ type: 'boolean',
117
+ description: 'Suppress log messages from output',
118
+ default: false,
119
+ })
120
+ .option('log-level', {
121
+ alias: 'l',
122
+ type: 'string',
123
+ description: 'Set log level (trace | debug | info)',
124
+ default: 'info',
125
+ choices: ['trace', 'debug', 'info'],
126
+ })
127
+ .help('help', 'Show help')
128
+ .alias('h', 'help')
129
+ .version(getVersion())
130
+ .alias('v', 'version')
131
+ .epilogue('For more information, visit https://github.com/hamidmayeli/rest_api_faker')
132
+ .parseSync();
133
+ let fileConfig = null;
134
+ try {
135
+ fileConfig = loadConfig(argv.config);
136
+ }
137
+ catch (error) {
138
+ logger.error(`Failed to load config file: ${error instanceof Error ? error.message : String(error)}`);
139
+ process.exit(1);
140
+ }
141
+ const cliConfig = {};
142
+ if (argv.port !== 3000)
143
+ cliConfig.port = argv.port;
144
+ if (argv.host !== 'localhost')
145
+ cliConfig.host = argv.host;
146
+ if (argv.watch)
147
+ cliConfig.watch = argv.watch;
148
+ if (argv.routes)
149
+ cliConfig.routes = argv.routes;
150
+ if (argv.middlewares)
151
+ cliConfig.middlewares = argv.middlewares;
152
+ if (argv.static !== './public')
153
+ cliConfig.static = argv.static;
154
+ if (argv['read-only'])
155
+ cliConfig.readOnly = argv['read-only'];
156
+ if (argv['no-cors'])
157
+ cliConfig.noCors = argv['no-cors'];
158
+ if (argv['no-gzip'])
159
+ cliConfig.noGzip = argv['no-gzip'];
160
+ if (argv.snapshots !== '.')
161
+ cliConfig.snapshots = argv.snapshots;
162
+ if (argv.delay !== undefined)
163
+ cliConfig.delay = argv.delay;
164
+ if (argv.id !== 'id')
165
+ cliConfig.id = argv.id;
166
+ if (argv.foreignKeySuffix !== 'Id')
167
+ cliConfig.foreignKeySuffix = argv.foreignKeySuffix;
168
+ if (argv.quiet)
169
+ cliConfig.quiet = argv.quiet;
170
+ if (argv['log-level'] !== 'info')
171
+ cliConfig.logLevel = argv['log-level'];
172
+ const merged = mergeConfig(cliConfig, fileConfig);
173
+ return {
174
+ source: argv._[0],
175
+ port: merged.port ?? 3000,
176
+ host: merged.host ?? 'localhost',
177
+ watch: merged.watch ?? false,
178
+ routes: merged.routes,
179
+ middlewares: merged.middlewares,
180
+ static: merged.static ?? './public',
181
+ noStatic: argv['no-static'],
182
+ readOnly: merged.readOnly ?? false,
183
+ noCors: merged.noCors ?? false,
184
+ noGzip: merged.noGzip ?? false,
185
+ snapshots: merged.snapshots ?? '.',
186
+ delay: merged.delay,
187
+ id: merged.id ?? 'id',
188
+ foreignKeySuffix: merged.foreignKeySuffix ?? 'Id',
189
+ quiet: merged.quiet ?? false,
190
+ logLevel: merged.logLevel ?? 'info',
191
+ config: argv.config,
192
+ };
1837
193
  }
1838
194
  async function main() {
1839
- const config = parseCli();
1840
- setLogLevel(config.logLevel);
1841
- if (!config.quiet) {
1842
- logger.log(`
1843
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1844
- \u2551 \u2551
1845
- \u2551 API Faker v${getVersion().padEnd(19)}\u2551
1846
- \u2551 \u2551
1847
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1848
- `);
1849
- }
1850
- if (!config.source) {
1851
- logger.error("No source file specified");
1852
- logger.info('Run "rest_api_faker --help" for usage information');
1853
- process.exit(1);
1854
- }
1855
- if (!config.quiet) {
1856
- logger.info(`Source: ${config.source}`);
1857
- logger.info(`Port: ${String(config.port)}`);
1858
- logger.info(`Host: ${config.host}`);
1859
- logger.log("");
1860
- logger.info("Loading database...");
1861
- }
1862
- try {
1863
- const db = new Database(config.source, {
1864
- idField: config.id,
1865
- foreignKeySuffix: config.foreignKeySuffix,
1866
- autoSave: false
1867
- // Keep changes in memory only
1868
- });
1869
- await db.init();
195
+ const config = parseCli();
196
+ setLogLevel(config.logLevel);
1870
197
  if (!config.quiet) {
1871
- const data = db.getData();
1872
- const resources = Object.keys(data);
1873
- logger.success(`Loaded ${String(resources.length)} resource(s): ${resources.join(", ")}`);
1874
- logger.log("");
1875
- }
1876
- const serverOptions = {
1877
- port: config.port,
1878
- host: config.host,
1879
- readOnly: config.readOnly,
1880
- noCors: config.noCors,
1881
- noGzip: config.noGzip,
1882
- quiet: config.quiet,
1883
- idField: config.id,
1884
- foreignKeySuffix: config.foreignKeySuffix,
1885
- enabled: !config.noStatic
1886
- };
1887
- if (config.static) {
1888
- serverOptions.directory = config.static;
1889
- }
1890
- if (config.routes) {
1891
- serverOptions.routes = config.routes;
198
+ logger.log(`
199
+ ╔═══════════════════════════════════════╗
200
+ ║ ║
201
+ ║ API Faker v${getVersion().padEnd(19)}║
202
+ ║ ║
203
+ ╚═══════════════════════════════════════╝
204
+ `);
1892
205
  }
1893
- if (config.middlewares) {
1894
- serverOptions.middlewares = config.middlewares;
206
+ if (!config.source) {
207
+ logger.error('No source file specified');
208
+ logger.info('Run "rest_api_faker --help" for usage information');
209
+ process.exit(1);
1895
210
  }
1896
- if (config.delay !== void 0) {
1897
- serverOptions.delay = config.delay;
211
+ if (!config.quiet) {
212
+ logger.info(`Source: ${config.source}`);
213
+ logger.info(`Port: ${String(config.port)}`);
214
+ logger.info(`Host: ${config.host}`);
215
+ logger.log('');
216
+ logger.info('Loading database...');
1898
217
  }
1899
- const app = await createServer(db, serverOptions);
1900
- let server = startServer(app, {
1901
- port: config.port,
1902
- host: config.host,
1903
- quiet: config.quiet
1904
- });
1905
- if (config.watch) {
1906
- const filesToWatch = [];
1907
- if (config.source && (0, import_fs3.existsSync)(config.source)) {
1908
- filesToWatch.push(config.source);
1909
- }
1910
- if (config.routes && (0, import_fs3.existsSync)(config.routes)) {
1911
- filesToWatch.push(config.routes);
1912
- }
1913
- if (config.middlewares && (0, import_fs3.existsSync)(config.middlewares)) {
1914
- filesToWatch.push(config.middlewares);
1915
- }
1916
- if (filesToWatch.length === 0) {
218
+ try {
219
+ const db = new Database(config.source, {
220
+ idField: config.id,
221
+ foreignKeySuffix: config.foreignKeySuffix,
222
+ autoSave: false,
223
+ });
224
+ await db.init();
1917
225
  if (!config.quiet) {
1918
- logger.warn("Watch mode enabled but no files found to watch");
226
+ const data = db.getData();
227
+ const resources = Object.keys(data);
228
+ logger.success(`Loaded ${String(resources.length)} resource(s): ${resources.join(', ')}`);
229
+ logger.log('');
230
+ }
231
+ const serverOptions = {
232
+ port: config.port,
233
+ host: config.host,
234
+ readOnly: config.readOnly,
235
+ noCors: config.noCors,
236
+ noGzip: config.noGzip,
237
+ quiet: config.quiet,
238
+ idField: config.id,
239
+ foreignKeySuffix: config.foreignKeySuffix,
240
+ enabled: !config.noStatic,
241
+ };
242
+ if (config.static) {
243
+ serverOptions.directory = config.static;
244
+ }
245
+ if (config.routes) {
246
+ serverOptions.routes = config.routes;
247
+ }
248
+ if (config.middlewares) {
249
+ serverOptions.middlewares = config.middlewares;
250
+ }
251
+ if (config.delay !== undefined) {
252
+ serverOptions.delay = config.delay;
1919
253
  }
1920
- } else {
1921
- const watcher = (0, import_chokidar.watch)(filesToWatch, {
1922
- ignoreInitial: true,
1923
- persistent: true
254
+ const app = await createServer(db, serverOptions);
255
+ let server = startServer(app, {
256
+ port: config.port,
257
+ host: config.host,
258
+ quiet: config.quiet,
1924
259
  });
1925
- watcher.on("change", (path) => {
1926
- void (async () => {
1927
- if (!config.quiet) {
1928
- logger.log("");
1929
- logger.info(`File changed: ${path}`);
260
+ if (config.watch) {
261
+ const filesToWatch = [];
262
+ if (config.source && existsSync(config.source)) {
263
+ filesToWatch.push(config.source);
1930
264
  }
1931
- try {
1932
- if (path === config.source) {
1933
- if (!config.quiet) {
1934
- logger.info("Reloading database...");
1935
- }
1936
- await db.init();
1937
- if (!config.quiet) {
1938
- const data = db.getData();
1939
- const resources = Object.keys(data);
1940
- logger.success(
1941
- `Reloaded ${String(resources.length)} resource(s): ${resources.join(", ")}`
1942
- );
1943
- }
1944
- }
1945
- if (path === config.routes || path === config.middlewares) {
265
+ if (config.routes && existsSync(config.routes)) {
266
+ filesToWatch.push(config.routes);
267
+ }
268
+ if (config.middlewares && existsSync(config.middlewares)) {
269
+ filesToWatch.push(config.middlewares);
270
+ }
271
+ if (filesToWatch.length === 0) {
1946
272
  if (!config.quiet) {
1947
- logger.info("Restarting server to apply changes...");
1948
- }
1949
- const { resolve: resolvePath } = await import("path");
1950
- const absolutePath = resolvePath(path);
1951
- const cacheKey = absolutePath;
1952
- if (cacheKey in require.cache) {
1953
- delete require.cache[cacheKey];
273
+ logger.warn('Watch mode enabled but no files found to watch');
1954
274
  }
275
+ }
276
+ else {
277
+ const watcher = watch(filesToWatch, {
278
+ ignoreInitial: true,
279
+ persistent: true,
280
+ });
281
+ watcher.on('change', (path) => {
282
+ void (async () => {
283
+ if (!config.quiet) {
284
+ logger.log('');
285
+ logger.info(`File changed: ${path}`);
286
+ }
287
+ try {
288
+ if (path === config.source) {
289
+ if (!config.quiet) {
290
+ logger.info('Reloading database...');
291
+ }
292
+ await db.init();
293
+ if (!config.quiet) {
294
+ const data = db.getData();
295
+ const resources = Object.keys(data);
296
+ logger.success(`Reloaded ${String(resources.length)} resource(s): ${resources.join(', ')}`);
297
+ }
298
+ }
299
+ if (path === config.routes || path === config.middlewares) {
300
+ if (!config.quiet) {
301
+ logger.info('Restarting server to apply changes...');
302
+ }
303
+ const { resolve: resolvePath } = await import('path');
304
+ const absolutePath = resolvePath(path);
305
+ const cacheKey = absolutePath;
306
+ if (cacheKey in require.cache) {
307
+ delete require.cache[cacheKey];
308
+ }
309
+ if (!config.quiet) {
310
+ logger.debug(`Cleared module cache for ${path}`);
311
+ logger.info('Shutting down current server...');
312
+ }
313
+ await new Promise((resolve) => {
314
+ server.close(() => {
315
+ if (!config.quiet) {
316
+ logger.debug('Server closed successfully');
317
+ }
318
+ resolve();
319
+ });
320
+ });
321
+ if (!config.quiet) {
322
+ logger.info('Creating new server with updated configuration...');
323
+ }
324
+ const newApp = await createServer(db, serverOptions);
325
+ if (!config.quiet) {
326
+ logger.info('Starting new server...');
327
+ }
328
+ const newServer = startServer(newApp, {
329
+ port: config.port,
330
+ host: config.host,
331
+ quiet: config.quiet,
332
+ });
333
+ server = newServer;
334
+ if (!config.quiet) {
335
+ logger.success('✓ Server restarted successfully with new configuration');
336
+ }
337
+ }
338
+ }
339
+ catch (error) {
340
+ logger.error(`Failed to reload: ${error instanceof Error ? error.message : 'Unknown error'}`);
341
+ }
342
+ })();
343
+ });
344
+ watcher.on('error', (error) => {
345
+ logger.error(`Watcher error: ${error instanceof Error ? error.message : String(error)}`);
346
+ });
1955
347
  if (!config.quiet) {
1956
- logger.debug(`Cleared module cache for ${path}`);
1957
- logger.info("Shutting down current server...");
348
+ const watchList = filesToWatch.map((f) => f).join(', ');
349
+ logger.info(`Watching ${watchList} for changes...`);
350
+ logger.log('');
1958
351
  }
1959
- await new Promise((resolve5) => {
1960
- server.close(() => {
352
+ process.on('SIGINT', () => {
1961
353
  if (!config.quiet) {
1962
- logger.debug("Server closed successfully");
354
+ logger.log('');
355
+ logger.info('Shutting down...');
1963
356
  }
1964
- resolve5();
1965
- });
357
+ watcher.close().catch(() => {
358
+ });
359
+ server.close(() => {
360
+ process.exit(0);
361
+ });
1966
362
  });
1967
- if (!config.quiet) {
1968
- logger.info("Creating new server with updated configuration...");
1969
- }
1970
- const newApp = await createServer(db, serverOptions);
1971
- if (!config.quiet) {
1972
- logger.info("Starting new server...");
1973
- }
1974
- const newServer = startServer(newApp, {
1975
- port: config.port,
1976
- host: config.host,
1977
- quiet: config.quiet
1978
- });
1979
- server = newServer;
1980
- if (!config.quiet) {
1981
- logger.success("\u2713 Server restarted successfully with new configuration");
1982
- }
1983
- }
1984
- } catch (error) {
1985
- logger.error(
1986
- `Failed to reload: ${error instanceof Error ? error.message : "Unknown error"}`
1987
- );
1988
363
  }
1989
- })();
1990
- });
1991
- watcher.on("error", (error) => {
1992
- logger.error(`Watcher error: ${error instanceof Error ? error.message : String(error)}`);
1993
- });
1994
- if (!config.quiet) {
1995
- const watchList = filesToWatch.map((f) => f).join(", ");
1996
- logger.info(`Watching ${watchList} for changes...`);
1997
- logger.log("");
1998
364
  }
1999
- process.on("SIGINT", () => {
2000
- if (!config.quiet) {
2001
- logger.log("");
2002
- logger.info("Shutting down...");
2003
- }
2004
- watcher.close().catch(() => {
2005
- });
2006
- server.close(() => {
2007
- process.exit(0);
2008
- });
2009
- });
2010
- }
2011
365
  }
2012
- } catch (error) {
2013
- logger.error(error instanceof Error ? error.message : "Unknown error");
2014
- process.exit(1);
2015
- }
366
+ catch (error) {
367
+ logger.error(error instanceof Error ? error.message : 'Unknown error');
368
+ process.exit(1);
369
+ }
2016
370
  }
2017
371
  main().catch((error) => {
2018
- logger.error(`Fatal error: ${error instanceof Error ? error.message : "Unknown error"}`);
2019
- process.exit(1);
372
+ logger.error(`Fatal error: ${error instanceof Error ? error.message : 'Unknown error'}`);
373
+ process.exit(1);
2020
374
  });
2021
375
  //# sourceMappingURL=cli.js.map