@schmock/core 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder.d.ts +13 -5
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +147 -60
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -11
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -17
- package/dist/types.d.ts +17 -210
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +4 -4
- package/src/builder.test.ts +2 -2
- package/src/builder.ts +232 -108
- package/src/constants.test.ts +59 -0
- package/src/constants.ts +25 -0
- package/src/errors.ts +3 -1
- package/src/index.ts +41 -29
- package/src/namespace.test.ts +3 -2
- package/src/parser.property.test.ts +495 -0
- package/src/parser.ts +2 -20
- package/src/route-matching.test.ts +1 -1
- package/src/steps/async-support.steps.ts +101 -91
- package/src/steps/basic-usage.steps.ts +49 -36
- package/src/steps/developer-experience.steps.ts +110 -94
- package/src/steps/error-handling.steps.ts +90 -66
- package/src/steps/fluent-api.steps.ts +75 -72
- package/src/steps/http-methods.steps.ts +33 -33
- package/src/steps/performance-reliability.steps.ts +52 -88
- package/src/steps/plugin-integration.steps.ts +176 -176
- package/src/steps/request-history.steps.ts +333 -0
- package/src/steps/state-concurrency.steps.ts +418 -316
- package/src/steps/stateful-workflows.steps.ts +138 -136
- package/src/types.ts +20 -259
package/dist/builder.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Generator, GlobalConfig, HttpMethod, Plugin, RequestOptions, Response, RouteConfig, RouteKey } from "./types.js";
|
|
2
1
|
/**
|
|
3
2
|
* Callable mock instance that implements the new API.
|
|
4
3
|
*
|
|
@@ -7,12 +6,21 @@ import type { Generator, GlobalConfig, HttpMethod, Plugin, RequestOptions, Respo
|
|
|
7
6
|
export declare class CallableMockInstance {
|
|
8
7
|
private globalConfig;
|
|
9
8
|
private routes;
|
|
9
|
+
private staticRoutes;
|
|
10
10
|
private plugins;
|
|
11
11
|
private logger;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
private requestHistory;
|
|
13
|
+
constructor(globalConfig?: Schmock.GlobalConfig);
|
|
14
|
+
defineRoute(route: Schmock.RouteKey, generator: Schmock.Generator, config: Schmock.RouteConfig): this;
|
|
15
|
+
pipe(plugin: Schmock.Plugin): this;
|
|
16
|
+
history(method?: Schmock.HttpMethod, path?: string): Schmock.RequestRecord[];
|
|
17
|
+
called(method?: Schmock.HttpMethod, path?: string): boolean;
|
|
18
|
+
callCount(method?: Schmock.HttpMethod, path?: string): number;
|
|
19
|
+
lastRequest(method?: Schmock.HttpMethod, path?: string): Schmock.RequestRecord | undefined;
|
|
20
|
+
reset(): void;
|
|
21
|
+
resetHistory(): void;
|
|
22
|
+
resetState(): void;
|
|
23
|
+
handle(method: Schmock.HttpMethod, path: string, options?: Schmock.RequestOptions): Promise<Schmock.Response>;
|
|
16
24
|
/**
|
|
17
25
|
* Apply configured response delay
|
|
18
26
|
* Supports both fixed delays and random delays within a range
|
package/dist/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAyEA;;;;GAIG;AACH,qBAAa,oBAAoB;IAOnB,OAAO,CAAC,YAAY;IANhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,cAAc,CAA+B;gBAEjC,YAAY,GAAE,OAAO,CAAC,YAAiB;IAa3D,WAAW,CACT,KAAK,EAAE,OAAO,CAAC,QAAQ,EACvB,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,EAAE,OAAO,CAAC,WAAW,GAC1B,IAAI;IAqFP,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;IAiBlC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE;IAS5E,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO;IAS3D,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAS7D,WAAW,CACT,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAC3B,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,GAAG,SAAS;IAYpC,KAAK,IAAI,IAAI;IAab,YAAY,IAAI,IAAI;IAKpB,UAAU,IAAI,IAAI;IASZ,MAAM,CACV,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,GAC/B,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;IAoL5B;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAwDrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAqG/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA0BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
|
package/dist/builder.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors.js";
|
|
2
2
|
import { parseRouteKey } from "./parser.js";
|
|
3
|
+
function errorMessage(error) {
|
|
4
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
5
|
+
}
|
|
3
6
|
/**
|
|
4
7
|
* Debug logger that respects debug mode configuration
|
|
5
8
|
*/
|
|
@@ -31,6 +34,15 @@ class DebugLogger {
|
|
|
31
34
|
console.timeEnd(`[SCHMOCK] ${label}`);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
37
|
+
function isGeneratorFunction(gen) {
|
|
38
|
+
return typeof gen === "function";
|
|
39
|
+
}
|
|
40
|
+
function isResponseObject(value) {
|
|
41
|
+
return (typeof value === "object" &&
|
|
42
|
+
value !== null &&
|
|
43
|
+
"status" in value &&
|
|
44
|
+
"body" in value);
|
|
45
|
+
}
|
|
34
46
|
/**
|
|
35
47
|
* Callable mock instance that implements the new API.
|
|
36
48
|
*
|
|
@@ -39,8 +51,10 @@ class DebugLogger {
|
|
|
39
51
|
export class CallableMockInstance {
|
|
40
52
|
globalConfig;
|
|
41
53
|
routes = [];
|
|
54
|
+
staticRoutes = new Map();
|
|
42
55
|
plugins = [];
|
|
43
56
|
logger;
|
|
57
|
+
requestHistory = [];
|
|
44
58
|
constructor(globalConfig = {}) {
|
|
45
59
|
this.globalConfig = globalConfig;
|
|
46
60
|
this.logger = new DebugLogger(globalConfig.debug || false);
|
|
@@ -88,6 +102,11 @@ export class CallableMockInstance {
|
|
|
88
102
|
}
|
|
89
103
|
// Parse the route key to create pattern and extract parameters
|
|
90
104
|
const parsed = parseRouteKey(route);
|
|
105
|
+
// Check for duplicate routes
|
|
106
|
+
const existing = this.routes.find((r) => r.method === parsed.method && r.path === parsed.path);
|
|
107
|
+
if (existing) {
|
|
108
|
+
this.logger.log("warning", `Duplicate route: ${route} — first registration wins`);
|
|
109
|
+
}
|
|
91
110
|
// Compile the route
|
|
92
111
|
const compiledRoute = {
|
|
93
112
|
pattern: parsed.pattern,
|
|
@@ -98,6 +117,17 @@ export class CallableMockInstance {
|
|
|
98
117
|
config,
|
|
99
118
|
};
|
|
100
119
|
this.routes.push(compiledRoute);
|
|
120
|
+
// Store static routes (no params) in Map for O(1) lookup
|
|
121
|
+
// Only store the first registration — "first registration wins" semantics
|
|
122
|
+
if (parsed.params.length === 0) {
|
|
123
|
+
const normalizedPath = parsed.path.endsWith("/") && parsed.path !== "/"
|
|
124
|
+
? parsed.path.slice(0, -1)
|
|
125
|
+
: parsed.path;
|
|
126
|
+
const key = `${parsed.method} ${normalizedPath}`;
|
|
127
|
+
if (!this.staticRoutes.has(key)) {
|
|
128
|
+
this.staticRoutes.set(key, compiledRoute);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
101
131
|
this.logger.log("route", `Route defined: ${route}`, {
|
|
102
132
|
contentType: config.contentType,
|
|
103
133
|
generatorType: typeof generator,
|
|
@@ -115,8 +145,59 @@ export class CallableMockInstance {
|
|
|
115
145
|
});
|
|
116
146
|
return this;
|
|
117
147
|
}
|
|
148
|
+
// ===== Request Spy / History API =====
|
|
149
|
+
history(method, path) {
|
|
150
|
+
if (method && path) {
|
|
151
|
+
return this.requestHistory.filter((r) => r.method === method && r.path === path);
|
|
152
|
+
}
|
|
153
|
+
return [...this.requestHistory];
|
|
154
|
+
}
|
|
155
|
+
called(method, path) {
|
|
156
|
+
if (method && path) {
|
|
157
|
+
return this.requestHistory.some((r) => r.method === method && r.path === path);
|
|
158
|
+
}
|
|
159
|
+
return this.requestHistory.length > 0;
|
|
160
|
+
}
|
|
161
|
+
callCount(method, path) {
|
|
162
|
+
if (method && path) {
|
|
163
|
+
return this.requestHistory.filter((r) => r.method === method && r.path === path).length;
|
|
164
|
+
}
|
|
165
|
+
return this.requestHistory.length;
|
|
166
|
+
}
|
|
167
|
+
lastRequest(method, path) {
|
|
168
|
+
if (method && path) {
|
|
169
|
+
const filtered = this.requestHistory.filter((r) => r.method === method && r.path === path);
|
|
170
|
+
return filtered[filtered.length - 1];
|
|
171
|
+
}
|
|
172
|
+
return this.requestHistory[this.requestHistory.length - 1];
|
|
173
|
+
}
|
|
174
|
+
// ===== Reset / Lifecycle =====
|
|
175
|
+
reset() {
|
|
176
|
+
this.routes = [];
|
|
177
|
+
this.staticRoutes.clear();
|
|
178
|
+
this.plugins = [];
|
|
179
|
+
this.requestHistory = [];
|
|
180
|
+
if (this.globalConfig.state) {
|
|
181
|
+
for (const key of Object.keys(this.globalConfig.state)) {
|
|
182
|
+
delete this.globalConfig.state[key];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
this.logger.log("lifecycle", "Mock fully reset");
|
|
186
|
+
}
|
|
187
|
+
resetHistory() {
|
|
188
|
+
this.requestHistory = [];
|
|
189
|
+
this.logger.log("lifecycle", "Request history cleared");
|
|
190
|
+
}
|
|
191
|
+
resetState() {
|
|
192
|
+
if (this.globalConfig.state) {
|
|
193
|
+
for (const key of Object.keys(this.globalConfig.state)) {
|
|
194
|
+
delete this.globalConfig.state[key];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this.logger.log("lifecycle", "State cleared");
|
|
198
|
+
}
|
|
118
199
|
async handle(method, path, options) {
|
|
119
|
-
const requestId =
|
|
200
|
+
const requestId = crypto.randomUUID();
|
|
120
201
|
this.logger.log("request", `[${requestId}] ${method} ${path}`, {
|
|
121
202
|
headers: options?.headers,
|
|
122
203
|
query: options?.query,
|
|
@@ -126,40 +207,30 @@ export class CallableMockInstance {
|
|
|
126
207
|
try {
|
|
127
208
|
// Apply namespace if configured
|
|
128
209
|
let requestPath = path;
|
|
129
|
-
if (this.globalConfig.namespace) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const response = {
|
|
150
|
-
status: 404,
|
|
151
|
-
body: { error: error.message, code: error.code },
|
|
152
|
-
headers: {},
|
|
153
|
-
};
|
|
154
|
-
this.logger.timeEnd(`request-${requestId}`);
|
|
155
|
-
return response;
|
|
156
|
-
}
|
|
157
|
-
// Remove namespace prefix, ensuring we always start with /
|
|
158
|
-
requestPath = normalizedPath.substring(finalNamespace.length);
|
|
159
|
-
if (!requestPath.startsWith("/")) {
|
|
160
|
-
requestPath = `/${requestPath}`;
|
|
161
|
-
}
|
|
210
|
+
if (this.globalConfig.namespace && this.globalConfig.namespace !== "/") {
|
|
211
|
+
const namespace = this.globalConfig.namespace.startsWith("/")
|
|
212
|
+
? this.globalConfig.namespace
|
|
213
|
+
: `/${this.globalConfig.namespace}`;
|
|
214
|
+
const pathToCheck = path.startsWith("/") ? path : `/${path}`;
|
|
215
|
+
// Check if path starts with namespace
|
|
216
|
+
// handle both "/api/users" (starts with /api) and "/api" (exact match)
|
|
217
|
+
// but NOT "/apiv2" (prefix match but wrong segment)
|
|
218
|
+
const isMatch = pathToCheck === namespace ||
|
|
219
|
+
pathToCheck.startsWith(namespace.endsWith("/") ? namespace : `${namespace}/`);
|
|
220
|
+
if (!isMatch) {
|
|
221
|
+
this.logger.log("route", `[${requestId}] Path doesn't match namespace ${namespace}`);
|
|
222
|
+
const error = new RouteNotFoundError(method, path);
|
|
223
|
+
const response = {
|
|
224
|
+
status: 404,
|
|
225
|
+
body: { error: error.message, code: error.code },
|
|
226
|
+
headers: {},
|
|
227
|
+
};
|
|
228
|
+
this.logger.timeEnd(`request-${requestId}`);
|
|
229
|
+
return response;
|
|
162
230
|
}
|
|
231
|
+
// Remove namespace prefix, ensuring we always start with /
|
|
232
|
+
const stripped = pathToCheck.slice(namespace.length);
|
|
233
|
+
requestPath = stripped.startsWith("/") ? stripped : `/${stripped}`;
|
|
163
234
|
}
|
|
164
235
|
// Find matching route
|
|
165
236
|
const matchedRoute = this.findRoute(method, requestPath);
|
|
@@ -188,7 +259,7 @@ export class CallableMockInstance {
|
|
|
188
259
|
state: this.globalConfig.state || {},
|
|
189
260
|
};
|
|
190
261
|
let result;
|
|
191
|
-
if (
|
|
262
|
+
if (isGeneratorFunction(matchedRoute.generator)) {
|
|
192
263
|
result = await matchedRoute.generator(context);
|
|
193
264
|
}
|
|
194
265
|
else {
|
|
@@ -213,13 +284,24 @@ export class CallableMockInstance {
|
|
|
213
284
|
result = pipelineResult.response;
|
|
214
285
|
}
|
|
215
286
|
catch (error) {
|
|
216
|
-
this.logger.log("error", `[${requestId}] Plugin pipeline error: ${error
|
|
287
|
+
this.logger.log("error", `[${requestId}] Plugin pipeline error: ${errorMessage(error)}`);
|
|
217
288
|
throw error;
|
|
218
289
|
}
|
|
219
290
|
// Parse and prepare response
|
|
220
291
|
const response = this.parseResponse(result, matchedRoute.config);
|
|
221
292
|
// Apply global delay if configured
|
|
222
293
|
await this.applyDelay();
|
|
294
|
+
// Record request in history
|
|
295
|
+
this.requestHistory.push({
|
|
296
|
+
method,
|
|
297
|
+
path: requestPath,
|
|
298
|
+
params,
|
|
299
|
+
query: options?.query || {},
|
|
300
|
+
headers: options?.headers || {},
|
|
301
|
+
body: options?.body,
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
response: { status: response.status, body: response.body },
|
|
304
|
+
});
|
|
223
305
|
// Log successful response
|
|
224
306
|
this.logger.log("response", `[${requestId}] Sending response ${response.status}`, {
|
|
225
307
|
status: response.status,
|
|
@@ -230,15 +312,13 @@ export class CallableMockInstance {
|
|
|
230
312
|
return response;
|
|
231
313
|
}
|
|
232
314
|
catch (error) {
|
|
233
|
-
this.logger.log("error", `[${requestId}] Error processing request: ${error
|
|
315
|
+
this.logger.log("error", `[${requestId}] Error processing request: ${errorMessage(error)}`, error);
|
|
234
316
|
// Return error response
|
|
235
317
|
const errorResponse = {
|
|
236
318
|
status: 500,
|
|
237
319
|
body: {
|
|
238
|
-
error: error
|
|
239
|
-
code: error instanceof SchmockError
|
|
240
|
-
? error.code
|
|
241
|
-
: "INTERNAL_ERROR",
|
|
320
|
+
error: errorMessage(error),
|
|
321
|
+
code: error instanceof SchmockError ? error.code : "INTERNAL_ERROR",
|
|
242
322
|
},
|
|
243
323
|
headers: {},
|
|
244
324
|
};
|
|
@@ -279,10 +359,7 @@ export class CallableMockInstance {
|
|
|
279
359
|
let headers = {};
|
|
280
360
|
let tupleFormat = false;
|
|
281
361
|
// Handle already-formed response objects (from plugin error recovery)
|
|
282
|
-
if (result
|
|
283
|
-
typeof result === "object" &&
|
|
284
|
-
"status" in result &&
|
|
285
|
-
"body" in result) {
|
|
362
|
+
if (isResponseObject(result)) {
|
|
286
363
|
return {
|
|
287
364
|
status: result.status,
|
|
288
365
|
body: result.body,
|
|
@@ -357,26 +434,38 @@ export class CallableMockInstance {
|
|
|
357
434
|
}
|
|
358
435
|
}
|
|
359
436
|
catch (error) {
|
|
360
|
-
this.logger.log("pipeline", `Plugin ${plugin.name} failed: ${error
|
|
437
|
+
this.logger.log("pipeline", `Plugin ${plugin.name} failed: ${errorMessage(error)}`);
|
|
361
438
|
// Try error handling if plugin has onError hook
|
|
362
439
|
if (plugin.onError) {
|
|
363
440
|
try {
|
|
364
|
-
const
|
|
441
|
+
const pluginError = error instanceof Error ? error : new Error(errorMessage(error));
|
|
442
|
+
const errorResult = await plugin.onError(pluginError, currentContext);
|
|
365
443
|
if (errorResult) {
|
|
366
444
|
this.logger.log("pipeline", `Plugin ${plugin.name} handled error`);
|
|
367
|
-
//
|
|
368
|
-
if (
|
|
369
|
-
|
|
445
|
+
// Error return → transform the thrown error
|
|
446
|
+
if (errorResult instanceof Error) {
|
|
447
|
+
throw new PluginError(plugin.name, errorResult);
|
|
448
|
+
}
|
|
449
|
+
// ResponseResult return → recover, stop pipeline
|
|
450
|
+
if (typeof errorResult === "object" &&
|
|
451
|
+
errorResult !== null &&
|
|
452
|
+
"status" in errorResult) {
|
|
370
453
|
response = errorResult;
|
|
371
454
|
break;
|
|
372
455
|
}
|
|
373
456
|
}
|
|
457
|
+
// void/falsy return → propagate original error below
|
|
374
458
|
}
|
|
375
459
|
catch (hookError) {
|
|
376
|
-
|
|
460
|
+
// If the hook itself threw (including our PluginError above), re-throw it
|
|
461
|
+
if (hookError instanceof PluginError) {
|
|
462
|
+
throw hookError;
|
|
463
|
+
}
|
|
464
|
+
this.logger.log("pipeline", `Plugin ${plugin.name} error handler failed: ${errorMessage(hookError)}`);
|
|
377
465
|
}
|
|
378
466
|
}
|
|
379
|
-
|
|
467
|
+
const cause = error instanceof Error ? error : new Error(errorMessage(error));
|
|
468
|
+
throw new PluginError(plugin.name, cause);
|
|
380
469
|
}
|
|
381
470
|
}
|
|
382
471
|
return { context: currentContext, response };
|
|
@@ -391,15 +480,13 @@ export class CallableMockInstance {
|
|
|
391
480
|
* @private
|
|
392
481
|
*/
|
|
393
482
|
findRoute(method, path) {
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return route;
|
|
400
|
-
}
|
|
483
|
+
// O(1) lookup for static routes
|
|
484
|
+
const normalizedPath = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
|
|
485
|
+
const staticMatch = this.staticRoutes.get(`${method} ${normalizedPath}`);
|
|
486
|
+
if (staticMatch) {
|
|
487
|
+
return staticMatch;
|
|
401
488
|
}
|
|
402
|
-
//
|
|
489
|
+
// Fall through to parameterized route scan
|
|
403
490
|
for (const route of this.routes) {
|
|
404
491
|
if (route.method === method &&
|
|
405
492
|
route.params.length > 0 &&
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { HttpMethod } from "./types.js";
|
|
2
|
+
export declare const ROUTE_NOT_FOUND_CODE: "ROUTE_NOT_FOUND";
|
|
3
|
+
export declare const HTTP_METHODS: readonly HttpMethod[];
|
|
4
|
+
export declare function isHttpMethod(method: string): method is HttpMethod;
|
|
5
|
+
export declare function toHttpMethod(method: string): HttpMethod;
|
|
6
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAG,iBAA0B,CAAC;AAE/D,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAQpC,CAAC;AAEX,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEjE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAMvD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const ROUTE_NOT_FOUND_CODE = "ROUTE_NOT_FOUND";
|
|
2
|
+
export const HTTP_METHODS = [
|
|
3
|
+
"GET",
|
|
4
|
+
"POST",
|
|
5
|
+
"PUT",
|
|
6
|
+
"DELETE",
|
|
7
|
+
"PATCH",
|
|
8
|
+
"HEAD",
|
|
9
|
+
"OPTIONS",
|
|
10
|
+
];
|
|
11
|
+
export function isHttpMethod(method) {
|
|
12
|
+
return HTTP_METHODS.includes(method);
|
|
13
|
+
}
|
|
14
|
+
export function toHttpMethod(method) {
|
|
15
|
+
const upper = method.toUpperCase();
|
|
16
|
+
if (!isHttpMethod(upper)) {
|
|
17
|
+
throw new Error(`Invalid HTTP method: "${method}"`);
|
|
18
|
+
}
|
|
19
|
+
return upper;
|
|
20
|
+
}
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,IAAI,EAAE,MAAM;aACZ,OAAO,CAAC,EAAE,OAAO;gBAFjC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,YAAA;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,IAAI,EAAE,MAAM;aACZ,OAAO,CAAC,EAAE,OAAO;gBAFjC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,YAAA;CAQpC;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;gBACtC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAOzC;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,YAAY;gBACnC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAQ7C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,YAAY;gBAC3C,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;CAQxC;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,YAAY;gBAC/B,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;CAO7C;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAQ7C;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;gBACzC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAQnE;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;gBACzC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,OAAO;CAQ1D;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;gBACtC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAQ7D"}
|
package/dist/errors.js
CHANGED
|
@@ -9,7 +9,9 @@ export class SchmockError extends Error {
|
|
|
9
9
|
this.code = code;
|
|
10
10
|
this.context = context;
|
|
11
11
|
this.name = "SchmockError";
|
|
12
|
-
Error.captureStackTrace
|
|
12
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
13
|
+
Error.captureStackTrace(this, this.constructor);
|
|
14
|
+
}
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CallableMockInstance, GlobalConfig } from "./types.js";
|
|
2
1
|
/**
|
|
3
2
|
* Create a new Schmock mock instance with callable API.
|
|
4
3
|
*
|
|
@@ -22,7 +21,8 @@ import type { CallableMockInstance, GlobalConfig } from "./types.js";
|
|
|
22
21
|
* @param config Optional global configuration
|
|
23
22
|
* @returns A callable mock instance
|
|
24
23
|
*/
|
|
25
|
-
export declare function schmock(config?: GlobalConfig): CallableMockInstance;
|
|
24
|
+
export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
|
|
25
|
+
export { HTTP_METHODS, isHttpMethod, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
26
26
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
|
27
|
-
export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, Response, ResponseResult, RouteConfig, RouteKey, } from "./types.js";
|
|
27
|
+
export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, RequestRecord, Response, ResponseBody, ResponseResult, RouteConfig, RouteKey, StaticData, } from "./types.js";
|
|
28
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,CA+B9B;AAGD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CallableMockInstance
|
|
1
|
+
import { CallableMockInstance } from "./builder.js";
|
|
2
2
|
/**
|
|
3
3
|
* Create a new Schmock mock instance with callable API.
|
|
4
4
|
*
|
|
@@ -24,19 +24,28 @@ import { CallableMockInstance as CallableMockInstanceImpl } from "./builder.js";
|
|
|
24
24
|
*/
|
|
25
25
|
export function schmock(config) {
|
|
26
26
|
// Always use new callable API
|
|
27
|
-
const instance = new
|
|
28
|
-
//
|
|
29
|
-
const callableInstance = ((route, generator, routeConfig = {}) => {
|
|
27
|
+
const instance = new CallableMockInstance(config || {});
|
|
28
|
+
// Callable proxy: a function with attached methods
|
|
29
|
+
const callableInstance = Object.assign((route, generator, routeConfig = {}) => {
|
|
30
30
|
instance.defineRoute(route, generator, routeConfig);
|
|
31
|
-
return callableInstance;
|
|
31
|
+
return callableInstance;
|
|
32
|
+
}, {
|
|
33
|
+
pipe: (plugin) => {
|
|
34
|
+
instance.pipe(plugin);
|
|
35
|
+
return callableInstance;
|
|
36
|
+
},
|
|
37
|
+
handle: instance.handle.bind(instance),
|
|
38
|
+
history: instance.history.bind(instance),
|
|
39
|
+
called: instance.called.bind(instance),
|
|
40
|
+
callCount: instance.callCount.bind(instance),
|
|
41
|
+
lastRequest: instance.lastRequest.bind(instance),
|
|
42
|
+
reset: instance.reset.bind(instance),
|
|
43
|
+
resetHistory: instance.resetHistory.bind(instance),
|
|
44
|
+
resetState: instance.resetState.bind(instance),
|
|
32
45
|
});
|
|
33
|
-
// Manually bind all instance methods to the callable function with proper return values
|
|
34
|
-
callableInstance.pipe = (plugin) => {
|
|
35
|
-
instance.pipe(plugin);
|
|
36
|
-
return callableInstance; // Return callable function for chaining
|
|
37
|
-
};
|
|
38
|
-
callableInstance.handle = instance.handle.bind(instance);
|
|
39
46
|
return callableInstance;
|
|
40
47
|
}
|
|
48
|
+
// Re-export constants and utilities
|
|
49
|
+
export { HTTP_METHODS, isHttpMethod, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
41
50
|
// Re-export errors
|
|
42
51
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAuC3D"}
|
package/dist/parser.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
+
import { toHttpMethod } from "./constants.js";
|
|
1
2
|
import { RouteParseError } from "./errors.js";
|
|
2
|
-
const HTTP_METHODS = [
|
|
3
|
-
"GET",
|
|
4
|
-
"POST",
|
|
5
|
-
"PUT",
|
|
6
|
-
"DELETE",
|
|
7
|
-
"PATCH",
|
|
8
|
-
"HEAD",
|
|
9
|
-
"OPTIONS",
|
|
10
|
-
];
|
|
11
|
-
function isHttpMethod(method) {
|
|
12
|
-
return HTTP_METHODS.includes(method);
|
|
13
|
-
}
|
|
14
3
|
/**
|
|
15
4
|
* Parse 'METHOD /path' route key format
|
|
16
5
|
*
|
|
@@ -43,12 +32,8 @@ export function parseRouteKey(routeKey) {
|
|
|
43
32
|
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // Escape special regex chars except :
|
|
44
33
|
.replace(/:([^/]+)/g, "([^/]+)"); // Replace :param with capture group
|
|
45
34
|
const pattern = new RegExp(`^${regexPath}$`);
|
|
46
|
-
// The regex guarantees method is valid, but we use the type guard for type safety
|
|
47
|
-
if (!isHttpMethod(method)) {
|
|
48
|
-
throw new RouteParseError(routeKey, `Invalid HTTP method: ${method}`);
|
|
49
|
-
}
|
|
50
35
|
return {
|
|
51
|
-
method,
|
|
36
|
+
method: toHttpMethod(method),
|
|
52
37
|
path,
|
|
53
38
|
pattern,
|
|
54
39
|
params,
|