ntropi 1.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,298 @@
1
+ <div align="center">
2
+ <a href="https://ntropi.tech/" style="text-decoration: none; color: inherit;">
3
+ <div style="background: #14181F; color: white;
4
+ font-family: 'Inter', sans-serif; font-size: 4rem;
5
+ font-weight: bold; border: 0px; border-radius: 8px;
6
+ padding: 20px;">
7
+ Ntropi<strong style="color: red">.</strong>
8
+ </div>
9
+ </a>
10
+ <div style="margin-top: 8px;">
11
+ <strong>
12
+ <p>A live mock API generator that works without having any actual backend.
13
+ Perfect for frontend development, testing, and prototyping.</p>
14
+ </strong>
15
+ </div>
16
+ </div>
17
+
18
+ ## Table of Contents
19
+
20
+ - [Installation](#installation)
21
+ - [Quick Start](#quick-start)
22
+ - [Usage](#usage)
23
+ - [Basic Commands](#basic-commands)
24
+ - [Command Options](#command-options)
25
+ - [Configuration File](#configuration-file)
26
+ - [Endpoint Properties](#endpoint-properties)
27
+ - [Examples](#examples)
28
+ - [Example 1: Simple API for Frontend Development](#example-1-simple-api-for-frontend-development)
29
+ - [Example 2: Simulate Slow Network](#example-2-simulate-slow-network)
30
+ - [Example 3: Test Error Handling](#example-3-test-error-handling)
31
+ - [Example 4: Multiple Endpoints](#example-4-multiple-endpoints)
32
+ - [Example 5: Add Endpoints](#example-5-add-endpoints)
33
+ - [Example 6: Custom Config File](#example-6-custom-config-file)
34
+ - [Example 7: Complete Workflow](#example-7-complete-workflow)
35
+ - [Advanced Usage](#advanced-usage)
36
+ - [Custom Response Data](#custom-response-data)
37
+ - [Testing Different Scenarios](#testing-different-scenarios)
38
+ - [Tips](#tips)
39
+ - [Troubleshooting](#troubleshooting)
40
+ - [Authors](#authors)
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install -g ntropi
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ Generate a config and run the server:
51
+
52
+ ```bash
53
+ ntropi --api users posts --delay 1000 --failure-rate 10
54
+ ntropi run
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Basic Commands
60
+
61
+ #### Run the Mock Server
62
+
63
+ ```bash
64
+ # Run with default config (ntropi-config.json)
65
+ ntropi run
66
+
67
+ # Run with custom config file
68
+ ntropi run --config my-config.json
69
+ ```
70
+
71
+ #### Generate Configuration
72
+
73
+ ```bash
74
+ # Generate default config with default settings
75
+ ntropi
76
+
77
+ # Generate config with specific APIs
78
+ ntropi --api users posts products
79
+
80
+ # Generate config with delay (in milliseconds)
81
+ ntropi --api users --delay 2000
82
+
83
+ # Generate config with failure rate (0-100%)
84
+ ntropi --api users --failure-rate 50
85
+
86
+ # Combine multiple options
87
+ ntropi --api users posts --delay 1000 --failure-rate 25
88
+ ```
89
+
90
+ ### Command Options
91
+
92
+ | Option | Shorthand | Description | Example |
93
+ |--------|-----------|-------------|---------|
94
+ | `--help` | | Show help information | `ntropi --help` |
95
+ | `run` | | Run the mock server | `ntropi run` |
96
+ | `--api` | `-a` | Specify API endpoints | `ntropi --api users posts` |
97
+ | `--delay` | `-d` | Set response delay (ms) | `ntropi --delay 2000` |
98
+ | `--failure-rate` | `-f` | Set failure rate (0-100%) | `ntropi --failure-rate 30` |
99
+ | `--config` | `-c` | Specify config file | `ntropi --config custom.json` |
100
+
101
+ ## Configuration File
102
+
103
+ The configuration file (`ntropi-config.json`) defines your mock API endpoints:
104
+
105
+ ```json
106
+ {
107
+ "endpoints": [
108
+ {
109
+ "path": "/api/users",
110
+ "method": "GET",
111
+ // If data is an array then one
112
+ // item is sent at random
113
+ "data": [
114
+ {"id": 1, "name": "John Doe"},
115
+ {"id": 2, "name": "Jane Smith"}
116
+ ],
117
+ "delay": 1000,
118
+ "failureRate": 10
119
+ },
120
+ {
121
+ "path": "/api/posts",
122
+ "method": "GET",
123
+ // If data is not an array then
124
+ // data is sent as it is
125
+ "data":
126
+ {"id": 1, "title": "Hello World"},
127
+ "delay": 0,
128
+ "failureRate": 0
129
+ }
130
+ ]
131
+ }
132
+ ```
133
+
134
+ **Note:** If data is an array then an item is choosen at random otherwise the data is sent as it is.
135
+
136
+ ### Endpoint Properties
137
+
138
+ - **path**: API endpoint path (e.g., `/api/users`)
139
+ - **method**: HTTP method (`GET`, `POST`, `PUT`, `DELETE`, etc.)
140
+ - **data**: Response data (can be any valid JSON)
141
+ - **delay**: Response delay in milliseconds
142
+ - **failureRate**: Percentage chance of returning 500 error (0-100)
143
+
144
+ ## Examples
145
+
146
+ ### Example 1: Simple API for Frontend Development
147
+
148
+ ```bash
149
+ # Create a users API with no delay
150
+ ntropi --api users --delay 0 --failure-rate 0
151
+ ntropi run
152
+ ```
153
+
154
+ Access at: `http://localhost:8008/api/users`
155
+
156
+ ### Example 2: Simulate Slow Network
157
+
158
+ ```bash
159
+ # Create API with 3 second delay
160
+ ntropi --api products --delay 3000
161
+ ntropi run
162
+ ```
163
+
164
+ ### Example 3: Test Error Handling
165
+
166
+ ```bash
167
+ # Create API with 50% failure rate
168
+ ntropi --api orders --failure-rate 50
169
+ ntropi run
170
+ ```
171
+
172
+ ### Example 4: Multiple Endpoints
173
+
174
+ ```bash
175
+ # Create multiple APIs with different configurations
176
+ ntropi --api users posts comments products
177
+ ntropi run
178
+ ```
179
+
180
+ Then manually edit `ntropi-config.json` to customize each endpoint.
181
+
182
+ ### Example 5: Add Endpoints
183
+
184
+ ```bash
185
+ # Add APIs to existing configuratoin
186
+ ntropi --api users
187
+ ntropi --api posts
188
+ ntropi --api comments
189
+ ntropi --api products
190
+ ntropi run
191
+ ```
192
+
193
+
194
+ ### Example 6: Custom Config File
195
+
196
+ ```bash
197
+ # Use a specific config file
198
+ ntropi run --config staging-api.json
199
+ ```
200
+
201
+ ### Example 7: Complete Workflow
202
+
203
+ ```bash
204
+ # 1. Generate config with 3 APIs
205
+ ntropi --api users posts comments --delay 500 --failure-rate 5
206
+
207
+ # 2. Edit ntropi-config.json to customize responses
208
+
209
+ # 3. Run the server
210
+ ntropi run
211
+
212
+ # 4. Access your mock APIs
213
+ # GET http://localhost:8008/api/users
214
+ # GET http://localhost:8008/api/posts
215
+ # GET http://localhost:8008/api/comments
216
+ ```
217
+
218
+ ## Advanced Usage
219
+
220
+ ### Custom Response Data
221
+
222
+ Edit `ntropi-config.json` to add custom response data:
223
+
224
+ ```json
225
+ {
226
+ "endpoints": [
227
+ {
228
+ "path": "/api/users/1",
229
+ "method": "GET",
230
+ "data": {
231
+ "id": 1,
232
+ "name": "Alice Johnson",
233
+ "email": "alice@example.com",
234
+ "role": "admin"
235
+ },
236
+ "delay": 500,
237
+ "failureRate": 0
238
+ }
239
+ ]
240
+ }
241
+ ```
242
+
243
+ ### Testing Different Scenarios
244
+
245
+ ```json
246
+ {
247
+ "endpoints": [
248
+ {
249
+ "path": "/api/fast-endpoint",
250
+ "method": "GET",
251
+ "data": {"message": "Lightning fast!"},
252
+ "delay": 0,
253
+ "failureRate": 0
254
+ },
255
+ {
256
+ "path": "/api/slow-endpoint",
257
+ "method": "GET",
258
+ "data": {"message": "Taking my time..."},
259
+ "delay": 5000,
260
+ "failureRate": 0
261
+ },
262
+ {
263
+ "path": "/api/unreliable-endpoint",
264
+ "method": "GET",
265
+ "data": {"message": "Hope I work!"},
266
+ "delay": 1000,
267
+ "failureRate": 80
268
+ }
269
+ ]
270
+ }
271
+ ```
272
+
273
+ ## Tips
274
+
275
+ 1. **Start Simple**: Begin with basic endpoints and add complexity as needed
276
+ 2. **Test Error States**: Use `failureRate` to ensure your app handles errors gracefully
277
+ 3. **Realistic Delays**: Use delays to simulate real-world network conditions
278
+ 4. **Version Control**: Commit your config files to share API mocks with your team
279
+ 5. **Multiple Configs**: Use different config files for different testing scenarios
280
+
281
+ ## Troubleshooting
282
+
283
+ ### Port Already in Use
284
+ If port 8008 is busy, Ntropi will automatically try the next available port.
285
+
286
+ ### Config File Not Found
287
+ Make sure you've generated a config file first or specify the correct path with `--config`.
288
+
289
+ ### Syntax Error in Config
290
+ Validate your JSON file using a JSON validator or linter.
291
+
292
+ ## Authors
293
+
294
+ - [Amitrajeet Konch](https://x.com/amitrajeet7635)
295
+ - [Pushpender Singh](https://x.com/Pushpender20359)
296
+
297
+
298
+
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ntropi",
3
+ "version": "1.0.0-beta",
4
+ "description": "A live mock api generator that works without having any actual backend.",
5
+ "author": "Amitrajeet Konch, Pushpender Singh",
6
+ "type": "module",
7
+ "main": "src/app.js",
8
+ "bin": {
9
+ "ntropi": "src/app.js"
10
+ },
11
+ "scripts": {
12
+ "test": "vitest"
13
+ },
14
+ "dependencies": {
15
+ "dotenv": "^17.2.3",
16
+ "fastify": "^5.7.4"
17
+ },
18
+ "devDependencies": {
19
+ "verdaccio": "^6.2.5",
20
+ "vitest": "^4.0.18"
21
+ }
22
+ }
package/src/app.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run_server } from './libs/backend.js';
4
+ import { parseArgs } from './libs/cli_parser.js';
5
+ import { updateConfig } from './libs/config_updater.js';
6
+
7
+ const args = process.argv.slice(2);
8
+ const CONFIG = parseArgs(args);
9
+
10
+ // Error Handling
11
+ if (CONFIG.error) {
12
+ console.log(`Error: ${CONFIG.error}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ // Help
17
+ if (CONFIG.help) {
18
+ console.log(`
19
+ Usage: node app.js [options]
20
+
21
+ Options:
22
+ --help Show help information
23
+ -a, --api Specify API endpoints to generate (e.g., --api users orders products)
24
+ -d, --delay <num> Set delay in seconds (non-negative integer)
25
+ -f, --failure-rate <num> Set failure rate percentage (0-100)
26
+ -c, --config <file> Specify configuration file (default: ntropi-config.json generated)
27
+ run Start the server with the default configuration file (-c for custom config file)
28
+ `);
29
+ process.exit(0);
30
+ }
31
+
32
+ if (CONFIG.run) {
33
+ // Run Server
34
+ const configPath = CONFIG.config || 'ntropi-config.json';
35
+ await run_server(configPath);
36
+ } else {
37
+ updateConfig(CONFIG);
38
+ }
@@ -0,0 +1,171 @@
1
+ import Fastify from "fastify";
2
+ import dotenv from "dotenv";
3
+ import { getConfigStateManager } from "./config_state_management.js";
4
+
5
+ dotenv.config();
6
+
7
+ let PORT;
8
+ let currentFastifyInstance = null;
9
+
10
+ function getEndpointSignature(endpoint) {
11
+ return `${endpoint.method}:${endpoint.path}`;
12
+ }
13
+
14
+ function hasEndpointChanges(oldConfig, newConfig) {
15
+ const oldSignatures = new Set(oldConfig.endpoints.map(getEndpointSignature));
16
+ const newSignatures = new Set(newConfig.endpoints.map(getEndpointSignature));
17
+
18
+ // Check if any endpoints were added or removed
19
+ if (oldSignatures.size !== newSignatures.size) {
20
+ return true;
21
+ }
22
+
23
+ for (const sig of oldSignatures) {
24
+ if (!newSignatures.has(sig)) {
25
+ return true;
26
+ }
27
+ }
28
+
29
+ for (const sig of newSignatures) {
30
+ if (!oldSignatures.has(sig)) {
31
+ return true;
32
+ }
33
+ }
34
+
35
+ return false;
36
+ }
37
+
38
+ function start_listener(fastify) {
39
+ fastify.listen({ port: PORT}, function (err, address) {
40
+ if (err) {
41
+ PORT = (PORT + 1);
42
+ if (PORT > 65535) {
43
+ console.error("No available ports found. Please free up a port and try again.");
44
+ process.exit(1);
45
+ }
46
+ start_listener(fastify);
47
+ } else {
48
+ console.log(`Server listening at ${address}`);
49
+ const configManager = getConfigStateManager();
50
+ const config = configManager.getState();
51
+ output_endpoint_path(PORT, config);
52
+ }
53
+ });
54
+ }
55
+
56
+ function generate_endpoint_path(fastify, config, configManager) {
57
+ for (let endpoint of config.endpoints) {
58
+ fastify.route({
59
+ method: endpoint.method,
60
+ url: endpoint.path,
61
+ handler: async (request, reply) => {
62
+ // Get latest config state for hot reload support
63
+ const currentConfig = configManager.getState();
64
+ const currentEndpoint = currentConfig.endpoints.find(
65
+ ep => ep.path === endpoint.path && ep.method === endpoint.method
66
+ );
67
+
68
+ if (!currentEndpoint) {
69
+ reply.code(404).send({ error: "Endpoint not found in current config" });
70
+ return;
71
+ }
72
+
73
+ // Simulate delay with current config value
74
+ if (currentEndpoint.delay && currentEndpoint.delay > 0) {
75
+ await new Promise(resolve => setTimeout(resolve, currentEndpoint.delay));
76
+ }
77
+
78
+ // Simulate failure with current config value
79
+ if (currentEndpoint.failureRate && Math.random() * 100 < currentEndpoint.failureRate) {
80
+ reply.code(500).send({ error: "Simulated server error" });
81
+ return;
82
+ }
83
+
84
+ if (Array.isArray(currentEndpoint.data)) {
85
+ currentEndpoint.data = currentEndpoint.data[Math.floor(Math.random() * currentEndpoint.data.length)];
86
+ }
87
+ reply.send(currentEndpoint.data);
88
+ }
89
+ });
90
+ }
91
+ }
92
+
93
+ function output_endpoint_path(PORT, config) {
94
+ for (let endpoint of config.endpoints) {
95
+ console.log(`Route: http://localhost:${PORT}${endpoint.path}`);
96
+ }
97
+ }
98
+
99
+ export function printNtropi() {
100
+ console.log(`
101
+ _ _ _ _
102
+ | \\ | | |_ _ __ ___ _ __ (_)
103
+ | \\| | __| '__/ _ \\| '_ \\| |
104
+ | |\\ | |_| | | (_) | |_) | | _
105
+ |_| \\_|\\__|_| \\___/| .__/|_| (_)
106
+ |_|
107
+ `);
108
+ }
109
+
110
+ export async function run_server(configPath = 'ntropi-config.json') {
111
+ printNtropi();
112
+
113
+ // Initialize config state manager
114
+ const configManager = getConfigStateManager(configPath);
115
+ const initResult = configManager.initialize();
116
+
117
+ if (!initResult.success) {
118
+ console.error(`Failed to load config: ${initResult.message}`);
119
+ process.exit(1);
120
+ }
121
+
122
+ let currentConfig = configManager.getState();
123
+
124
+ // Enable hot reload for automatic config updates
125
+ configManager.enableHotReload();
126
+
127
+ // Subscribe to config changes for logging and server restart
128
+ configManager.subscribe((event, state, details) => {
129
+ if (event === 'updated') {
130
+ // Check if endpoints were added or removed
131
+ if (hasEndpointChanges(currentConfig, state)) {
132
+ console.log('Endpoints added or removed - restarting server...');
133
+ restartServer(configManager);
134
+ currentConfig = state;
135
+ } else {
136
+ console.log('Config updated - endpoints will use new delay and failure rate values');
137
+ currentConfig = state;
138
+ }
139
+ } else if (event === 'fallback') {
140
+ console.log('Config update failed - using previous working configuration');
141
+ }
142
+ });
143
+
144
+ startServer(configManager);
145
+ }
146
+
147
+ function startServer(configManager) {
148
+ const config = configManager.getState();
149
+ const fastify = Fastify({ logger: false });
150
+ PORT = Number(process.env.PORT) || 8008;
151
+
152
+ generate_endpoint_path(fastify, config, configManager);
153
+ currentFastifyInstance = fastify;
154
+ start_listener(fastify);
155
+ }
156
+
157
+ async function restartServer(configManager) {
158
+ if (currentFastifyInstance) {
159
+ try {
160
+ await currentFastifyInstance.close();
161
+ console.log('Server stopped');
162
+ } catch (error) {
163
+ console.error('Error stopping server:', error);
164
+ }
165
+ }
166
+
167
+ // Reset to find next available port if needed
168
+ PORT = Number(process.env.PORT) || 8008;
169
+ console.log('Starting server with new configuration...');
170
+ startServer(configManager);
171
+ }
@@ -0,0 +1,111 @@
1
+ import { updateConfig } from "./config_updater.js";
2
+
3
+ export function parseArgs(args) {
4
+ const result = {};
5
+
6
+ for (let i = 0; i < args.length; i++) {
7
+ let arg = args[i];
8
+
9
+ // Handle 'run' command (without dashes)
10
+ if (arg === 'run') {
11
+ result.run = true;
12
+ continue;
13
+ }
14
+
15
+ // Skip if not a flag
16
+ if (!arg.startsWith('-')) {
17
+ continue;
18
+ }
19
+
20
+ // Map shorthand to full argument names
21
+ const shorthandMap = {
22
+ '-d': '--delay',
23
+ '-f': '--failure-rate',
24
+ '-c': '--config',
25
+ '-a': '--api',
26
+ '-r': '--run'
27
+ };
28
+
29
+ if (shorthandMap[arg]) {
30
+ arg = shorthandMap[arg];
31
+ }
32
+
33
+ const validArgs = ['--help', '--delay', '--failure-rate', '--config', "--api", '--run'];
34
+ if (!validArgs.includes(arg)) {
35
+ return { error: `Unknown argument: ${args[i]}` };
36
+ }
37
+
38
+ if (arg === '--help') {
39
+ result.help = true;
40
+ } else if (arg === '--run') {
41
+ result.run = true;
42
+ } else if (arg === '--delay') {
43
+ const delayValue = args[i + 1];
44
+ const delayNum = Number(delayValue);
45
+
46
+ if (isNaN(delayNum) || delayNum < 0 || !Number.isInteger(delayNum)) {
47
+ return { error: 'Delay must be a non-negative integer' };
48
+ }
49
+
50
+ result.delay = delayNum;
51
+ i++;
52
+ } else if (arg === '--api') {
53
+ // Collect all values following --api until the next flag or end of args
54
+ const apiValues = [];
55
+ let j = i + 1;
56
+ while (j < args.length && !args[j].startsWith('--')) {
57
+ apiValues.push(args[j]);
58
+ j++;
59
+ }
60
+
61
+ if (apiValues.length === 0) {
62
+ return { error: 'API argument requires at least one API name' };
63
+ }
64
+
65
+ result.apis = apiValues;
66
+ i = j - 1; // Update i to the last processed value
67
+ } else if (arg === '--failure-rate') {
68
+ const failureRateValue = args[i + 1];
69
+ const failureRateNum = Number(failureRateValue);
70
+
71
+ if (isNaN(failureRateNum) || failureRateNum < 0 || failureRateNum > 100) {
72
+ return { error: 'Failure rate must be between 0 and 100' };
73
+ }
74
+
75
+ result.failureRate = failureRateNum;
76
+ i++;
77
+ } else if (arg === '--config') {
78
+ result.config = args[i + 1];
79
+ i++;
80
+ }
81
+ }
82
+
83
+ if (result.run) {
84
+ if (result.config) {
85
+ return result;
86
+
87
+ }
88
+ result.config = 'ntropi-config.json';
89
+ return result;
90
+ }
91
+
92
+ if (!result.help) {
93
+ if (!result.config) {
94
+ result.config = 'ntropi-config.json';
95
+ if (!result.delay) {
96
+ result.delay = 0;
97
+ }
98
+ if (!result.failureRate) {
99
+ result.failureRate = 0;
100
+ }
101
+ }
102
+ else if(result.config) {
103
+ const response = updateConfig(result);
104
+ if (response.error) {
105
+ console.log(`Error: ${response.error}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ return result;
111
+ }
@@ -0,0 +1,36 @@
1
+ import fs from 'fs';
2
+
3
+ export function generateConfig(delay=0, failureRate=0, apis=[], configFileName='ntropi-config.json') {
4
+ let fileConfigurationJSON = {"endpoints": []};
5
+ if (apis.length == 0) {
6
+ fileConfigurationJSON["endpoints"].push(
7
+ {
8
+ 'path': '/api/health_check',
9
+ "method": "GET",
10
+ "data": ["OK", "Healthy", "Working Fine", "All Good", "Up and Running"],
11
+ "delay": delay,
12
+ "failureRate": failureRate
13
+ }
14
+ );
15
+ } else {
16
+ for (let api of apis) {
17
+ fileConfigurationJSON["endpoints"].push(
18
+ {
19
+ 'path': `/api/${api}`,
20
+ "method": "GET",
21
+ "data": [`Response from ${api}`],
22
+ "delay": delay,
23
+ "failureRate": failureRate
24
+ }
25
+ );
26
+ }
27
+ }
28
+
29
+ if (fs.existsSync(configFileName)) {
30
+ return {error: "CONFIG_ALREADY_EXISTS"};
31
+ }
32
+
33
+ fs.writeFileSync(configFileName, JSON.stringify(fileConfigurationJSON, null, 2));
34
+ return {success: true};
35
+
36
+ }