phantomback 1.0.2 β†’ 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,85 +1,76 @@
1
1
  <div align="center">
2
2
 
3
+ <br />
4
+
3
5
  # πŸ‘» PhantomBack
4
6
 
5
- ### Instant Fake Backend Generator with Smart Responses
7
+ **Instant Fake Backend Generator with Smart Responses**
6
8
 
7
9
  [![npm version](https://img.shields.io/npm/v/phantomback.svg?style=flat-square&color=a78bfa)](https://www.npmjs.com/package/phantomback)
10
+ [![downloads](https://img.shields.io/npm/dm/phantomback.svg?style=flat-square&color=a78bfa)](https://www.npmjs.com/package/phantomback)
8
11
  [![license](https://img.shields.io/npm/l/phantomback.svg?style=flat-square)](LICENSE)
9
12
  [![node](https://img.shields.io/node/v/phantomback.svg?style=flat-square)](package.json)
10
- [![docs](https://img.shields.io/badge/docs-phantomback-a78bfa?style=flat-square)](https://phantombackxdocs.vercel.app)
11
-
12
- **Stop waiting for the backend. Start building now.**
13
-
14
- Drop in your API schema β†’ get a fully functional REST server with realistic data,
15
- JWT auth, pagination, filtering, sorting, search, and nested routes β€” in seconds.
16
13
 
17
- [Getting Started](https://phantombackxdocs.vercel.app/getting-started) Β· [Config Guide](https://phantombackxdocs.vercel.app/configuration) Β· [API Reference](https://phantombackxdocs.vercel.app/api-reference) Β· [Examples](https://phantombackxdocs.vercel.app/examples)
14
+ Stop waiting for the backend. Drop in your API schema β†’ get a fully functional REST server with
15
+ realistic data, JWT auth, pagination, filtering, sorting, search, and nested routes β€” in seconds.
18
16
 
19
- [Documentation](https://phantombackxdocs.vercel.app) Β· [npm](https://www.npmjs.com/package/phantomback) Β· [GitHub](https://github.com/madhavxchaturvedi/npm-phantomback)
17
+ [Documentation](https://phantombackxdocs.vercel.app) Β·
18
+ [Getting Started](https://phantombackxdocs.vercel.app/docs/getting-started) Β·
19
+ [API Reference](https://phantombackxdocs.vercel.app/docs/api-reference) Β·
20
+ [Playground](https://phantombackxdocs.vercel.app/docs/playground) Β·
21
+ [GitHub](https://github.com/madhavxchaturvedi/npm-phantomback)
20
22
 
21
- Made by [Madhav Chaturvedi](https://madhavxchaturvedi.vercel.app) Β· [LinkedIn](https://www.linkedin.com/in/madhavxchaturvedi/) Β· [Instagram](https://www.instagram.com/madhavxchaturvedi)
23
+ <br />
22
24
 
23
25
  </div>
24
26
 
25
27
  ---
26
28
 
27
- ## ✨ Features
28
-
29
- - πŸš€ **Zero-config mode** β€” one command, full working API
30
- - πŸ“¦ **Auto CRUD** β€” GET, POST, PUT, PATCH, DELETE for every resource
31
- - 🎭 **Realistic data** β€” powered by Faker.js (names, emails, prices, avatars...)
32
- - πŸ“„ **Pagination** β€” `?page=1&limit=10` with total counts & page metadata
33
- - πŸ” **Search** β€” `?q=term` full-text search across all fields
34
- - 🎯 **Filtering** β€” `?role=admin`, `?age_gte=18`, `?price_lte=100`, `?name_like=john`
35
- - ↕️ **Sorting** β€” `?sort=-price`, `?sort=name,createdAt`
36
- - πŸ”— **Relations & Nested Routes** β€” `GET /users/:id/posts` auto-detected from foreign keys
37
- - πŸ”’ **JWT Auth** β€” register, login, protected routes with Bearer tokens
38
- - ⏱️ **Response Delay** β€” simulate slow networks with fixed or random latency
39
- - βœ… **Validation** β€” required fields, type checks, unique constraints, email format
40
- - πŸ–₯️ **CLI + Library** β€” use as a CLI tool or import in your code
41
- - 🧠 **Smart Defaults** β€” sensible conventions, override only what you need
29
+ ## Why PhantomBack?
30
+
31
+ | Pain point | PhantomBack fix |
32
+ |---|---|
33
+ | Backend not ready yet | Full REST API in one command |
34
+ | Static JSON mocks feel fake | Stateful CRUD with realistic Faker data |
35
+ | No pagination / filtering in mocks | Full query support out of the box |
36
+ | Auth testing is painful | JWT auth simulation built-in |
37
+ | Mock server setup takes time | One command or one line of code |
42
38
 
43
39
  ---
44
40
 
45
- ## πŸ“¦ Installation
41
+ ## Quick Start
46
42
 
47
- ```bash
48
- # Global install (recommended for CLI)
49
- npm install -g phantomback
43
+ ### Install
50
44
 
51
- # Or as a dev dependency in your project
52
- npm install --save-dev phantomback
45
+ ```bash
46
+ npm install -g phantomback # CLI (global)
47
+ npm install --save-dev phantomback # Library (project)
53
48
  ```
54
49
 
55
- ---
56
-
57
- ## πŸš€ Quick Start
58
-
59
- ### One command β€” full API:
50
+ ### Zero-config β€” one command, full API
60
51
 
61
52
  ```bash
62
53
  phantomback start --zero
63
54
  ```
64
55
 
65
- That's it. You now have a REST API running at `http://localhost:3777` with:
66
- - πŸ‘€ 25 Users (protected with auth)
67
- - πŸ“ 50 Posts
68
- - πŸ’¬ 100 Comments
69
- - πŸ“¦ 30 Products
70
- - βœ… 40 Todos
56
+ You now have a REST API at **`http://localhost:3777`** with:
71
57
 
72
- ### Or with your own config:
58
+ | Resource | Count | Auth |
59
+ |---|---|---|
60
+ | `/api/users` | 25 records | πŸ”’ Protected |
61
+ | `/api/posts` | 50 records | β€” |
62
+ | `/api/comments` | 100 records | β€” |
63
+ | `/api/products` | 30 records | β€” |
64
+ | `/api/todos` | 40 records | β€” |
73
65
 
74
- ```bash
75
- # Generate a starter config
76
- phantomback init
66
+ ### Or bring your own schema
77
67
 
78
- # Edit phantom.config.js to your needs, then:
79
- phantomback start
68
+ ```bash
69
+ phantomback init # generates phantom.config.js
70
+ phantomback start # reads config and starts server
80
71
  ```
81
72
 
82
- ### Or as a library:
73
+ ### Or use as a library
83
74
 
84
75
  ```js
85
76
  import { createPhantom } from 'phantomback';
@@ -100,10 +91,10 @@ const server = await createPhantom({
100
91
 
101
92
  // server.stop() β€” shut down
102
93
  // server.reset() β€” re-seed all data
103
- // server.getStore() β€” export current state as JSON
94
+ // server.getStore() β€” export current state
104
95
  ```
105
96
 
106
- One line for zero-config:
97
+ One-liner for zero-config:
107
98
 
108
99
  ```js
109
100
  import { createPhantomZero } from 'phantomback';
@@ -112,18 +103,16 @@ await createPhantomZero(); // Full demo API on port 3777
112
103
 
113
104
  ---
114
105
 
115
- ## βš™οΈ Configuration
106
+ ## Configuration
116
107
 
117
- Create a `phantom.config.js` in your project root:
108
+ Create **`phantom.config.js`** in your project root:
118
109
 
119
110
  ```js
120
111
  export default {
121
112
  port: 3777,
122
113
  prefix: '/api',
123
-
124
- // Global response latency (ms)
125
- // latency: 500,
126
- // latency: [200, 800], // random range
114
+ // latency: 500, // fixed delay (ms)
115
+ // latency: [200, 800], // random range
127
116
 
128
117
  auth: {
129
118
  secret: 'my-secret-key',
@@ -133,23 +122,23 @@ export default {
133
122
  resources: {
134
123
  users: {
135
124
  fields: {
136
- name: { type: 'name', required: true },
137
- email: { type: 'email', unique: true },
138
- age: { type: 'number', min: 18, max: 65 },
139
- role: { type: 'enum', values: ['admin', 'user', 'moderator'] },
140
- avatar: { type: 'avatar' },
125
+ name: { type: 'name', required: true },
126
+ email: { type: 'email', unique: true },
127
+ age: { type: 'number', min: 18, max: 65 },
128
+ role: { type: 'enum', values: ['admin', 'user', 'moderator'] },
129
+ avatar: { type: 'avatar' },
141
130
  isActive: { type: 'boolean' },
142
131
  },
143
- seed: 25, // auto-generate 25 records
132
+ seed: 25,
144
133
  auth: true, // protect with JWT
145
134
  },
146
135
 
147
136
  posts: {
148
137
  fields: {
149
- title: { type: 'title', required: true },
150
- body: { type: 'paragraphs', count: 3 },
138
+ title: { type: 'title', required: true },
139
+ body: { type: 'paragraphs', count: 3 },
151
140
  userId: { type: 'relation', resource: 'users' },
152
- views: { type: 'number', min: 0, max: 10000 },
141
+ views: { type: 'number', min: 0, max: 10000 },
153
142
  },
154
143
  seed: 50,
155
144
  },
@@ -157,43 +146,29 @@ export default {
157
146
  };
158
147
  ```
159
148
 
160
- ### Supported Field Types
149
+ > **Full config reference β†’** [phantombackxdocs.vercel.app/docs/configuration](https://phantombackxdocs.vercel.app/docs/configuration)
150
+
151
+ ---
152
+
153
+ ## Supported Field Types
161
154
 
162
155
  | Type | Generates | Options |
163
- |------|-----------|---------|
164
- | `name` | Full name | β€” |
165
- | `firstName` | First name | β€” |
166
- | `lastName` | Last name | β€” |
167
- | `username` | Username | β€” |
168
- | `email` | Email address | `unique: true` |
169
- | `avatar` | Avatar URL | β€” |
156
+ |---|---|---|
157
+ | `name` `firstName` `lastName` `username` | Names | β€” |
158
+ | `email` | Email | `unique` |
170
159
  | `phone` | Phone number | β€” |
171
- | `bio` | Short bio | β€” |
172
- | `jobTitle` | Job title | β€” |
173
- | `sentence` | One sentence | β€” |
174
- | `paragraph` | One paragraph | β€” |
175
- | `paragraphs` | Multiple paragraphs | `count: 3` |
176
- | `title` | Short title | β€” |
177
- | `description` | 2-4 sentences | β€” |
178
- | `slug` | URL slug | β€” |
179
- | `number` | Integer | `min`, `max` |
180
- | `float` | Decimal | `min`, `max`, `precision` |
181
- | `price` | Price string | β€” |
182
- | `rating` | 1.0 – 5.0 | β€” |
183
- | `boolean` | true/false | β€” |
184
- | `date` | ISO date | β€” |
185
- | `pastDate` | Past date | β€” |
186
- | `futureDate` | Future date | β€” |
187
- | `url` | URL | β€” |
188
- | `image` | Image URL | β€” |
189
- | `color` | Color name | β€” |
190
- | `address` | Street address | β€” |
191
- | `city` | City name | β€” |
192
- | `country` | Country name | β€” |
193
- | `product` | Product name | β€” |
194
- | `company` | Company name | β€” |
160
+ | `avatar` | Avatar URL | β€” |
161
+ | `bio` `jobTitle` | Profile text | β€” |
162
+ | `sentence` `paragraph` `paragraphs` | Text blocks | `count` |
163
+ | `title` `description` `slug` | Content | β€” |
164
+ | `number` `float` `price` `rating` | Numbers | `min` `max` `precision` |
165
+ | `boolean` | true / false | β€” |
166
+ | `date` `pastDate` `futureDate` | ISO dates | β€” |
167
+ | `url` `image` `color` | Misc | β€” |
168
+ | `address` `city` `country` | Location | β€” |
169
+ | `product` `company` | Business | β€” |
195
170
  | `enum` | Random from list | `values: [...]` |
196
- | `relation` | Foreign key | `resource: 'users'` |
171
+ | `relation` | Foreign key | `resource: '...'` |
197
172
  | `uuid` | UUID string | β€” |
198
173
 
199
174
  ### Field Options
@@ -201,60 +176,81 @@ export default {
201
176
  ```js
202
177
  {
203
178
  type: 'email',
204
- required: true, // validation: must be present
205
- unique: true, // validation: no duplicates
206
- min: 0, // for numbers: minimum value
207
- max: 100, // for numbers: maximum value
179
+ required: true, // must be present on create
180
+ unique: true, // no duplicates allowed
181
+ min: 0, // number minimum
182
+ max: 100, // number maximum
208
183
  }
209
184
  ```
210
185
 
211
186
  ---
212
187
 
213
- ## πŸ›£οΈ Auto-Generated Routes
188
+ ## Auto-Generated Routes
214
189
 
215
- For each resource (e.g., `users`), PhantomBack generates:
190
+ Every resource gets full CRUD automatically:
216
191
 
217
192
  | Method | Endpoint | Description |
218
- |--------|----------|-------------|
193
+ |---|---|---|
219
194
  | `GET` | `/api/users` | List all (paginated) |
220
- | `GET` | `/api/users/:id` | Get one by ID |
221
- | `POST` | `/api/users` | Create new |
195
+ | `GET` | `/api/users/:id` | Get one |
196
+ | `POST` | `/api/users` | Create |
222
197
  | `PUT` | `/api/users/:id` | Full update |
223
198
  | `PATCH` | `/api/users/:id` | Partial update |
224
199
  | `DELETE` | `/api/users/:id` | Delete |
225
200
 
226
- ### Nested Routes (auto-detected from relations)
201
+ ### Nested Routes
227
202
 
228
- If `posts` has `userId: { type: 'relation', resource: 'users' }`, you get:
203
+ If `posts` has `userId: { type: 'relation', resource: 'users' }`, you automatically get:
229
204
 
230
205
  ```
231
- GET /api/users/:id/posts β†’ all posts by this user
206
+ GET /api/users/:id/posts β†’ all posts by this user
232
207
  ```
233
208
 
234
209
  ### Special Routes
235
210
 
236
211
  | Method | Endpoint | Description |
237
- |--------|----------|-------------|
212
+ |---|---|---|
238
213
  | `GET` | `/api` | List all endpoints |
239
- | `GET` | `/api/_health` | Server health check |
240
- | `POST` | `/api/auth/register` | Register (email + password) |
241
- | `POST` | `/api/auth/login` | Login (returns JWT) |
242
- | `GET` | `/api/auth/me` | Current user (requires token) |
214
+ | `GET` | `/api/_health` | Health check |
215
+ | `POST` | `/api/auth/register` | Register |
216
+ | `POST` | `/api/auth/login` | Login β†’ JWT |
217
+ | `GET` | `/api/auth/me` | Current user (token required) |
243
218
 
244
219
  ---
245
220
 
246
- ## πŸ” Query Parameters
247
-
248
- ### Pagination
221
+ ## Query Parameters
249
222
 
250
223
  ```bash
224
+ # Pagination
251
225
  GET /api/users?page=2&limit=10
252
226
  GET /api/users?offset=20&limit=10
227
+
228
+ # Filtering
229
+ GET /api/users?role=admin # exact match
230
+ GET /api/users?age_gte=18 # β‰₯
231
+ GET /api/users?age_lte=30 # ≀
232
+ GET /api/users?age_gt=18&age_lt=30 # range
233
+ GET /api/users?role_ne=admin # not equal
234
+ GET /api/users?name_like=john # contains
235
+
236
+ # Sorting
237
+ GET /api/users?sort=name # ascending
238
+ GET /api/users?sort=-name # descending
239
+ GET /api/users?sort=role,-age # multi-field
240
+
241
+ # Search
242
+ GET /api/users?q=john # full-text across all fields
243
+
244
+ # Field Selection
245
+ GET /api/users?fields=name,email,role
253
246
  ```
254
247
 
255
- Response includes:
248
+ Response includes pagination metadata:
249
+
256
250
  ```json
257
251
  {
252
+ "success": true,
253
+ "data": [ ... ],
258
254
  "meta": {
259
255
  "page": 2,
260
256
  "limit": 10,
@@ -266,91 +262,92 @@ Response includes:
266
262
  }
267
263
  ```
268
264
 
269
- ### Filtering
270
-
271
- ```bash
272
- GET /api/users?role=admin # exact match
273
- GET /api/users?age_gte=18 # greater than or equal
274
- GET /api/users?age_lte=30 # less than or equal
275
- GET /api/users?age_gt=18 # greater than
276
- GET /api/users?age_lt=30 # less than
277
- GET /api/users?role_ne=admin # not equal
278
- GET /api/users?name_like=john # contains (case-insensitive)
279
- ```
280
-
281
- ### Sorting
282
-
283
- ```bash
284
- GET /api/users?sort=name # ascending
285
- GET /api/users?sort=-name # descending
286
- GET /api/users?sort=role,-age # multi-field
287
- ```
288
-
289
- ### Search
290
-
291
- ```bash
292
- GET /api/users?q=john # search across all fields
293
- ```
294
-
295
- ### Field Selection
296
-
297
- ```bash
298
- GET /api/users?fields=name,email,role # only return these fields
299
- ```
300
-
301
265
  ---
302
266
 
303
- ## πŸ”’ Authentication
267
+ ## Authentication
304
268
 
305
- 1. **Register:**
306
269
  ```bash
270
+ # 1. Register
307
271
  curl -X POST http://localhost:3777/api/auth/register \
308
272
  -H "Content-Type: application/json" \
309
- -d '{"email": "user@example.com", "password": "secret123", "name": "John"}'
310
- ```
273
+ -d '{"email": "user@test.com", "password": "secret123", "name": "John"}'
311
274
 
312
- 2. **Login:**
313
- ```bash
275
+ # 2. Login
314
276
  curl -X POST http://localhost:3777/api/auth/login \
315
277
  -H "Content-Type: application/json" \
316
- -d '{"email": "user@example.com", "password": "secret123"}'
317
- ```
278
+ -d '{"email": "user@test.com", "password": "secret123"}'
318
279
 
319
- 3. **Access protected routes:**
320
- ```bash
280
+ # 3. Use the token
321
281
  curl http://localhost:3777/api/users \
322
282
  -H "Authorization: Bearer <your-token>"
323
283
  ```
324
284
 
325
285
  ---
326
286
 
327
- ## πŸ’» CLI Reference
287
+ ## Reality Mode β€” Chaos Engineering
288
+
289
+ Test your frontend's resilience by simulating real-world production failures.
290
+
291
+ Enable in `phantom.config.js`:
292
+
293
+ ```js
294
+ export default {
295
+ // ...
296
+ chaos: {
297
+ enabled: true,
298
+ latency: { min: 200, max: 5000 }, // latency jitter range (ms)
299
+ failureRate: 0.1, // 10% random 5xx responses
300
+ errorCodes: [500, 502, 503, 504],
301
+ connectionDropRate: 0.02, // 2% abrupt connection drops
302
+ corruptionRate: 0.02, // 2% malformed JSON
303
+ timeoutRate: 0.03, // 3% hanging responses
304
+ scenarios: ['latency', 'failure', 'drop', 'corruption', 'timeout'],
305
+ },
306
+ };
307
+ ```
308
+
309
+ Or enable instantly from the CLI:
328
310
 
329
311
  ```bash
330
- # Start with auto-detected config file
331
- phantomback start
312
+ phantomback start --chaos # enable with defaults
313
+ phantomback start --chaos --chaos-failure 0.2 # 20% failure rate
314
+ phantomback start --chaos --chaos-latency 500,3000 # 500–3000 ms jitter
315
+ ```
332
316
 
333
- # Start with zero-config (demo mode)
334
- phantomback start --zero
317
+ | Scenario | Config Key | Description |
318
+ |---|---|---|
319
+ | `latency` | `latency` | Injects random delay on ~30% of requests |
320
+ | `failure` | `failureRate` | Returns a random 5xx error |
321
+ | `drop` | `connectionDropRate` | Abruptly closes the TCP connection |
322
+ | `corruption` | `corruptionRate` | Sends malformed / partial JSON |
323
+ | `timeout` | `timeoutRate` | Hangs the response for ~30 seconds |
335
324
 
336
- # Custom port
337
- phantomback start --port 4000
325
+ > **Full guide β†’** [phantombackxdocs.vercel.app/docs/reality-mode](https://phantombackxdocs.vercel.app/docs/reality-mode)
338
326
 
339
- # Specific config file
340
- phantomback start --config ./my-api.config.js
327
+ ---
341
328
 
342
- # Generate starter config
343
- phantomback init
329
+ ## CLI Reference
344
330
 
345
- # Show help
331
+ ```bash
332
+ phantomback start # start with phantom.config.js
333
+ phantomback start --zero # zero-config demo mode
334
+ phantomback start --port 4000 # custom port
335
+ phantomback start --config ./my-api.config.js # custom config path
336
+ phantomback start --chaos # enable Reality Mode
337
+ phantomback start --chaos --chaos-failure 0.2 # 20% failure rate
338
+ phantomback start --chaos --chaos-latency 200,5000 # latency jitter range
339
+ phantomback init # generate starter config
346
340
  phantomback --help
347
341
  ```
348
342
 
343
+ > **Full CLI docs β†’** [phantombackxdocs.vercel.app/docs/cli](https://phantombackxdocs.vercel.app/docs/cli)
344
+
349
345
  ---
350
346
 
351
- ## πŸ—οΈ Real-World Examples
347
+ ## Real-World Examples
352
348
 
353
- ### Hospital Management
349
+ <details>
350
+ <summary><strong>πŸ₯ Hospital Management</strong></summary>
354
351
 
355
352
  ```js
356
353
  export default {
@@ -376,8 +373,10 @@ export default {
376
373
  },
377
374
  };
378
375
  ```
376
+ </details>
379
377
 
380
- ### E-Commerce
378
+ <details>
379
+ <summary><strong>πŸ›’ E-Commerce</strong></summary>
381
380
 
382
381
  ```js
383
382
  export default {
@@ -403,12 +402,13 @@ export default {
403
402
  },
404
403
  };
405
404
  ```
405
+ </details>
406
406
 
407
- ---
407
+ > **More examples β†’** [phantombackxdocs.vercel.app/docs/examples](https://phantombackxdocs.vercel.app/docs/examples)
408
408
 
409
- ## πŸ“œ Response Format
409
+ ---
410
410
 
411
- All responses follow a consistent format:
411
+ ## Response Format
412
412
 
413
413
  **Success:**
414
414
  ```json
@@ -423,10 +423,7 @@ All responses follow a consistent format:
423
423
  ```json
424
424
  {
425
425
  "success": false,
426
- "error": {
427
- "status": 404,
428
- "message": "users with id \"abc\" not found"
429
- }
426
+ "error": { "status": 404, "message": "users with id \"abc\" not found" }
430
427
  }
431
428
  ```
432
429
 
@@ -437,28 +434,27 @@ All responses follow a consistent format:
437
434
  "error": {
438
435
  "status": 400,
439
436
  "message": "Validation failed",
440
- "details": [
441
- { "field": "email", "message": "\"email\" is required" }
442
- ]
437
+ "details": [{ "field": "email", "message": "\"email\" is required" }]
443
438
  }
444
439
  }
445
440
  ```
446
441
 
447
442
  ---
448
443
 
449
- ## 🌟 Why PhantomBack?
444
+ ## License
450
445
 
451
- | Problem | PhantomBack Solution |
452
- |---------|---------------------|
453
- | Backend not ready yet | Start frontend dev instantly |
454
- | Static JSON mocks are unrealistic | Stateful CRUD with realistic Faker data |
455
- | No pagination/filtering in mocks | Full query support out of the box |
456
- | Auth testing is painful | JWT auth simulation built-in |
457
- | Setting up mock servers takes time | One command / one line of code |
458
- | Different projects need different schemas | Define any resource with a config file |
446
+ MIT Β© [Madhav Chaturvedi](https://github.com/madhavxchaturvedi)
459
447
 
460
448
  ---
461
449
 
462
- ## πŸ“„ License
450
+ <div align="center">
463
451
 
464
- MIT Β© [Madhav Chaturvedi](https://github.com/madhavxchaturvedi)
452
+ [Documentation](https://phantombackxdocs.vercel.app) Β·
453
+ [npm](https://www.npmjs.com/package/phantomback) Β·
454
+ [GitHub](https://github.com/madhavxchaturvedi/npm-phantomback) Β·
455
+ [CLI Reference](https://phantombackxdocs.vercel.app/docs/cli) Β·
456
+ [Playground](https://phantombackxdocs.vercel.app/docs/playground)
457
+
458
+ Made with ❀️ by [Madhav Chaturvedi](https://madhavxchaturvedi.vercel.app) · [LinkedIn](https://www.linkedin.com/in/madhavxchaturvedi/) · [Instagram](https://www.instagram.com/madhavxchaturvedi)
459
+
460
+ </div>
@@ -23,6 +23,9 @@ program
23
23
  .option('--prefix <prefix>', 'API route prefix', '/api')
24
24
  .option('-c, --config <path>', 'Path to config file')
25
25
  .option('-z, --zero', 'Zero-config mode: generate a full demo backend')
26
+ .option('--chaos', 'Enable Reality Mode (chaos engineering)')
27
+ .option('--chaos-failure <rate>', 'Failure rate for Reality Mode (0-1)', parseFloat)
28
+ .option('--chaos-latency <range>', 'Latency range in ms (e.g. "200,5000")')
26
29
  .action(async (options) => {
27
30
  const { startCommand } = await import('../src/cli/commands.js');
28
31
  await startCommand(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phantomback",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "description": "Instant fake backend generator with smart responses & chaos engineering. Drop in your API schema β†’ get a fully functional, stateful REST server with realistic data, auth, pagination, filtering, and Reality Mode for chaos testing.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -27,6 +27,10 @@
27
27
  "development",
28
28
  "prototyping",
29
29
  "chaos-engineering",
30
+ "reality-mode",
31
+ "chaos-testing",
32
+ "fault-injection",
33
+ "resilience",
30
34
  "testing",
31
35
  "frontend",
32
36
  "faker",
@@ -69,14 +69,24 @@ export default {
69
69
  },
70
70
  },
71
71
 
72
- // Chaos Engineering (Reality Mode) β€” Phase 2
73
- // chaos: {
74
- // enabled: true,
75
- // jitter: { min: 100, max: 3000 },
76
- // failureRate: 0.1,
77
- // duplicateRate: 0.05,
78
- // connectionDropRate: 0.02,
79
- // },
72
+ // Chaos Engineering (Reality Mode)
73
+ chaos: {
74
+ enabled: false,
75
+ // Latency jitter range (ms) β€” random delays injected on ~30% of requests
76
+ latency: { min: 200, max: 5000 },
77
+ // Probability (0-1) of returning a random 5xx error
78
+ failureRate: 0.1,
79
+ // HTTP error codes used for random failures
80
+ errorCodes: [500, 502, 503, 504],
81
+ // Probability of abruptly dropping the connection
82
+ connectionDropRate: 0.02,
83
+ // Probability of sending malformed/partial JSON
84
+ corruptionRate: 0.02,
85
+ // Probability of request hanging (30s timeout)
86
+ timeoutRate: 0.03,
87
+ // Which chaos scenarios to activate
88
+ scenarios: ['latency', 'failure', 'drop', 'corruption', 'timeout'],
89
+ },
80
90
  };
81
91
  `;
82
92
 
@@ -114,6 +124,31 @@ export async function startCommand(options) {
114
124
  if (options.port) config.port = parseInt(options.port, 10);
115
125
  if (options.prefix) config.prefix = options.prefix;
116
126
 
127
+ // Reality Mode (Chaos) CLI overrides
128
+ if (options.chaos) {
129
+ config.chaos = config.chaos || {};
130
+ config.chaos.enabled = true;
131
+ }
132
+ if (options.chaosFailure !== undefined) {
133
+ config.chaos = config.chaos || {};
134
+ config.chaos.enabled = true;
135
+ const rate = options.chaosFailure;
136
+ if (rate < 0 || rate > 1) {
137
+ logger.warn('--chaos-failure must be between 0 and 1. Clamping to valid range.');
138
+ }
139
+ config.chaos.failureRate = Math.max(0, Math.min(1, rate));
140
+ }
141
+ if (options.chaosLatency) {
142
+ config.chaos = config.chaos || {};
143
+ config.chaos.enabled = true;
144
+ const parts = options.chaosLatency.split(',').map(Number);
145
+ if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1]) && parts[0] >= 0 && parts[1] >= parts[0]) {
146
+ config.chaos.latency = { min: parts[0], max: parts[1] };
147
+ } else {
148
+ logger.warn('Invalid --chaos-latency format. Expected "min,max" (e.g. "200,5000"). Using defaults.');
149
+ }
150
+ }
151
+
117
152
  // Check if using defaults (no config found and no resources)
118
153
  if (Object.keys(config.resources).length === 0) {
119
154
  logger.warn('No config found and no resources defined. Using zero-config defaults.');
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Reality Mode β€” Chaos Engineering Middleware for PhantomBack
3
+ *
4
+ * Simulates real-world production instability during development:
5
+ * β€’ Latency spikes (jitter)
6
+ * β€’ Random HTTP failures (5xx errors)
7
+ * β€’ Connection drops (socket destruction)
8
+ * β€’ Response corruption (malformed JSON)
9
+ * β€’ Request timeouts (hanging responses)
10
+ * β€’ Out-of-order response delays
11
+ *
12
+ * Configuration:
13
+ * chaos: {
14
+ * enabled: true,
15
+ * latency: { min: 200, max: 5000 },
16
+ * failureRate: 0.1,
17
+ * errorCodes: [500, 502, 503, 504],
18
+ * connectionDropRate: 0.02,
19
+ * corruptionRate: 0.02,
20
+ * timeoutRate: 0.03,
21
+ * scenarios: ['latency', 'failure', 'drop', 'corruption', 'timeout'],
22
+ * }
23
+ */
24
+
25
+ import { logger } from '../utils/logger.js';
26
+
27
+ // ─── Default Chaos Configuration ─────────────────────────────────────────────
28
+
29
+ const DEFAULT_CHAOS_CONFIG = {
30
+ enabled: false,
31
+ latency: { min: 200, max: 5000 },
32
+ failureRate: 0.1,
33
+ errorCodes: [500, 502, 503, 504],
34
+ connectionDropRate: 0.02,
35
+ corruptionRate: 0.02,
36
+ timeoutRate: 0.03,
37
+ scenarios: ['latency', 'failure', 'drop', 'corruption', 'timeout'],
38
+ };
39
+
40
+ // ─── Chaos Engine ────────────────────────────────────────────────────────────
41
+
42
+ export class ChaosEngine {
43
+ constructor(config = {}) {
44
+ this.config = {
45
+ ...DEFAULT_CHAOS_CONFIG,
46
+ ...config,
47
+ latency: {
48
+ ...DEFAULT_CHAOS_CONFIG.latency,
49
+ ...(config.latency || {}),
50
+ },
51
+ errorCodes: config.errorCodes || DEFAULT_CHAOS_CONFIG.errorCodes,
52
+ scenarios: config.scenarios || DEFAULT_CHAOS_CONFIG.scenarios,
53
+ };
54
+ this.stats = {
55
+ totalRequests: 0,
56
+ chaosApplied: 0,
57
+ latencySpikes: 0,
58
+ failures: 0,
59
+ drops: 0,
60
+ corruptions: 0,
61
+ timeouts: 0,
62
+ startedAt: new Date().toISOString(),
63
+ };
64
+ this.paused = false;
65
+ }
66
+
67
+ /** Check if a specific scenario is enabled */
68
+ isScenarioEnabled(name) {
69
+ return this.config.enabled && !this.paused && this.config.scenarios.includes(name);
70
+ }
71
+
72
+ /** Roll the dice β€” returns true with the given probability (0-1) */
73
+ shouldTrigger(rate) {
74
+ return Math.random() < rate;
75
+ }
76
+
77
+ /** Generate a random latency value within the configured jitter range */
78
+ getJitter() {
79
+ const { min, max } = this.config.latency;
80
+ return Math.floor(Math.random() * (max - min + 1)) + min;
81
+ }
82
+
83
+ /** Pick a random error code from the configured list */
84
+ getRandomErrorCode() {
85
+ const codes = this.config.errorCodes;
86
+ return codes[Math.floor(Math.random() * codes.length)];
87
+ }
88
+
89
+ /** Enable chaos at runtime */
90
+ enable() {
91
+ this.config.enabled = true;
92
+ this.paused = false;
93
+ logger.chaos('Reality Mode ENABLED');
94
+ }
95
+
96
+ /** Disable chaos at runtime */
97
+ disable() {
98
+ this.config.enabled = false;
99
+ logger.chaos('Reality Mode DISABLED');
100
+ }
101
+
102
+ /** Pause chaos temporarily */
103
+ pause() {
104
+ this.paused = true;
105
+ logger.chaos('Reality Mode PAUSED');
106
+ }
107
+
108
+ /** Resume chaos after pause */
109
+ resume() {
110
+ this.paused = false;
111
+ logger.chaos('Reality Mode RESUMED');
112
+ }
113
+
114
+ /** Update chaos configuration at runtime */
115
+ configure(newConfig) {
116
+ this.config = { ...this.config, ...newConfig };
117
+ logger.chaos('Configuration updated');
118
+ }
119
+
120
+ /** Get current status and stats */
121
+ getStatus() {
122
+ return {
123
+ enabled: this.config.enabled,
124
+ paused: this.paused,
125
+ active: this.config.enabled && !this.paused,
126
+ config: this.config,
127
+ stats: { ...this.stats },
128
+ };
129
+ }
130
+
131
+ /** Reset stats counters */
132
+ resetStats() {
133
+ this.stats = {
134
+ totalRequests: 0,
135
+ chaosApplied: 0,
136
+ latencySpikes: 0,
137
+ failures: 0,
138
+ drops: 0,
139
+ corruptions: 0,
140
+ timeouts: 0,
141
+ startedAt: new Date().toISOString(),
142
+ };
143
+ }
144
+ }
145
+
146
+ // ─── Chaos Scenarios ─────────────────────────────────────────────────────────
147
+
148
+ const ERROR_MESSAGES = {
149
+ 500: 'Internal Server Error β€” [Reality Mode] Simulated server crash',
150
+ 502: 'Bad Gateway β€” [Reality Mode] Upstream service unavailable',
151
+ 503: 'Service Unavailable β€” [Reality Mode] Server overloaded',
152
+ 504: 'Gateway Timeout β€” [Reality Mode] Upstream request timed out',
153
+ };
154
+
155
+ /**
156
+ * Scenario: Latency Spike
157
+ * Adds a random delay to simulate network jitter or slow backends
158
+ */
159
+ function applyLatencySpike(engine, _req, _res) {
160
+ if (!engine.isScenarioEnabled('latency')) return null;
161
+ if (!engine.shouldTrigger(0.3)) return null; // 30% of requests get jitter
162
+
163
+ const delay = engine.getJitter();
164
+ engine.stats.latencySpikes++;
165
+
166
+ return new Promise((resolve) => {
167
+ logger.chaos(`Latency spike: +${delay}ms`);
168
+ setTimeout(resolve, delay);
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Scenario: Random Failure
174
+ * Returns a random 5xx error response
175
+ */
176
+ function applyFailure(engine, _req, res) {
177
+ if (!engine.isScenarioEnabled('failure')) return false;
178
+ if (!engine.shouldTrigger(engine.config.failureRate)) return false;
179
+
180
+ const code = engine.getRandomErrorCode();
181
+ engine.stats.failures++;
182
+
183
+ logger.chaos(`Random failure: HTTP ${code}`);
184
+ res.status(code).json({
185
+ success: false,
186
+ error: {
187
+ status: code,
188
+ message: ERROR_MESSAGES[code] || `HTTP ${code} β€” [Reality Mode] Simulated failure`,
189
+ chaos: true,
190
+ },
191
+ });
192
+
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * Scenario: Connection Drop
198
+ * Destroys the socket mid-request to simulate network issues
199
+ */
200
+ function applyConnectionDrop(engine, req, _res) {
201
+ if (!engine.isScenarioEnabled('drop')) return false;
202
+ if (!engine.shouldTrigger(engine.config.connectionDropRate)) return false;
203
+
204
+ engine.stats.drops++;
205
+ logger.chaos(`Connection drop: ${req.method} ${req.originalUrl}`);
206
+
207
+ // Destroy the underlying socket
208
+ if (req.socket) {
209
+ req.socket.destroy();
210
+ }
211
+
212
+ return true;
213
+ }
214
+
215
+ /**
216
+ * Scenario: Response Corruption
217
+ * Sends back malformed/partial JSON to test error handling
218
+ */
219
+ function applyCorruption(engine, req, res) {
220
+ if (!engine.isScenarioEnabled('corruption')) return false;
221
+ if (!engine.shouldTrigger(engine.config.corruptionRate)) return false;
222
+
223
+ engine.stats.corruptions++;
224
+ logger.chaos(`Response corruption: ${req.method} ${req.originalUrl}`);
225
+
226
+ // Pick a random corruption type
227
+ const corruptions = [
228
+ // Truncated JSON
229
+ () => {
230
+ res.setHeader('Content-Type', 'application/json');
231
+ res.status(200).end('{"success":true,"data":[{"id":"abc","na');
232
+ },
233
+ // Invalid JSON
234
+ () => {
235
+ res.setHeader('Content-Type', 'application/json');
236
+ res.status(200).end('{success: true, data: undefined}');
237
+ },
238
+ // Empty body with 200
239
+ () => {
240
+ res.status(200).end('');
241
+ },
242
+ // Wrong content type
243
+ () => {
244
+ res.setHeader('Content-Type', 'text/html');
245
+ res.status(200).end('<html><body>Unexpected HTML response</body></html>');
246
+ },
247
+ // Partial response with wrong status
248
+ () => {
249
+ res.status(206).json({
250
+ success: true,
251
+ data: null,
252
+ error: { message: '[Reality Mode] Partial response β€” data truncated' },
253
+ chaos: true,
254
+ });
255
+ },
256
+ ];
257
+
258
+ const corrupt = corruptions[Math.floor(Math.random() * corruptions.length)];
259
+ corrupt();
260
+ return true;
261
+ }
262
+
263
+ /**
264
+ * Scenario: Request Timeout
265
+ * Holds the connection open without responding (simulates hung backend)
266
+ */
267
+ function applyTimeout(engine, req, _res) {
268
+ if (!engine.isScenarioEnabled('timeout')) return false;
269
+ if (!engine.shouldTrigger(engine.config.timeoutRate)) return false;
270
+
271
+ engine.stats.timeouts++;
272
+ logger.chaos(`Request timeout: ${req.method} ${req.originalUrl} (hanging for 30s)`);
273
+
274
+ // Return a promise that resolves after 30s (or until client gives up)
275
+ return new Promise((resolve) => {
276
+ const timer = setTimeout(resolve, 30000);
277
+
278
+ // Clean up if client disconnects
279
+ req.on('close', () => {
280
+ clearTimeout(timer);
281
+ resolve();
282
+ });
283
+ });
284
+ }
285
+
286
+ // ─── Main Chaos Middleware ───────────────────────────────────────────────────
287
+
288
+ /**
289
+ * Create the Reality Mode middleware.
290
+ * This is the main entry point β€” attach to Express before resource routes.
291
+ *
292
+ * @param {ChaosEngine} engine - Chaos engine instance
293
+ * @returns {Function} Express middleware
294
+ */
295
+ export function chaosMiddleware(engine) {
296
+ return async (req, res, next) => {
297
+ engine.stats.totalRequests++;
298
+
299
+ // Skip if chaos is disabled or paused
300
+ if (!engine.config.enabled || engine.paused) {
301
+ return next();
302
+ }
303
+
304
+ // Skip chaos control endpoints
305
+ if (req.path.includes('/_chaos')) {
306
+ return next();
307
+ }
308
+
309
+ // Skip health check
310
+ if (req.path.includes('/_health')) {
311
+ return next();
312
+ }
313
+
314
+ // Add chaos header so clients know Reality Mode is active
315
+ res.setHeader('X-PhantomBack-Chaos', 'active');
316
+
317
+ // ── Execute scenarios in priority order ──
318
+
319
+ // 1. Connection Drop (highest priority β€” immediate termination)
320
+ if (applyConnectionDrop(engine, req, res)) {
321
+ engine.stats.chaosApplied++;
322
+ return; // Socket destroyed, nothing more to do
323
+ }
324
+
325
+ // 2. Request Timeout (holds connection)
326
+ const timeoutResult = applyTimeout(engine, req, res);
327
+ if (timeoutResult instanceof Promise) {
328
+ engine.stats.chaosApplied++;
329
+ await timeoutResult;
330
+ // After timeout, destroy the socket (client likely already disconnected)
331
+ if (req.socket && !req.socket.destroyed) {
332
+ req.socket.destroy();
333
+ }
334
+ return;
335
+ }
336
+
337
+ // 3. Random Failure (returns error response)
338
+ if (applyFailure(engine, req, res)) {
339
+ engine.stats.chaosApplied++;
340
+ return;
341
+ }
342
+
343
+ // 4. Response Corruption (sends malformed data)
344
+ if (applyCorruption(engine, req, res)) {
345
+ engine.stats.chaosApplied++;
346
+ return;
347
+ }
348
+
349
+ // 5. Latency Spike (adds delay, then continues to real handler)
350
+ const latencyResult = applyLatencySpike(engine, req, res);
351
+ if (latencyResult instanceof Promise) {
352
+ engine.stats.chaosApplied++;
353
+ await latencyResult;
354
+ }
355
+
356
+ // If no chaos blocked the request, proceed normally
357
+ next();
358
+ };
359
+ }
360
+
361
+ // ─── Chaos Control Routes ────────────────────────────────────────────────────
362
+
363
+ /**
364
+ * Register chaos control endpoints on the Express app.
365
+ * These allow runtime control of Reality Mode.
366
+ *
367
+ * Routes:
368
+ * GET {prefix}/_chaos β€” Status & stats
369
+ * POST {prefix}/_chaos/enable β€” Enable chaos
370
+ * POST {prefix}/_chaos/disable β€” Disable chaos
371
+ * POST {prefix}/_chaos/pause β€” Pause chaos
372
+ * POST {prefix}/_chaos/resume β€” Resume chaos
373
+ * POST {prefix}/_chaos/configure β€” Update config
374
+ * POST {prefix}/_chaos/reset β€” Reset stats
375
+ */
376
+ export function createChaosRoutes(app, engine, config) {
377
+ const prefix = config.prefix || '/api';
378
+
379
+ // GET /_chaos β€” status dashboard
380
+ app.get(`${prefix}/_chaos`, (_req, res) => {
381
+ const status = engine.getStatus();
382
+ res.json({
383
+ success: true,
384
+ message: status.active
385
+ ? 'πŸ”₯ Reality Mode is ACTIVE β€” chaos is being injected!'
386
+ : '😴 Reality Mode is inactive',
387
+ ...status,
388
+ });
389
+ });
390
+
391
+ // POST /_chaos/enable
392
+ app.post(`${prefix}/_chaos/enable`, (_req, res) => {
393
+ engine.enable();
394
+ res.json({
395
+ success: true,
396
+ message: 'πŸ”₯ Reality Mode ENABLED β€” brace yourself!',
397
+ ...engine.getStatus(),
398
+ });
399
+ });
400
+
401
+ // POST /_chaos/disable
402
+ app.post(`${prefix}/_chaos/disable`, (_req, res) => {
403
+ engine.disable();
404
+ res.json({
405
+ success: true,
406
+ message: '😴 Reality Mode DISABLED β€” back to calm waters',
407
+ ...engine.getStatus(),
408
+ });
409
+ });
410
+
411
+ // POST /_chaos/pause
412
+ app.post(`${prefix}/_chaos/pause`, (_req, res) => {
413
+ engine.pause();
414
+ res.json({
415
+ success: true,
416
+ message: '⏸️ Reality Mode PAUSED',
417
+ ...engine.getStatus(),
418
+ });
419
+ });
420
+
421
+ // POST /_chaos/resume
422
+ app.post(`${prefix}/_chaos/resume`, (_req, res) => {
423
+ engine.resume();
424
+ res.json({
425
+ success: true,
426
+ message: '▢️ Reality Mode RESUMED',
427
+ ...engine.getStatus(),
428
+ });
429
+ });
430
+
431
+ // POST /_chaos/configure β€” update chaos config at runtime
432
+ app.post(`${prefix}/_chaos/configure`, (req, res) => {
433
+ const updates = req.body;
434
+ if (!updates || typeof updates !== 'object') {
435
+ return res.status(400).json({
436
+ success: false,
437
+ error: { status: 400, message: 'Request body must be a JSON object with chaos config' },
438
+ });
439
+ }
440
+
441
+ engine.configure(updates);
442
+ res.json({
443
+ success: true,
444
+ message: 'βš™οΈ Chaos configuration updated',
445
+ ...engine.getStatus(),
446
+ });
447
+ });
448
+
449
+ // POST /_chaos/reset β€” reset stats
450
+ app.post(`${prefix}/_chaos/reset`, (_req, res) => {
451
+ engine.resetStats();
452
+ res.json({
453
+ success: true,
454
+ message: 'πŸ“Š Chaos stats reset',
455
+ ...engine.getStatus(),
456
+ });
457
+ });
458
+
459
+ // Log registered chaos routes
460
+ logger.chaos('Control endpoints registered:');
461
+ logger.route('GET', `${prefix}/_chaos`);
462
+ logger.route('POST', `${prefix}/_chaos/enable`);
463
+ logger.route('POST', `${prefix}/_chaos/disable`);
464
+ logger.route('POST', `${prefix}/_chaos/pause`);
465
+ logger.route('POST', `${prefix}/_chaos/resume`);
466
+ logger.route('POST', `${prefix}/_chaos/configure`);
467
+ logger.route('POST', `${prefix}/_chaos/reset`);
468
+ }
package/src/index.js CHANGED
@@ -64,4 +64,5 @@ export { createServer } from './server/createServer.js';
64
64
  export { parseConfig } from './schema/parser.js';
65
65
  export { DEFAULT_RESOURCES } from './schema/defaults.js';
66
66
  export { DataStore } from './data/store.js';
67
+ export { ChaosEngine, chaosMiddleware, createChaosRoutes } from './features/chaos.js';
67
68
  export { logger } from './utils/logger.js';
@@ -16,6 +16,13 @@ export const DEFAULT_CONFIG = {
16
16
  },
17
17
  chaos: {
18
18
  enabled: false,
19
+ latency: { min: 200, max: 5000 },
20
+ failureRate: 0.1,
21
+ errorCodes: [500, 502, 503, 504],
22
+ connectionDropRate: 0.02,
23
+ corruptionRate: 0.02,
24
+ timeoutRate: 0.03,
25
+ scenarios: ['latency', 'failure', 'drop', 'corruption', 'timeout'],
19
26
  },
20
27
  resources: {},
21
28
  snapshot: false,
@@ -79,6 +86,8 @@ async function findConfigFile() {
79
86
  * Merge user config with defaults
80
87
  */
81
88
  function mergeConfig(userConfig) {
89
+ const userChaos = userConfig.chaos || {};
90
+
82
91
  return {
83
92
  ...DEFAULT_CONFIG,
84
93
  ...userConfig,
@@ -88,7 +97,13 @@ function mergeConfig(userConfig) {
88
97
  },
89
98
  chaos: {
90
99
  ...DEFAULT_CONFIG.chaos,
91
- ...(userConfig.chaos || {}),
100
+ ...userChaos,
101
+ latency: {
102
+ ...DEFAULT_CONFIG.chaos.latency,
103
+ ...(userChaos.latency || {}),
104
+ },
105
+ errorCodes: userChaos.errorCodes || DEFAULT_CONFIG.chaos.errorCodes,
106
+ scenarios: userChaos.scenarios || DEFAULT_CONFIG.chaos.scenarios,
92
107
  },
93
108
  };
94
109
  }
@@ -1,6 +1,7 @@
1
1
  import { createExpressApp, addErrorHandling } from './middleware.js';
2
2
  import { createRouter } from './router.js';
3
3
  import { createAuthRoutes } from '../features/auth.js';
4
+ import { ChaosEngine, chaosMiddleware, createChaosRoutes } from '../features/chaos.js';
4
5
  import { seedAll } from '../data/seeder.js';
5
6
  import { DataStore } from '../data/store.js';
6
7
  import { logger } from '../utils/logger.js';
@@ -15,6 +16,22 @@ export async function createServer(config) {
15
16
  const store = new DataStore();
16
17
  const app = createExpressApp(config);
17
18
 
19
+ // Initialize Reality Mode (Chaos Engine)
20
+ const chaosConfig = config.chaos || {};
21
+ const chaos = new ChaosEngine(chaosConfig);
22
+
23
+ // Register chaos control endpoints (before chaos middleware so they're never affected)
24
+ createChaosRoutes(app, chaos, config);
25
+
26
+ // Always mount chaos middleware so runtime toggling via /_chaos/enable works
27
+ // The middleware itself checks engine.config.enabled internally
28
+ app.use(chaosMiddleware(chaos));
29
+
30
+ // Print chaos banner if enabled at startup
31
+ if (chaosConfig.enabled) {
32
+ logger.chaosBanner(chaosConfig);
33
+ }
34
+
18
35
  // Seed data
19
36
  seedAll(config.resources, store);
20
37
 
@@ -43,6 +60,7 @@ export async function createServer(config) {
43
60
  app,
44
61
  server,
45
62
  store,
63
+ chaos,
46
64
  stop: () =>
47
65
  new Promise((resolve) => {
48
66
  server.close(resolve);
@@ -53,5 +71,6 @@ export async function createServer(config) {
53
71
  logger.info('Store has been reset and re-seeded');
54
72
  },
55
73
  getStore: () => store.toJSON(),
74
+ getChaos: () => chaos.getStatus(),
56
75
  };
57
76
  }
@@ -29,7 +29,7 @@ export const logger = {
29
29
  console.log(chalk.hex('#a78bfa').bold(' ╔═══════════════════════════════════════╗'));
30
30
  console.log(
31
31
  chalk.hex('#a78bfa').bold(' β•‘ ') +
32
- chalk.white.bold('PhantomBack v1.0.0') +
32
+ chalk.white.bold('PhantomBack v2.0.0') +
33
33
  chalk.hex('#a78bfa').bold(' β•‘'),
34
34
  );
35
35
  console.log(
@@ -55,4 +55,70 @@ export const logger = {
55
55
  }
56
56
  console.log('');
57
57
  },
58
+
59
+ // ── Reality Mode (Chaos) Logging ──
60
+ chaos: (...args) =>
61
+ console.log(PREFIX, chalk.hex('#ff6b6b').bold('⚑CHAOS'), ...args),
62
+
63
+ chaosBanner: (config) => {
64
+ console.log('');
65
+ console.log(chalk.hex('#ff6b6b').bold(' β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”'));
66
+ console.log(
67
+ chalk.hex('#ff6b6b').bold(' β”‚ ') +
68
+ chalk.white.bold('⚑ Reality Mode ACTIVE ⚑') +
69
+ chalk.hex('#ff6b6b').bold(' β”‚'),
70
+ );
71
+ console.log(
72
+ chalk.hex('#ff6b6b').bold(' β”‚ ') +
73
+ chalk.dim('Chaos is being injected into your API') +
74
+ chalk.hex('#ff6b6b').bold(' β”‚'),
75
+ );
76
+ console.log(chalk.hex('#ff6b6b').bold(' β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜'));
77
+ console.log('');
78
+ if (config) {
79
+ const scenarios = config.scenarios || [];
80
+ console.log(PREFIX, chalk.hex('#ff6b6b').bold('Active Scenarios:'));
81
+ if (scenarios.includes('latency')) {
82
+ console.log(
83
+ PREFIX,
84
+ chalk.dim(' β”œβ”€'),
85
+ chalk.yellow('⏱ Latency Spikes'),
86
+ chalk.dim(`(${config.latency?.min || 200}–${config.latency?.max || 5000}ms)`),
87
+ );
88
+ }
89
+ if (scenarios.includes('failure')) {
90
+ console.log(
91
+ PREFIX,
92
+ chalk.dim(' β”œβ”€'),
93
+ chalk.red('πŸ’₯ Random Failures'),
94
+ chalk.dim(`(${(config.failureRate || 0.1) * 100}% rate)`),
95
+ );
96
+ }
97
+ if (scenarios.includes('drop')) {
98
+ console.log(
99
+ PREFIX,
100
+ chalk.dim(' β”œβ”€'),
101
+ chalk.magenta('πŸ”Œ Connection Drops'),
102
+ chalk.dim(`(${(config.connectionDropRate || 0.02) * 100}% rate)`),
103
+ );
104
+ }
105
+ if (scenarios.includes('corruption')) {
106
+ console.log(
107
+ PREFIX,
108
+ chalk.dim(' β”œβ”€'),
109
+ chalk.hex('#ff9f43')('🧩 Response Corruption'),
110
+ chalk.dim(`(${(config.corruptionRate || 0.02) * 100}% rate)`),
111
+ );
112
+ }
113
+ if (scenarios.includes('timeout')) {
114
+ console.log(
115
+ PREFIX,
116
+ chalk.dim(' β”œβ”€'),
117
+ chalk.hex('#ee5a24')('⏳ Request Timeouts'),
118
+ chalk.dim(`(${(config.timeoutRate || 0.03) * 100}% rate)`),
119
+ );
120
+ }
121
+ console.log('');
122
+ }
123
+ },
58
124
  };