@schmock/core 1.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.
Files changed (48) hide show
  1. package/dist/builder.d.ts +62 -0
  2. package/dist/builder.d.ts.map +1 -0
  3. package/dist/builder.js +432 -0
  4. package/dist/errors.d.ts +56 -0
  5. package/dist/errors.d.ts.map +1 -0
  6. package/dist/errors.js +92 -0
  7. package/dist/index.d.ts +27 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/parser.d.ts +19 -0
  11. package/dist/parser.d.ts.map +1 -0
  12. package/dist/parser.js +40 -0
  13. package/dist/types.d.ts +15 -0
  14. package/dist/types.d.ts.map +1 -0
  15. package/dist/types.js +2 -0
  16. package/package.json +39 -0
  17. package/src/builder.d.ts.map +1 -0
  18. package/src/builder.test.ts +289 -0
  19. package/src/builder.ts +580 -0
  20. package/src/debug.test.ts +241 -0
  21. package/src/delay.test.ts +319 -0
  22. package/src/errors.d.ts.map +1 -0
  23. package/src/errors.test.ts +223 -0
  24. package/src/errors.ts +124 -0
  25. package/src/factory.test.ts +133 -0
  26. package/src/index.d.ts.map +1 -0
  27. package/src/index.ts +80 -0
  28. package/src/namespace.test.ts +273 -0
  29. package/src/parser.d.ts.map +1 -0
  30. package/src/parser.test.ts +131 -0
  31. package/src/parser.ts +61 -0
  32. package/src/plugin-system.test.ts +511 -0
  33. package/src/response-parsing.test.ts +255 -0
  34. package/src/route-matching.test.ts +351 -0
  35. package/src/smart-defaults.test.ts +361 -0
  36. package/src/steps/async-support.steps.ts +427 -0
  37. package/src/steps/basic-usage.steps.ts +316 -0
  38. package/src/steps/developer-experience.steps.ts +439 -0
  39. package/src/steps/error-handling.steps.ts +387 -0
  40. package/src/steps/fluent-api.steps.ts +252 -0
  41. package/src/steps/http-methods.steps.ts +397 -0
  42. package/src/steps/performance-reliability.steps.ts +459 -0
  43. package/src/steps/plugin-integration.steps.ts +279 -0
  44. package/src/steps/route-key-format.steps.ts +118 -0
  45. package/src/steps/state-concurrency.steps.ts +643 -0
  46. package/src/steps/stateful-workflows.steps.ts +351 -0
  47. package/src/types.d.ts.map +1 -0
  48. package/src/types.ts +17 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Callable mock instance that implements the new API.
3
+ *
4
+ * @internal
5
+ */
6
+ export declare class CallableMockInstance {
7
+ private globalConfig;
8
+ private routes;
9
+ private plugins;
10
+ private logger;
11
+ constructor(globalConfig?: Schmock.GlobalConfig);
12
+ defineRoute(route: Schmock.RouteKey, generator: Schmock.Generator, config: Schmock.RouteConfig): this;
13
+ pipe(plugin: Schmock.Plugin): this;
14
+ handle(method: Schmock.HttpMethod, path: string, options?: Schmock.RequestOptions): Promise<Schmock.Response>;
15
+ /**
16
+ * Apply configured response delay
17
+ * Supports both fixed delays and random delays within a range
18
+ * @private
19
+ */
20
+ private applyDelay;
21
+ /**
22
+ * Parse and normalize response result into Response object
23
+ * Handles tuple format [status, body, headers], direct values, and response objects
24
+ * @param result - Raw result from generator or plugin
25
+ * @param routeConfig - Route configuration for content-type defaults
26
+ * @returns Normalized Response object with status, body, and headers
27
+ * @private
28
+ */
29
+ private parseResponse;
30
+ /**
31
+ * Run all registered plugins in sequence
32
+ * First plugin to set response becomes generator, subsequent plugins transform
33
+ * Handles plugin errors via onError hooks
34
+ * @param context - Plugin context with request details
35
+ * @param initialResponse - Initial response from route generator
36
+ * @param _routeConfig - Route config (unused but kept for signature)
37
+ * @param _requestId - Request ID (unused but kept for signature)
38
+ * @returns Updated context and final response after all plugins
39
+ * @private
40
+ */
41
+ private runPluginPipeline;
42
+ /**
43
+ * Find a route that matches the given method and path
44
+ * Uses two-pass matching: exact routes first, then parameterized routes
45
+ * Searches in reverse order to prefer most recently defined routes
46
+ * @param method - HTTP method to match
47
+ * @param path - Request path to match
48
+ * @returns Matched compiled route or undefined if no match
49
+ * @private
50
+ */
51
+ private findRoute;
52
+ /**
53
+ * Extract parameter values from path based on route pattern
54
+ * Maps capture groups from regex match to parameter names
55
+ * @param route - Compiled route with pattern and param names
56
+ * @param path - Request path to extract values from
57
+ * @returns Object mapping parameter names to extracted values
58
+ * @private
59
+ */
60
+ private extractParams;
61
+ }
62
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAkDA;;;;GAIG;AACH,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,YAAY;IAJhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,MAAM,CAAc;gBAER,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;IA4DP,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;IAe5B,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;IAmL5B;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA6DrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAmF/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA+BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
@@ -0,0 +1,432 @@
1
+ import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors";
2
+ import { parseRouteKey } from "./parser";
3
+ /**
4
+ * Debug logger that respects debug mode configuration
5
+ */
6
+ class DebugLogger {
7
+ enabled;
8
+ constructor(enabled = false) {
9
+ this.enabled = enabled;
10
+ }
11
+ log(category, message, data) {
12
+ if (!this.enabled)
13
+ return;
14
+ const timestamp = new Date().toISOString();
15
+ const prefix = `[${timestamp}] [SCHMOCK:${category.toUpperCase()}]`;
16
+ if (data) {
17
+ console.log(`${prefix} ${message}`, data);
18
+ }
19
+ else {
20
+ console.log(`${prefix} ${message}`);
21
+ }
22
+ }
23
+ time(label) {
24
+ if (!this.enabled)
25
+ return;
26
+ console.time(`[SCHMOCK] ${label}`);
27
+ }
28
+ timeEnd(label) {
29
+ if (!this.enabled)
30
+ return;
31
+ console.timeEnd(`[SCHMOCK] ${label}`);
32
+ }
33
+ }
34
+ /**
35
+ * Callable mock instance that implements the new API.
36
+ *
37
+ * @internal
38
+ */
39
+ export class CallableMockInstance {
40
+ globalConfig;
41
+ routes = [];
42
+ plugins = [];
43
+ logger;
44
+ constructor(globalConfig = {}) {
45
+ this.globalConfig = globalConfig;
46
+ this.logger = new DebugLogger(globalConfig.debug || false);
47
+ if (globalConfig.debug) {
48
+ this.logger.log("config", "Debug mode enabled");
49
+ }
50
+ this.logger.log("config", "Callable mock instance created", {
51
+ debug: globalConfig.debug,
52
+ namespace: globalConfig.namespace,
53
+ delay: globalConfig.delay,
54
+ });
55
+ }
56
+ // Method for defining routes (called when instance is invoked)
57
+ defineRoute(route, generator, config) {
58
+ // Auto-detect contentType if not provided
59
+ if (!config.contentType) {
60
+ if (typeof generator === "function") {
61
+ // Default to JSON for function generators
62
+ config.contentType = "application/json";
63
+ }
64
+ else if (typeof generator === "string" ||
65
+ typeof generator === "number" ||
66
+ typeof generator === "boolean") {
67
+ // Default to plain text for primitives
68
+ config.contentType = "text/plain";
69
+ }
70
+ else if (Buffer.isBuffer(generator)) {
71
+ // Default to octet-stream for buffers
72
+ config.contentType = "application/octet-stream";
73
+ }
74
+ else {
75
+ // Default to JSON for objects/arrays
76
+ config.contentType = "application/json";
77
+ }
78
+ }
79
+ // Validate generator matches contentType if it's static data
80
+ if (typeof generator !== "function" &&
81
+ config.contentType === "application/json") {
82
+ try {
83
+ JSON.stringify(generator);
84
+ }
85
+ catch (_error) {
86
+ throw new RouteDefinitionError(route, "Generator data is not valid JSON but contentType is application/json");
87
+ }
88
+ }
89
+ // Parse the route key to create pattern and extract parameters
90
+ const parsed = parseRouteKey(route);
91
+ // Compile the route
92
+ const compiledRoute = {
93
+ pattern: parsed.pattern,
94
+ params: parsed.params,
95
+ method: parsed.method,
96
+ path: parsed.path,
97
+ generator,
98
+ config,
99
+ };
100
+ this.routes.push(compiledRoute);
101
+ this.logger.log("route", `Route defined: ${route}`, {
102
+ contentType: config.contentType,
103
+ generatorType: typeof generator,
104
+ hasParams: parsed.params.length > 0,
105
+ });
106
+ return this;
107
+ }
108
+ pipe(plugin) {
109
+ this.plugins.push(plugin);
110
+ this.logger.log("plugin", `Registered plugin: ${plugin.name}@${plugin.version || "unknown"}`, {
111
+ name: plugin.name,
112
+ version: plugin.version,
113
+ hasProcess: typeof plugin.process === "function",
114
+ hasOnError: typeof plugin.onError === "function",
115
+ });
116
+ return this;
117
+ }
118
+ async handle(method, path, options) {
119
+ const requestId = Math.random().toString(36).substring(7);
120
+ this.logger.log("request", `[${requestId}] ${method} ${path}`, {
121
+ headers: options?.headers,
122
+ query: options?.query,
123
+ bodyType: options?.body ? typeof options.body : "none",
124
+ });
125
+ this.logger.time(`request-${requestId}`);
126
+ try {
127
+ // Apply namespace if configured
128
+ let requestPath = path;
129
+ if (this.globalConfig.namespace) {
130
+ // Normalize namespace to handle edge cases
131
+ const namespace = this.globalConfig.namespace;
132
+ if (namespace === "/") {
133
+ // Root namespace means no transformation needed
134
+ requestPath = path;
135
+ }
136
+ else {
137
+ // Handle namespace without leading slash by normalizing both namespace and path
138
+ const normalizedNamespace = namespace.startsWith("/")
139
+ ? namespace
140
+ : `/${namespace}`;
141
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
142
+ // Remove trailing slash from namespace unless it's root
143
+ const finalNamespace = normalizedNamespace.endsWith("/") && normalizedNamespace !== "/"
144
+ ? normalizedNamespace.slice(0, -1)
145
+ : normalizedNamespace;
146
+ if (!normalizedPath.startsWith(finalNamespace)) {
147
+ this.logger.log("route", `[${requestId}] Path doesn't match namespace ${namespace}`);
148
+ const error = new RouteNotFoundError(method, path);
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
+ }
162
+ }
163
+ }
164
+ // Find matching route
165
+ const matchedRoute = this.findRoute(method, requestPath);
166
+ if (!matchedRoute) {
167
+ this.logger.log("route", `[${requestId}] No route found for ${method} ${requestPath}`);
168
+ const error = new RouteNotFoundError(method, path);
169
+ const response = {
170
+ status: 404,
171
+ body: { error: error.message, code: error.code },
172
+ headers: {},
173
+ };
174
+ this.logger.timeEnd(`request-${requestId}`);
175
+ return response;
176
+ }
177
+ this.logger.log("route", `[${requestId}] Matched route: ${method} ${matchedRoute.path}`);
178
+ // Extract parameters from the matched route
179
+ const params = this.extractParams(matchedRoute, requestPath);
180
+ // Generate initial response from route handler
181
+ const context = {
182
+ method,
183
+ path: requestPath,
184
+ params,
185
+ query: options?.query || {},
186
+ headers: options?.headers || {},
187
+ body: options?.body,
188
+ state: this.globalConfig.state || {},
189
+ };
190
+ let result;
191
+ if (typeof matchedRoute.generator === "function") {
192
+ result = await matchedRoute.generator(context);
193
+ }
194
+ else {
195
+ result = matchedRoute.generator;
196
+ }
197
+ // Build plugin context
198
+ let pluginContext = {
199
+ path: requestPath,
200
+ route: matchedRoute.config,
201
+ method,
202
+ params,
203
+ query: options?.query || {},
204
+ headers: options?.headers || {},
205
+ body: options?.body,
206
+ state: new Map(),
207
+ routeState: this.globalConfig.state || {},
208
+ };
209
+ // Run plugin pipeline to transform the response
210
+ try {
211
+ const pipelineResult = await this.runPluginPipeline(pluginContext, result, matchedRoute.config, requestId);
212
+ pluginContext = pipelineResult.context;
213
+ result = pipelineResult.response;
214
+ }
215
+ catch (error) {
216
+ this.logger.log("error", `[${requestId}] Plugin pipeline error: ${error.message}`);
217
+ throw error;
218
+ }
219
+ // Parse and prepare response
220
+ const response = this.parseResponse(result, matchedRoute.config);
221
+ // Apply global delay if configured
222
+ await this.applyDelay();
223
+ // Log successful response
224
+ this.logger.log("response", `[${requestId}] Sending response ${response.status}`, {
225
+ status: response.status,
226
+ headers: response.headers,
227
+ bodyType: typeof response.body,
228
+ });
229
+ this.logger.timeEnd(`request-${requestId}`);
230
+ return response;
231
+ }
232
+ catch (error) {
233
+ this.logger.log("error", `[${requestId}] Error processing request: ${error.message}`, error);
234
+ // Return error response
235
+ const errorResponse = {
236
+ status: 500,
237
+ body: {
238
+ error: error.message,
239
+ code: error instanceof SchmockError
240
+ ? error.code
241
+ : "INTERNAL_ERROR",
242
+ },
243
+ headers: {},
244
+ };
245
+ // Apply global delay if configured (even for error responses)
246
+ await this.applyDelay();
247
+ this.logger.log("error", `[${requestId}] Returning error response 500`);
248
+ this.logger.timeEnd(`request-${requestId}`);
249
+ return errorResponse;
250
+ }
251
+ }
252
+ /**
253
+ * Apply configured response delay
254
+ * Supports both fixed delays and random delays within a range
255
+ * @private
256
+ */
257
+ async applyDelay() {
258
+ if (!this.globalConfig.delay) {
259
+ return;
260
+ }
261
+ const delay = Array.isArray(this.globalConfig.delay)
262
+ ? Math.random() *
263
+ (this.globalConfig.delay[1] - this.globalConfig.delay[0]) +
264
+ this.globalConfig.delay[0]
265
+ : this.globalConfig.delay;
266
+ await new Promise((resolve) => setTimeout(resolve, delay));
267
+ }
268
+ /**
269
+ * Parse and normalize response result into Response object
270
+ * Handles tuple format [status, body, headers], direct values, and response objects
271
+ * @param result - Raw result from generator or plugin
272
+ * @param routeConfig - Route configuration for content-type defaults
273
+ * @returns Normalized Response object with status, body, and headers
274
+ * @private
275
+ */
276
+ parseResponse(result, routeConfig) {
277
+ let status = 200;
278
+ let body = result;
279
+ let headers = {};
280
+ let tupleFormat = false;
281
+ // Handle already-formed response objects (from plugin error recovery)
282
+ if (result &&
283
+ typeof result === "object" &&
284
+ "status" in result &&
285
+ "body" in result) {
286
+ return {
287
+ status: result.status,
288
+ body: result.body,
289
+ headers: result.headers || {},
290
+ };
291
+ }
292
+ // Handle tuple response format [status, body, headers?]
293
+ if (Array.isArray(result) && typeof result[0] === "number") {
294
+ [status, body, headers = {}] = result;
295
+ tupleFormat = true;
296
+ }
297
+ // Handle null/undefined responses with 204 No Content
298
+ // But don't auto-convert if tuple format was used (status was explicitly provided)
299
+ if (body === null || body === undefined) {
300
+ if (!tupleFormat) {
301
+ status = status === 200 ? 204 : status; // Only change to 204 if status wasn't explicitly set via tuple
302
+ }
303
+ body = undefined; // Ensure body is undefined for null responses
304
+ }
305
+ // Add content-type header from route config if it exists and headers don't already have it
306
+ // But only if this isn't a tuple response (where headers are explicitly controlled)
307
+ if (!headers["content-type"] && routeConfig.contentType && !tupleFormat) {
308
+ headers["content-type"] = routeConfig.contentType;
309
+ // Handle special conversion cases when contentType is explicitly set
310
+ if (routeConfig.contentType === "text/plain" && body !== undefined) {
311
+ if (typeof body === "object" && !Buffer.isBuffer(body)) {
312
+ body = JSON.stringify(body);
313
+ }
314
+ else if (typeof body !== "string") {
315
+ body = String(body);
316
+ }
317
+ }
318
+ }
319
+ return {
320
+ status,
321
+ body,
322
+ headers,
323
+ };
324
+ }
325
+ /**
326
+ * Run all registered plugins in sequence
327
+ * First plugin to set response becomes generator, subsequent plugins transform
328
+ * Handles plugin errors via onError hooks
329
+ * @param context - Plugin context with request details
330
+ * @param initialResponse - Initial response from route generator
331
+ * @param _routeConfig - Route config (unused but kept for signature)
332
+ * @param _requestId - Request ID (unused but kept for signature)
333
+ * @returns Updated context and final response after all plugins
334
+ * @private
335
+ */
336
+ async runPluginPipeline(context, initialResponse, _routeConfig, _requestId) {
337
+ let currentContext = context;
338
+ let response = initialResponse;
339
+ this.logger.log("pipeline", `Running plugin pipeline for ${this.plugins.length} plugins`);
340
+ for (const plugin of this.plugins) {
341
+ this.logger.log("pipeline", `Processing plugin: ${plugin.name}`);
342
+ try {
343
+ const result = await plugin.process(currentContext, response);
344
+ if (!result || !result.context) {
345
+ throw new Error(`Plugin ${plugin.name} didn't return valid result`);
346
+ }
347
+ currentContext = result.context;
348
+ // First plugin to set response becomes the generator
349
+ if (result.response !== undefined &&
350
+ (response === undefined || response === null)) {
351
+ this.logger.log("pipeline", `Plugin ${plugin.name} generated response`);
352
+ response = result.response;
353
+ }
354
+ else if (result.response !== undefined && response !== undefined) {
355
+ this.logger.log("pipeline", `Plugin ${plugin.name} transformed response`);
356
+ response = result.response;
357
+ }
358
+ }
359
+ catch (error) {
360
+ this.logger.log("pipeline", `Plugin ${plugin.name} failed: ${error.message}`);
361
+ // Try error handling if plugin has onError hook
362
+ if (plugin.onError) {
363
+ try {
364
+ const errorResult = await plugin.onError(error, currentContext);
365
+ if (errorResult) {
366
+ this.logger.log("pipeline", `Plugin ${plugin.name} handled error`);
367
+ // If error handler returns response, use it and stop pipeline
368
+ if (typeof errorResult === "object" && errorResult.status) {
369
+ // Return the error response as the current response, stop pipeline
370
+ response = errorResult;
371
+ break;
372
+ }
373
+ }
374
+ }
375
+ catch (hookError) {
376
+ this.logger.log("pipeline", `Plugin ${plugin.name} error handler failed: ${hookError.message}`);
377
+ }
378
+ }
379
+ throw new PluginError(plugin.name, error);
380
+ }
381
+ }
382
+ return { context: currentContext, response };
383
+ }
384
+ /**
385
+ * Find a route that matches the given method and path
386
+ * Uses two-pass matching: exact routes first, then parameterized routes
387
+ * Searches in reverse order to prefer most recently defined routes
388
+ * @param method - HTTP method to match
389
+ * @param path - Request path to match
390
+ * @returns Matched compiled route or undefined if no match
391
+ * @private
392
+ */
393
+ findRoute(method, path) {
394
+ // First pass: Look for exact matches (routes without parameters)
395
+ for (let i = this.routes.length - 1; i >= 0; i--) {
396
+ const route = this.routes[i];
397
+ if (route.method === method &&
398
+ route.params.length === 0 &&
399
+ route.pattern.test(path)) {
400
+ return route;
401
+ }
402
+ }
403
+ // Second pass: Look for parameterized routes
404
+ for (let i = this.routes.length - 1; i >= 0; i--) {
405
+ const route = this.routes[i];
406
+ if (route.method === method &&
407
+ route.params.length > 0 &&
408
+ route.pattern.test(path)) {
409
+ return route;
410
+ }
411
+ }
412
+ return undefined;
413
+ }
414
+ /**
415
+ * Extract parameter values from path based on route pattern
416
+ * Maps capture groups from regex match to parameter names
417
+ * @param route - Compiled route with pattern and param names
418
+ * @param path - Request path to extract values from
419
+ * @returns Object mapping parameter names to extracted values
420
+ * @private
421
+ */
422
+ extractParams(route, path) {
423
+ const match = path.match(route.pattern);
424
+ if (!match)
425
+ return {};
426
+ const params = {};
427
+ route.params.forEach((param, index) => {
428
+ params[param] = match[index + 1];
429
+ });
430
+ return params;
431
+ }
432
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Base error class for all Schmock errors
3
+ */
4
+ export declare class SchmockError extends Error {
5
+ readonly code: string;
6
+ readonly context?: unknown;
7
+ constructor(message: string, code: string, context?: unknown);
8
+ }
9
+ /**
10
+ * Error thrown when a route is not found
11
+ */
12
+ export declare class RouteNotFoundError extends SchmockError {
13
+ constructor(method: string, path: string);
14
+ }
15
+ /**
16
+ * Error thrown when route parsing fails
17
+ */
18
+ export declare class RouteParseError extends SchmockError {
19
+ constructor(routeKey: string, reason: string);
20
+ }
21
+ /**
22
+ * Error thrown when response generation fails
23
+ */
24
+ export declare class ResponseGenerationError extends SchmockError {
25
+ constructor(route: string, error: Error);
26
+ }
27
+ /**
28
+ * Error thrown when a plugin fails
29
+ */
30
+ export declare class PluginError extends SchmockError {
31
+ constructor(pluginName: string, error: Error);
32
+ }
33
+ /**
34
+ * Error thrown when route definition is invalid
35
+ */
36
+ export declare class RouteDefinitionError extends SchmockError {
37
+ constructor(routeKey: string, reason: string);
38
+ }
39
+ /**
40
+ * Error thrown when schema validation fails
41
+ */
42
+ export declare class SchemaValidationError extends SchmockError {
43
+ constructor(schemaPath: string, issue: string, suggestion?: string);
44
+ }
45
+ /**
46
+ * Error thrown when schema generation fails
47
+ */
48
+ export declare class SchemaGenerationError extends SchmockError {
49
+ constructor(route: string, error: Error, schema?: unknown);
50
+ }
51
+ /**
52
+ * Error thrown when resource limits are exceeded
53
+ */
54
+ export declare class ResourceLimitError extends SchmockError {
55
+ constructor(resource: string, limit: number, actual?: number);
56
+ }
@@ -0,0 +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;CAMpC;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 ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Base error class for all Schmock errors
3
+ */
4
+ export class SchmockError extends Error {
5
+ code;
6
+ context;
7
+ constructor(message, code, context) {
8
+ super(message);
9
+ this.code = code;
10
+ this.context = context;
11
+ this.name = "SchmockError";
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ /**
16
+ * Error thrown when a route is not found
17
+ */
18
+ export class RouteNotFoundError extends SchmockError {
19
+ constructor(method, path) {
20
+ super(`Route not found: ${method} ${path}`, "ROUTE_NOT_FOUND", {
21
+ method,
22
+ path,
23
+ });
24
+ this.name = "RouteNotFoundError";
25
+ }
26
+ }
27
+ /**
28
+ * Error thrown when route parsing fails
29
+ */
30
+ export class RouteParseError extends SchmockError {
31
+ constructor(routeKey, reason) {
32
+ super(`Invalid route key format: "${routeKey}". ${reason}`, "ROUTE_PARSE_ERROR", { routeKey, reason });
33
+ this.name = "RouteParseError";
34
+ }
35
+ }
36
+ /**
37
+ * Error thrown when response generation fails
38
+ */
39
+ export class ResponseGenerationError extends SchmockError {
40
+ constructor(route, error) {
41
+ super(`Failed to generate response for route ${route}: ${error.message}`, "RESPONSE_GENERATION_ERROR", { route, originalError: error });
42
+ this.name = "ResponseGenerationError";
43
+ }
44
+ }
45
+ /**
46
+ * Error thrown when a plugin fails
47
+ */
48
+ export class PluginError extends SchmockError {
49
+ constructor(pluginName, error) {
50
+ super(`Plugin "${pluginName}" failed: ${error.message}`, "PLUGIN_ERROR", {
51
+ pluginName,
52
+ originalError: error,
53
+ });
54
+ this.name = "PluginError";
55
+ }
56
+ }
57
+ /**
58
+ * Error thrown when route definition is invalid
59
+ */
60
+ export class RouteDefinitionError extends SchmockError {
61
+ constructor(routeKey, reason) {
62
+ super(`Invalid route definition for "${routeKey}": ${reason}`, "ROUTE_DEFINITION_ERROR", { routeKey, reason });
63
+ this.name = "RouteDefinitionError";
64
+ }
65
+ }
66
+ /**
67
+ * Error thrown when schema validation fails
68
+ */
69
+ export class SchemaValidationError extends SchmockError {
70
+ constructor(schemaPath, issue, suggestion) {
71
+ super(`Schema validation failed at ${schemaPath}: ${issue}${suggestion ? `. ${suggestion}` : ""}`, "SCHEMA_VALIDATION_ERROR", { schemaPath, issue, suggestion });
72
+ this.name = "SchemaValidationError";
73
+ }
74
+ }
75
+ /**
76
+ * Error thrown when schema generation fails
77
+ */
78
+ export class SchemaGenerationError extends SchmockError {
79
+ constructor(route, error, schema) {
80
+ super(`Schema generation failed for route ${route}: ${error.message}`, "SCHEMA_GENERATION_ERROR", { route, originalError: error, schema });
81
+ this.name = "SchemaGenerationError";
82
+ }
83
+ }
84
+ /**
85
+ * Error thrown when resource limits are exceeded
86
+ */
87
+ export class ResourceLimitError extends SchmockError {
88
+ constructor(resource, limit, actual) {
89
+ super(`Resource limit exceeded for ${resource}: limit=${limit}${actual ? `, actual=${actual}` : ""}`, "RESOURCE_LIMIT_ERROR", { resource, limit, actual });
90
+ this.name = "ResourceLimitError";
91
+ }
92
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Create a new Schmock mock instance with callable API.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * // New callable API (default)
7
+ * const mock = schmock({ debug: true })
8
+ * mock('GET /users', () => [{ id: 1, name: 'John' }])
9
+ * .pipe(authPlugin())
10
+ *
11
+ * const response = await mock.handle('GET', '/users')
12
+ * ```
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Simple usage with defaults
17
+ * const mock = schmock()
18
+ * mock('GET /users', [{ id: 1, name: 'John' }])
19
+ * ```
20
+ *
21
+ * @param config Optional global configuration
22
+ * @returns A callable mock instance
23
+ */
24
+ export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
25
+ export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors";
26
+ export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, Response, ResponseResult, RouteConfig, RouteKey, } from "./types";
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
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,CAsB9B;AAGD,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAQ,GACT,MAAM,SAAS,CAAC"}