@zahid15/mockline 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,70 @@
1
+ # Architecture notes
2
+
3
+ Mockline has a small dependency surface and keeps request handling in Node's
4
+ built-in `node:http` server. The main path is:
5
+
6
+ ```text
7
+ OpenAPI file
8
+ ├─ validate/dereference + warnings (src/spec/load.ts)
9
+ ├─ compile method/path table (src/router.ts)
10
+ └─ create seeded in-memory stores (src/state/)
11
+
12
+ HTTP request
13
+ ├─ parse bounded body and apply CORS (src/server.ts)
14
+ ├─ serve inspector, short-circuit preflight, match route, or proxy
15
+ ├─ check declared credentials and validate the request (src/validate.ts)
16
+ ├─ select override/state/example/schema response (src/select.ts)
17
+ ├─ optionally delay, serialize, and write response safely
18
+ └─ record redacted headers, result, validation issues, and duration
19
+ ```
20
+
21
+ ## Runtime ownership
22
+
23
+ Each `createMockServer()` call owns its HTTP listener, router/spec references,
24
+ state store, deterministic counters, request recorder, proxy-example writer, and
25
+ optional file watcher. There is no shared singleton state between server
26
+ instances. The returned `close()` stops watching, closes the listener, and
27
+ flushes queued file writes; `reset()` clears state and response counters without
28
+ clearing recorded request history.
29
+
30
+ The CLI is a thin adapter for config loading, option validation, log setup,
31
+ shutdown signals, and the `init`, `routes`, and `validate` commands. The package
32
+ entry point exports server/config/spec/router/generation/validation/state
33
+ primitives for programmatic use.
34
+
35
+ ## Reload behavior
36
+
37
+ `--watch` watches the spec and config file (or the conventional config names).
38
+ Changes are debounced. A candidate spec or config is parsed and validated before
39
+ its reference is swapped into the live server. Invalid changes are logged and
40
+ the last good version stays active. State and request/sequence counters are
41
+ reset when the spec changes; state is reset when seed/state/generation settings
42
+ change. Host and port remain those of the original listener and cannot be hot
43
+ changed.
44
+
45
+ ## Safety boundaries
46
+
47
+ - Default bind is loopback, and the inspector has no authentication.
48
+ - Request bodies are streamed with a byte limit; HTTP request/header timeouts
49
+ are configurable.
50
+ - Proxying checks resolved addresses and blocks local/private destinations by
51
+ default; an opt-in is needed for local upstreams. It is a best-effort SSRF
52
+ guard, not a replacement for network egress controls (DNS rebinding is not
53
+ pinned away).
54
+ - Hop-by-hop headers are removed and redirects are not followed by the proxy.
55
+ - Common credential headers are redacted from request records; request bodies
56
+ and proxied example response bodies are not automatically redacted.
57
+ - This is a development/testing tool, not a hardened production API server or
58
+ a replacement for network egress controls.
59
+
60
+ ## Known scope limits
61
+
62
+ - Swagger 2.0 is rejected; use OpenAPI 3.0 or 3.1.
63
+ - Mockline serves HTTP operations, not OpenAPI webhooks or callbacks. It reports
64
+ unsupported/documentation-only features through validation warnings.
65
+ - Stateful mode only detects conventional list/create/item REST shapes; it is
66
+ not a general workflow engine or persistent database.
67
+ - Binary/non-JSON responses require explicit examples; Mockline does not
68
+ synthesize files or media streams from schemas.
69
+ - Authentication checks presence only when enabled; it does not verify tokens
70
+ or authorization policy.
@@ -0,0 +1,31 @@
1
+ # Response behavior
2
+
3
+ For a matched operation, selection follows this order:
4
+
5
+ | Priority | Source | Behavior |
6
+ | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
7
+ | 1 | Control headers | Status, named example, or forced failure selects the requested response. A delay header only changes timing. |
8
+ | 2 | Config overrides | Operation ID wins over METHOD /path; first matching rule, then a sequence or fixed response. |
9
+ | 3 | In-memory state | Detected collections perform CRUD only when a state response is selected. |
10
+ | 4 | Spec examples | First named example, media example, then schema example(s). |
11
+ | 5 | Schema generation | Deterministic generation for JSON media; unsupported media without examples returns 501. |
12
+
13
+ Default status is the lowest declared 2xx, then default (served as 200), then 200. Explicit status controls may request a status not declared in the spec;
14
+ without a matching response or default, its body is empty. Control headers can
15
+ be disabled with controlHeaders.allow: false.
16
+
17
+ The configured failure rate is evaluated per operation/request counter after
18
+ selection; it can replace any selected response. X-Mockline-Error forces the
19
+ first configured error status. Failure randomness advances even when the data
20
+ generation counter is disabled. Delay precedence is control header, override,
21
+ then global latency. Sequences loop only with loop: true; otherwise they stay
22
+ at the last entry. reset() resets sequences, counters and collection seed data.
23
+
24
+ Accept selects declared media by quality value and wildcard matching. When no
25
+ declared media match, the response is 406. Overrides and stateful responses are
26
+ also subject to negotiation. Override bodies and examples are trusted and are
27
+ not schema-validated; generated JSON values are validated before serving.
28
+
29
+ Unknown paths return 404 and wrong methods return 405 with Allow. Both use
30
+ application/problem+json and include the nearest known path. Strict validation
31
+ runs before selection and can reject a request regardless of control headers.
@@ -0,0 +1,47 @@
1
+ # Performance notes
2
+
3
+ Mockline includes a dependency-free loopback smoke benchmark:
4
+
5
+ ```sh
6
+ corepack pnpm benchmark -- --duration 5 --concurrency 10
7
+ ```
8
+
9
+ It starts one server with `examples/petstore/openapi.yaml`, disables request
10
+ recording/validation/inspector overhead, then issues concurrent Node `fetch`
11
+ requests to `GET /v1/pets` for the requested duration. It consumes each response
12
+ body and reports successful responses per second, sampled p50/p95 request
13
+ latency, status counts, failures, Node version, and platform. Defaults are five
14
+ seconds and ten workers; duration is in seconds and concurrency must be a
15
+ positive integer.
16
+
17
+ ## Local review runs
18
+
19
+ Measured on 2026-10-11 with Node 24.15.0, Windows x64, Intel Core i7-8665U
20
+ (1.90 GHz), and 15.8 GiB RAM. Two seconds, five concurrent clients, no warm-up,
21
+ validation/recording/inspector disabled, seed 42:
22
+
23
+ | Response | Requests | Throughput | p50 | p95 | Failures |
24
+ | --------- | -------: | ------------: | -------: | -------: | -------: |
25
+ | Example | 4,044 | 2,020.1 req/s | 1.87 ms | 5.06 ms | 0 |
26
+ | Generated | 209 | 103.6 req/s | 45.74 ms | 60.06 ms | 0 |
27
+
28
+ Both runs returned only HTTP 200. Generated responses include schema validation
29
+ on every request, which adds overhead. These short local runs are smoke checks,
30
+ not capacity estimates. Add `--generate` to benchmark generated responses.
31
+
32
+ ## Original build report sample run
33
+
34
+ One 5-second run in the Arena sandbox on 2026-10-10, using Node.js 20.20.2 on
35
+ Linux x64:
36
+
37
+ | Duration | Concurrency | Requests | Throughput | p50 | p95 | Failures |
38
+ | -------- | ----------: | -------: | ------------: | ------: | ------: | -------: |
39
+ | 5.00 s | 10 | 12,445 | 2,487.9 req/s | 3.05 ms | 7.65 ms | 0 |
40
+
41
+ All responses were HTTP 200. This is a single shared-container smoke result,
42
+ not a stable performance claim or a production capacity estimate. It has no
43
+ warm-up phase, generates no large payloads, and does not model TLS, external
44
+ networks, real upstreams, request recording, load balancing, or contention.
45
+ Repeat it on your own target hardware, report multiple runs and configuration,
46
+ and use a purpose-built load-testing tool for capacity planning. The benchmark
47
+ script is intentionally small and adds no runtime dependency.
@@ -0,0 +1,508 @@
1
+ {
2
+ "$ref": "#/definitions/MocklineConfig",
3
+ "definitions": {
4
+ "MocklineConfig": {
5
+ "type": "object",
6
+ "properties": {
7
+ "port": {
8
+ "type": "integer",
9
+ "minimum": 0,
10
+ "maximum": 65535,
11
+ "default": 4010
12
+ },
13
+ "host": {
14
+ "type": "string",
15
+ "minLength": 1,
16
+ "default": "127.0.0.1"
17
+ },
18
+ "seed": {
19
+ "type": "integer"
20
+ },
21
+ "prefer": {
22
+ "type": "string",
23
+ "enum": [
24
+ "example",
25
+ "generate"
26
+ ],
27
+ "default": "example"
28
+ },
29
+ "latency": {
30
+ "anyOf": [
31
+ {
32
+ "type": "number",
33
+ "minimum": 0
34
+ },
35
+ {
36
+ "type": "array",
37
+ "minItems": 2,
38
+ "maxItems": 2,
39
+ "items": [
40
+ {
41
+ "type": "number",
42
+ "minimum": 0
43
+ },
44
+ {
45
+ "type": "number",
46
+ "minimum": 0
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ "type": "object",
52
+ "properties": {
53
+ "fixed": {
54
+ "type": "number",
55
+ "minimum": 0
56
+ },
57
+ "min": {
58
+ "type": "number",
59
+ "minimum": 0
60
+ },
61
+ "max": {
62
+ "type": "number",
63
+ "minimum": 0
64
+ },
65
+ "distribution": {
66
+ "type": "string",
67
+ "enum": [
68
+ "uniform",
69
+ "normal"
70
+ ],
71
+ "default": "uniform"
72
+ }
73
+ },
74
+ "additionalProperties": false
75
+ }
76
+ ],
77
+ "default": 0
78
+ },
79
+ "errors": {
80
+ "type": "object",
81
+ "properties": {
82
+ "rate": {
83
+ "type": "number",
84
+ "minimum": 0,
85
+ "maximum": 1,
86
+ "default": 0
87
+ },
88
+ "statuses": {
89
+ "type": "array",
90
+ "items": {
91
+ "type": "integer",
92
+ "minimum": 400,
93
+ "maximum": 599
94
+ },
95
+ "minItems": 1,
96
+ "default": [
97
+ 500,
98
+ 503
99
+ ]
100
+ },
101
+ "perOperationRates": {
102
+ "type": "object",
103
+ "additionalProperties": {
104
+ "type": "number",
105
+ "minimum": 0,
106
+ "maximum": 1
107
+ },
108
+ "default": {}
109
+ }
110
+ },
111
+ "additionalProperties": false,
112
+ "default": {}
113
+ },
114
+ "validate": {
115
+ "type": "string",
116
+ "enum": [
117
+ "off",
118
+ "warn",
119
+ "strict"
120
+ ],
121
+ "default": "warn"
122
+ },
123
+ "stateful": {
124
+ "type": "object",
125
+ "properties": {
126
+ "enabled": {
127
+ "type": "boolean",
128
+ "default": false
129
+ },
130
+ "seedItems": {
131
+ "type": "integer",
132
+ "minimum": 0,
133
+ "default": 3
134
+ },
135
+ "idProperty": {
136
+ "type": "string",
137
+ "minLength": 1,
138
+ "default": "id"
139
+ }
140
+ },
141
+ "additionalProperties": false,
142
+ "default": {}
143
+ },
144
+ "overrides": {
145
+ "type": "object",
146
+ "additionalProperties": {
147
+ "type": "object",
148
+ "properties": {
149
+ "status": {
150
+ "type": "integer",
151
+ "minimum": 100,
152
+ "maximum": 599
153
+ },
154
+ "body": {},
155
+ "headers": {
156
+ "type": "object",
157
+ "additionalProperties": {
158
+ "type": "string"
159
+ }
160
+ },
161
+ "delay": {
162
+ "type": "number",
163
+ "minimum": 0
164
+ },
165
+ "sequence": {
166
+ "type": "array",
167
+ "items": {
168
+ "type": "object",
169
+ "properties": {
170
+ "status": {
171
+ "type": "integer",
172
+ "minimum": 100,
173
+ "maximum": 599
174
+ },
175
+ "body": {},
176
+ "headers": {
177
+ "type": "object",
178
+ "additionalProperties": {
179
+ "type": "string"
180
+ }
181
+ },
182
+ "delay": {
183
+ "type": "number",
184
+ "minimum": 0
185
+ }
186
+ },
187
+ "additionalProperties": false
188
+ },
189
+ "minItems": 1
190
+ },
191
+ "loop": {
192
+ "type": "boolean",
193
+ "default": false
194
+ },
195
+ "when": {
196
+ "type": "object",
197
+ "properties": {
198
+ "location": {
199
+ "type": "string",
200
+ "enum": [
201
+ "query",
202
+ "header",
203
+ "body"
204
+ ]
205
+ },
206
+ "name": {
207
+ "type": "string",
208
+ "minLength": 1
209
+ },
210
+ "op": {
211
+ "type": "string",
212
+ "enum": [
213
+ "eq",
214
+ "contains",
215
+ "regex"
216
+ ],
217
+ "default": "eq"
218
+ },
219
+ "value": {}
220
+ },
221
+ "required": [
222
+ "location",
223
+ "name"
224
+ ],
225
+ "additionalProperties": false
226
+ },
227
+ "response": {
228
+ "type": "object",
229
+ "properties": {
230
+ "status": {
231
+ "type": "integer",
232
+ "minimum": 100,
233
+ "maximum": 599
234
+ },
235
+ "body": {},
236
+ "headers": {
237
+ "type": "object",
238
+ "additionalProperties": {
239
+ "type": "string"
240
+ }
241
+ },
242
+ "delay": {
243
+ "type": "number",
244
+ "minimum": 0
245
+ }
246
+ },
247
+ "additionalProperties": false
248
+ },
249
+ "rules": {
250
+ "type": "array",
251
+ "items": {
252
+ "type": "object",
253
+ "properties": {
254
+ "when": {
255
+ "type": "object",
256
+ "properties": {
257
+ "location": {
258
+ "type": "string",
259
+ "enum": [
260
+ "query",
261
+ "header",
262
+ "body"
263
+ ]
264
+ },
265
+ "name": {
266
+ "type": "string",
267
+ "minLength": 1
268
+ },
269
+ "op": {
270
+ "type": "string",
271
+ "enum": [
272
+ "eq",
273
+ "contains",
274
+ "regex"
275
+ ],
276
+ "default": "eq"
277
+ },
278
+ "value": {}
279
+ },
280
+ "required": [
281
+ "location",
282
+ "name"
283
+ ],
284
+ "additionalProperties": false
285
+ },
286
+ "response": {
287
+ "type": "object",
288
+ "properties": {
289
+ "status": {
290
+ "type": "integer",
291
+ "minimum": 100,
292
+ "maximum": 599
293
+ },
294
+ "body": {},
295
+ "headers": {
296
+ "type": "object",
297
+ "additionalProperties": {
298
+ "type": "string"
299
+ }
300
+ },
301
+ "delay": {
302
+ "type": "number",
303
+ "minimum": 0
304
+ }
305
+ },
306
+ "additionalProperties": false
307
+ }
308
+ },
309
+ "required": [
310
+ "when",
311
+ "response"
312
+ ],
313
+ "additionalProperties": false
314
+ }
315
+ }
316
+ },
317
+ "additionalProperties": false
318
+ },
319
+ "default": {}
320
+ },
321
+ "proxy": {
322
+ "type": "object",
323
+ "properties": {
324
+ "url": {
325
+ "type": "string",
326
+ "format": "uri"
327
+ },
328
+ "all": {
329
+ "type": "boolean",
330
+ "default": false
331
+ },
332
+ "allowPrivate": {
333
+ "type": "boolean",
334
+ "default": false
335
+ },
336
+ "timeoutMs": {
337
+ "type": "integer",
338
+ "exclusiveMinimum": 0,
339
+ "default": 5000
340
+ },
341
+ "recordFile": {
342
+ "type": "string"
343
+ }
344
+ },
345
+ "additionalProperties": false,
346
+ "default": {}
347
+ },
348
+ "cors": {
349
+ "type": "object",
350
+ "properties": {
351
+ "enabled": {
352
+ "type": "boolean",
353
+ "default": true
354
+ },
355
+ "origins": {
356
+ "type": "array",
357
+ "items": {
358
+ "type": "string"
359
+ },
360
+ "default": [
361
+ "*"
362
+ ]
363
+ }
364
+ },
365
+ "additionalProperties": false,
366
+ "default": {}
367
+ },
368
+ "recording": {
369
+ "type": "object",
370
+ "properties": {
371
+ "enabled": {
372
+ "type": "boolean",
373
+ "default": true
374
+ },
375
+ "file": {
376
+ "type": "string"
377
+ },
378
+ "maxRequests": {
379
+ "type": "integer",
380
+ "exclusiveMinimum": 0,
381
+ "default": 500
382
+ },
383
+ "redactHeaders": {
384
+ "type": "array",
385
+ "items": {
386
+ "type": "string"
387
+ },
388
+ "default": [
389
+ "authorization",
390
+ "cookie",
391
+ "set-cookie",
392
+ "x-api-key",
393
+ "api-key",
394
+ "x-auth-token"
395
+ ]
396
+ }
397
+ },
398
+ "additionalProperties": false,
399
+ "default": {}
400
+ },
401
+ "generation": {
402
+ "type": "object",
403
+ "properties": {
404
+ "hints": {
405
+ "anyOf": [
406
+ {
407
+ "type": "boolean"
408
+ },
409
+ {
410
+ "type": "object",
411
+ "additionalProperties": {
412
+ "anyOf": [
413
+ {
414
+ "type": "string"
415
+ },
416
+ {
417
+ "type": "number"
418
+ },
419
+ {
420
+ "type": "boolean"
421
+ },
422
+ {
423
+ "type": "null"
424
+ },
425
+ {
426
+ "type": "array",
427
+ "items": {
428
+ "type": [
429
+ "string",
430
+ "number",
431
+ "boolean",
432
+ "null"
433
+ ]
434
+ }
435
+ }
436
+ ]
437
+ }
438
+ }
439
+ ],
440
+ "default": true
441
+ },
442
+ "counter": {
443
+ "type": "boolean",
444
+ "default": false
445
+ },
446
+ "maxDepth": {
447
+ "type": "integer",
448
+ "minimum": 0,
449
+ "maximum": 20,
450
+ "default": 3
451
+ },
452
+ "minArrayItems": {
453
+ "type": "integer",
454
+ "minimum": 0,
455
+ "default": 1
456
+ },
457
+ "maxArrayItems": {
458
+ "type": "integer",
459
+ "minimum": 0,
460
+ "default": 3
461
+ }
462
+ },
463
+ "additionalProperties": false,
464
+ "default": {}
465
+ },
466
+ "controlHeaders": {
467
+ "type": "object",
468
+ "properties": {
469
+ "allow": {
470
+ "type": "boolean",
471
+ "default": true
472
+ }
473
+ },
474
+ "additionalProperties": false,
475
+ "default": {}
476
+ },
477
+ "inspector": {
478
+ "type": "object",
479
+ "properties": {
480
+ "enabled": {
481
+ "type": "boolean"
482
+ }
483
+ },
484
+ "additionalProperties": false,
485
+ "default": {}
486
+ },
487
+ "enforceAuth": {
488
+ "type": "boolean",
489
+ "default": false
490
+ },
491
+ "requestBodyLimit": {
492
+ "type": "integer",
493
+ "exclusiveMinimum": 0,
494
+ "default": 1048576
495
+ },
496
+ "requestTimeoutMs": {
497
+ "type": "integer",
498
+ "exclusiveMinimum": 0,
499
+ "default": 30000
500
+ }
501
+ },
502
+ "additionalProperties": false
503
+ }
504
+ },
505
+ "$schema": "http://json-schema.org/draft-07/schema#",
506
+ "title": "Mockline configuration",
507
+ "description": "Configuration accepted by Mockline CLI and library."
508
+ }