@schmock/core 1.0.4 → 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 +139 -59
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -11
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -17
- package/dist/types.d.ts +17 -214
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/builder.test.ts +2 -2
- package/src/builder.ts +216 -107
- package/src/constants.ts +1 -1
- package/src/index.ts +32 -29
- package/src/namespace.test.ts +3 -2
- package/src/parser.property.test.ts +495 -0
- package/src/parser.ts +2 -20
- 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 +95 -97
- package/src/steps/error-handling.steps.ts +71 -72
- 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 -271
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);
|
|
@@ -103,6 +117,17 @@ export class CallableMockInstance {
|
|
|
103
117
|
config,
|
|
104
118
|
};
|
|
105
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
|
+
}
|
|
106
131
|
this.logger.log("route", `Route defined: ${route}`, {
|
|
107
132
|
contentType: config.contentType,
|
|
108
133
|
generatorType: typeof generator,
|
|
@@ -120,8 +145,59 @@ export class CallableMockInstance {
|
|
|
120
145
|
});
|
|
121
146
|
return this;
|
|
122
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
|
+
}
|
|
123
199
|
async handle(method, path, options) {
|
|
124
|
-
const requestId =
|
|
200
|
+
const requestId = crypto.randomUUID();
|
|
125
201
|
this.logger.log("request", `[${requestId}] ${method} ${path}`, {
|
|
126
202
|
headers: options?.headers,
|
|
127
203
|
query: options?.query,
|
|
@@ -131,40 +207,30 @@ export class CallableMockInstance {
|
|
|
131
207
|
try {
|
|
132
208
|
// Apply namespace if configured
|
|
133
209
|
let requestPath = path;
|
|
134
|
-
if (this.globalConfig.namespace) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const response = {
|
|
155
|
-
status: 404,
|
|
156
|
-
body: { error: error.message, code: error.code },
|
|
157
|
-
headers: {},
|
|
158
|
-
};
|
|
159
|
-
this.logger.timeEnd(`request-${requestId}`);
|
|
160
|
-
return response;
|
|
161
|
-
}
|
|
162
|
-
// Remove namespace prefix, ensuring we always start with /
|
|
163
|
-
requestPath = normalizedPath.substring(finalNamespace.length);
|
|
164
|
-
if (!requestPath.startsWith("/")) {
|
|
165
|
-
requestPath = `/${requestPath}`;
|
|
166
|
-
}
|
|
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;
|
|
167
230
|
}
|
|
231
|
+
// Remove namespace prefix, ensuring we always start with /
|
|
232
|
+
const stripped = pathToCheck.slice(namespace.length);
|
|
233
|
+
requestPath = stripped.startsWith("/") ? stripped : `/${stripped}`;
|
|
168
234
|
}
|
|
169
235
|
// Find matching route
|
|
170
236
|
const matchedRoute = this.findRoute(method, requestPath);
|
|
@@ -193,7 +259,7 @@ export class CallableMockInstance {
|
|
|
193
259
|
state: this.globalConfig.state || {},
|
|
194
260
|
};
|
|
195
261
|
let result;
|
|
196
|
-
if (
|
|
262
|
+
if (isGeneratorFunction(matchedRoute.generator)) {
|
|
197
263
|
result = await matchedRoute.generator(context);
|
|
198
264
|
}
|
|
199
265
|
else {
|
|
@@ -218,13 +284,24 @@ export class CallableMockInstance {
|
|
|
218
284
|
result = pipelineResult.response;
|
|
219
285
|
}
|
|
220
286
|
catch (error) {
|
|
221
|
-
this.logger.log("error", `[${requestId}] Plugin pipeline error: ${error
|
|
287
|
+
this.logger.log("error", `[${requestId}] Plugin pipeline error: ${errorMessage(error)}`);
|
|
222
288
|
throw error;
|
|
223
289
|
}
|
|
224
290
|
// Parse and prepare response
|
|
225
291
|
const response = this.parseResponse(result, matchedRoute.config);
|
|
226
292
|
// Apply global delay if configured
|
|
227
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
|
+
});
|
|
228
305
|
// Log successful response
|
|
229
306
|
this.logger.log("response", `[${requestId}] Sending response ${response.status}`, {
|
|
230
307
|
status: response.status,
|
|
@@ -235,15 +312,13 @@ export class CallableMockInstance {
|
|
|
235
312
|
return response;
|
|
236
313
|
}
|
|
237
314
|
catch (error) {
|
|
238
|
-
this.logger.log("error", `[${requestId}] Error processing request: ${error
|
|
315
|
+
this.logger.log("error", `[${requestId}] Error processing request: ${errorMessage(error)}`, error);
|
|
239
316
|
// Return error response
|
|
240
317
|
const errorResponse = {
|
|
241
318
|
status: 500,
|
|
242
319
|
body: {
|
|
243
|
-
error: error
|
|
244
|
-
code: error instanceof SchmockError
|
|
245
|
-
? error.code
|
|
246
|
-
: "INTERNAL_ERROR",
|
|
320
|
+
error: errorMessage(error),
|
|
321
|
+
code: error instanceof SchmockError ? error.code : "INTERNAL_ERROR",
|
|
247
322
|
},
|
|
248
323
|
headers: {},
|
|
249
324
|
};
|
|
@@ -284,10 +359,7 @@ export class CallableMockInstance {
|
|
|
284
359
|
let headers = {};
|
|
285
360
|
let tupleFormat = false;
|
|
286
361
|
// Handle already-formed response objects (from plugin error recovery)
|
|
287
|
-
if (result
|
|
288
|
-
typeof result === "object" &&
|
|
289
|
-
"status" in result &&
|
|
290
|
-
"body" in result) {
|
|
362
|
+
if (isResponseObject(result)) {
|
|
291
363
|
return {
|
|
292
364
|
status: result.status,
|
|
293
365
|
body: result.body,
|
|
@@ -362,28 +434,38 @@ export class CallableMockInstance {
|
|
|
362
434
|
}
|
|
363
435
|
}
|
|
364
436
|
catch (error) {
|
|
365
|
-
this.logger.log("pipeline", `Plugin ${plugin.name} failed: ${error
|
|
437
|
+
this.logger.log("pipeline", `Plugin ${plugin.name} failed: ${errorMessage(error)}`);
|
|
366
438
|
// Try error handling if plugin has onError hook
|
|
367
439
|
if (plugin.onError) {
|
|
368
440
|
try {
|
|
369
|
-
const
|
|
441
|
+
const pluginError = error instanceof Error ? error : new Error(errorMessage(error));
|
|
442
|
+
const errorResult = await plugin.onError(pluginError, currentContext);
|
|
370
443
|
if (errorResult) {
|
|
371
444
|
this.logger.log("pipeline", `Plugin ${plugin.name} handled error`);
|
|
372
|
-
//
|
|
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
|
|
373
450
|
if (typeof errorResult === "object" &&
|
|
374
451
|
errorResult !== null &&
|
|
375
452
|
"status" in errorResult) {
|
|
376
|
-
// Return the error response as the current response, stop pipeline
|
|
377
453
|
response = errorResult;
|
|
378
454
|
break;
|
|
379
455
|
}
|
|
380
456
|
}
|
|
457
|
+
// void/falsy return → propagate original error below
|
|
381
458
|
}
|
|
382
459
|
catch (hookError) {
|
|
383
|
-
|
|
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)}`);
|
|
384
465
|
}
|
|
385
466
|
}
|
|
386
|
-
|
|
467
|
+
const cause = error instanceof Error ? error : new Error(errorMessage(error));
|
|
468
|
+
throw new PluginError(plugin.name, cause);
|
|
387
469
|
}
|
|
388
470
|
}
|
|
389
471
|
return { context: currentContext, response };
|
|
@@ -398,15 +480,13 @@ export class CallableMockInstance {
|
|
|
398
480
|
* @private
|
|
399
481
|
*/
|
|
400
482
|
findRoute(method, path) {
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return route;
|
|
407
|
-
}
|
|
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;
|
|
408
488
|
}
|
|
409
|
-
//
|
|
489
|
+
// Fall through to parameterized route scan
|
|
410
490
|
for (const route of this.routes) {
|
|
411
491
|
if (route.method === method &&
|
|
412
492
|
route.params.length > 0 &&
|
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,8 +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;
|
|
26
25
|
export { HTTP_METHODS, isHttpMethod, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
27
26
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
|
28
|
-
export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, Response, ResponseBody, ResponseResult, RouteConfig, RouteKey, StaticData, } 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";
|
|
29
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,18 +24,25 @@ 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
|
}
|
|
41
48
|
// Re-export constants and utilities
|
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,
|