@schmock/core 1.0.1 → 1.0.3

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 CHANGED
@@ -1,4 +1,4 @@
1
- import type { Generator, GlobalConfig, HttpMethod, Plugin, RequestOptions, Response, RouteConfig, RouteKey } from "./types";
1
+ import type { Generator, GlobalConfig, HttpMethod, Plugin, RequestOptions, Response, RouteConfig, RouteKey } from "./types.js";
2
2
  /**
3
3
  * Callable mock instance that implements the new API.
4
4
  *
@@ -42,8 +42,8 @@ export declare class CallableMockInstance {
42
42
  private runPluginPipeline;
43
43
  /**
44
44
  * Find a route that matches the given method and path
45
- * Uses two-pass matching: exact routes first, then parameterized routes
46
- * Searches in reverse order to prefer most recently defined routes
45
+ * Uses two-pass matching: static routes first, then parameterized routes
46
+ * Matches routes in registration order (first registered wins)
47
47
  * @param method - HTTP method to match
48
48
  * @param path - Request path to match
49
49
  * @returns Matched compiled route or undefined if no match
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EAET,YAAY,EACZ,UAAU,EACV,MAAM,EAGN,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACT,MAAM,SAAS,CAAC;AA4CjB;;;;GAIG;AACH,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,YAAY;IAJhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAc;gBAER,YAAY,GAAE,YAAiB;IAanD,WAAW,CACT,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,WAAW,GAClB,IAAI;IA4DP,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAepB,MAAM,CACV,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC;IAiLpB;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA0DrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAmF/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA+BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EAET,YAAY,EACZ,UAAU,EACV,MAAM,EAGN,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACT,MAAM,YAAY,CAAC;AA4CpB;;;;GAIG;AACH,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,YAAY;IAJhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAc;gBAER,YAAY,GAAE,YAAiB;IAanD,WAAW,CACT,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,WAAW,GAClB,IAAI;IA4DP,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAepB,MAAM,CACV,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC;IAiLpB;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA0DrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAmF/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA6BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
package/dist/builder.js CHANGED
@@ -1,5 +1,5 @@
1
- import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors";
2
- import { parseRouteKey } from "./parser";
1
+ import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors.js";
2
+ import { parseRouteKey } from "./parser.js";
3
3
  /**
4
4
  * Debug logger that respects debug mode configuration
5
5
  */
@@ -383,17 +383,16 @@ export class CallableMockInstance {
383
383
  }
384
384
  /**
385
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
386
+ * Uses two-pass matching: static routes first, then parameterized routes
387
+ * Matches routes in registration order (first registered wins)
388
388
  * @param method - HTTP method to match
389
389
  * @param path - Request path to match
390
390
  * @returns Matched compiled route or undefined if no match
391
391
  * @private
392
392
  */
393
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];
394
+ // First pass: Look for static routes (routes without parameters)
395
+ for (const route of this.routes) {
397
396
  if (route.method === method &&
398
397
  route.params.length === 0 &&
399
398
  route.pattern.test(path)) {
@@ -401,8 +400,7 @@ export class CallableMockInstance {
401
400
  }
402
401
  }
403
402
  // Second pass: Look for parameterized routes
404
- for (let i = this.routes.length - 1; i >= 0; i--) {
405
- const route = this.routes[i];
403
+ for (const route of this.routes) {
406
404
  if (route.method === method &&
407
405
  route.params.length > 0 &&
408
406
  route.pattern.test(path)) {
package/dist/errors.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  */
4
4
  export declare class SchmockError extends Error {
5
5
  readonly code: string;
6
- readonly context?: unknown;
7
- constructor(message: string, code: string, context?: unknown);
6
+ readonly context?: unknown | undefined;
7
+ constructor(message: string, code: string, context?: unknown | undefined);
8
8
  }
9
9
  /**
10
10
  * Error thrown when a route is not found
@@ -54,3 +54,4 @@ export declare class SchemaGenerationError extends SchmockError {
54
54
  export declare class ResourceLimitError extends SchmockError {
55
55
  constructor(resource: string, limit: number, actual?: number);
56
56
  }
57
+ //# sourceMappingURL=errors.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CallableMockInstance, GlobalConfig } from "./types";
1
+ import type { CallableMockInstance, GlobalConfig } from "./types.js";
2
2
  /**
3
3
  * Create a new Schmock mock instance with callable API.
4
4
  *
@@ -23,6 +23,6 @@ import type { CallableMockInstance, GlobalConfig } from "./types";
23
23
  * @returns A callable mock instance
24
24
  */
25
25
  export declare function schmock(config?: GlobalConfig): CallableMockInstance;
26
- export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors";
27
- export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, Response, ResponseResult, RouteConfig, RouteKey, } from "./types";
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";
28
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EAEpB,YAAY,EAIb,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,oBAAoB,CAsBnE;AAGD,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,UAAU,CAAC;AAGlB,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EAEpB,YAAY,EAIb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,oBAAoB,CAsBnE;AAGD,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,aAAa,CAAC;AAGrB,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,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,42 @@
1
- class Z extends Error{code;context;constructor(A,B,G){super(A);this.code=B;this.context=G;this.name="SchmockError",Error.captureStackTrace(this,this.constructor)}}class _ extends Z{constructor(A,B){super(`Route not found: ${A} ${B}`,"ROUTE_NOT_FOUND",{method:A,path:B});this.name="RouteNotFoundError"}}class L extends Z{constructor(A,B){super(`Invalid route key format: "${A}". ${B}`,"ROUTE_PARSE_ERROR",{routeKey:A,reason:B});this.name="RouteParseError"}}class v extends Z{constructor(A,B){super(`Failed to generate response for route ${A}: ${B.message}`,"RESPONSE_GENERATION_ERROR",{route:A,originalError:B});this.name="ResponseGenerationError"}}class O extends Z{constructor(A,B){super(`Plugin "${A}" failed: ${B.message}`,"PLUGIN_ERROR",{pluginName:A,originalError:B});this.name="PluginError"}}class H extends Z{constructor(A,B){super(`Invalid route definition for "${A}": ${B}`,"ROUTE_DEFINITION_ERROR",{routeKey:A,reason:B});this.name="RouteDefinitionError"}}class M extends Z{constructor(A,B,G){super(`Schema validation failed at ${A}: ${B}${G?`. ${G}`:""}`,"SCHEMA_VALIDATION_ERROR",{schemaPath:A,issue:B,suggestion:G});this.name="SchemaValidationError"}}class N extends Z{constructor(A,B,G){super(`Schema generation failed for route ${A}: ${B.message}`,"SCHEMA_GENERATION_ERROR",{route:A,originalError:B,schema:G});this.name="SchemaGenerationError"}}class K extends Z{constructor(A,B,G){super(`Resource limit exceeded for ${A}: limit=${B}${G?`, actual=${G}`:""}`,"RESOURCE_LIMIT_ERROR",{resource:A,limit:B,actual:G});this.name="ResourceLimitError"}}var x=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"];function P(A){return x.includes(A)}function S(A){let B=A.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/);if(!B)throw new L(A,'Expected format: "METHOD /path" (e.g., "GET /users")');let[,G,J]=B,Q=[],U=/:([^/]+)/g,V;V=U.exec(J);while(V!==null)Q.push(V[1]),V=U.exec(J);let W=J.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/:([^/]+)/g,"([^/]+)"),X=new RegExp(`^${W}$`);if(!P(G))throw new L(A,`Invalid HTTP method: ${G}`);return{method:G,path:J,pattern:X,params:Q}}class k{enabled;constructor(A=!1){this.enabled=A}log(A,B,G){if(!this.enabled)return;let Q=`[${new Date().toISOString()}] [SCHMOCK:${A.toUpperCase()}]`;if(G)console.log(`${Q} ${B}`,G);else console.log(`${Q} ${B}`)}time(A){if(!this.enabled)return;console.time(`[SCHMOCK] ${A}`)}timeEnd(A){if(!this.enabled)return;console.timeEnd(`[SCHMOCK] ${A}`)}}class w{globalConfig;routes=[];plugins=[];logger;constructor(A={}){this.globalConfig=A;if(this.logger=new k(A.debug||!1),A.debug)this.logger.log("config","Debug mode enabled");this.logger.log("config","Callable mock instance created",{debug:A.debug,namespace:A.namespace,delay:A.delay})}defineRoute(A,B,G){if(!G.contentType)if(typeof B==="function")G.contentType="application/json";else if(typeof B==="string"||typeof B==="number"||typeof B==="boolean")G.contentType="text/plain";else if(Buffer.isBuffer(B))G.contentType="application/octet-stream";else G.contentType="application/json";if(typeof B!=="function"&&G.contentType==="application/json")try{JSON.stringify(B)}catch(U){throw new H(A,"Generator data is not valid JSON but contentType is application/json")}let J=S(A),Q={pattern:J.pattern,params:J.params,method:J.method,path:J.path,generator:B,config:G};return this.routes.push(Q),this.logger.log("route",`Route defined: ${A}`,{contentType:G.contentType,generatorType:typeof B,hasParams:J.params.length>0}),this}pipe(A){return this.plugins.push(A),this.logger.log("plugin",`Registered plugin: ${A.name}@${A.version||"unknown"}`,{name:A.name,version:A.version,hasProcess:typeof A.process==="function",hasOnError:typeof A.onError==="function"}),this}async handle(A,B,G){let J=Math.random().toString(36).substring(7);this.logger.log("request",`[${J}] ${A} ${B}`,{headers:G?.headers,query:G?.query,bodyType:G?.body?typeof G.body:"none"}),this.logger.time(`request-${J}`);try{let Q=B;if(this.globalConfig.namespace){let Y=this.globalConfig.namespace;if(Y==="/")Q=B;else{let $=Y.startsWith("/")?Y:`/${Y}`,T=B.startsWith("/")?B:`/${B}`,F=$.endsWith("/")&&$!=="/"?$.slice(0,-1):$;if(!T.startsWith(F)){this.logger.log("route",`[${J}] Path doesn't match namespace ${Y}`);let z=new _(A,B),I={status:404,body:{error:z.message,code:z.code},headers:{}};return this.logger.timeEnd(`request-${J}`),I}if(Q=T.substring(F.length),!Q.startsWith("/"))Q=`/${Q}`}}let U=this.findRoute(A,Q);if(!U){this.logger.log("route",`[${J}] No route found for ${A} ${Q}`);let Y=new _(A,B),$={status:404,body:{error:Y.message,code:Y.code},headers:{}};return this.logger.timeEnd(`request-${J}`),$}this.logger.log("route",`[${J}] Matched route: ${A} ${U.path}`);let V=this.extractParams(U,Q),W={method:A,path:Q,params:V,query:G?.query||{},headers:G?.headers||{},body:G?.body,state:this.globalConfig.state||{}},X;if(typeof U.generator==="function")X=await U.generator(W);else X=U.generator;let D={path:Q,route:U.config,method:A,params:V,query:G?.query||{},headers:G?.headers||{},body:G?.body,state:new Map,routeState:this.globalConfig.state||{}};try{let Y=await this.runPluginPipeline(D,X,U.config,J);D=Y.context,X=Y.response}catch(Y){throw this.logger.log("error",`[${J}] Plugin pipeline error: ${Y.message}`),Y}let j=this.parseResponse(X,U.config);return await this.applyDelay(),this.logger.log("response",`[${J}] Sending response ${j.status}`,{status:j.status,headers:j.headers,bodyType:typeof j.body}),this.logger.timeEnd(`request-${J}`),j}catch(Q){this.logger.log("error",`[${J}] Error processing request: ${Q.message}`,Q);let U={status:500,body:{error:Q.message,code:Q instanceof Z?Q.code:"INTERNAL_ERROR"},headers:{}};return await this.applyDelay(),this.logger.log("error",`[${J}] Returning error response 500`),this.logger.timeEnd(`request-${J}`),U}}async applyDelay(){if(!this.globalConfig.delay)return;let A=Array.isArray(this.globalConfig.delay)?Math.random()*(this.globalConfig.delay[1]-this.globalConfig.delay[0])+this.globalConfig.delay[0]:this.globalConfig.delay;await new Promise((B)=>setTimeout(B,A))}parseResponse(A,B){let G=200,J=A,Q={},U=!1;if(A&&typeof A==="object"&&"status"in A&&"body"in A)return{status:A.status,body:A.body,headers:A.headers||{}};if(Array.isArray(A)&&typeof A[0]==="number")[G,J,Q={}]=A,U=!0;if(J===null||J===void 0){if(!U)G=G===200?204:G;J=void 0}if(!Q["content-type"]&&B.contentType&&!U){if(Q["content-type"]=B.contentType,B.contentType==="text/plain"&&J!==void 0){if(typeof J==="object"&&!Buffer.isBuffer(J))J=JSON.stringify(J);else if(typeof J!=="string")J=String(J)}}return{status:G,body:J,headers:Q}}async runPluginPipeline(A,B,G,J){let Q=A,U=B;this.logger.log("pipeline",`Running plugin pipeline for ${this.plugins.length} plugins`);for(let V of this.plugins){this.logger.log("pipeline",`Processing plugin: ${V.name}`);try{let W=await V.process(Q,U);if(!W||!W.context)throw Error(`Plugin ${V.name} didn't return valid result`);if(Q=W.context,W.response!==void 0&&(U===void 0||U===null))this.logger.log("pipeline",`Plugin ${V.name} generated response`),U=W.response;else if(W.response!==void 0&&U!==void 0)this.logger.log("pipeline",`Plugin ${V.name} transformed response`),U=W.response}catch(W){if(this.logger.log("pipeline",`Plugin ${V.name} failed: ${W.message}`),V.onError)try{let X=await V.onError(W,Q);if(X){if(this.logger.log("pipeline",`Plugin ${V.name} handled error`),typeof X==="object"&&X.status){U=X;break}}}catch(X){this.logger.log("pipeline",`Plugin ${V.name} error handler failed: ${X.message}`)}throw new O(V.name,W)}}return{context:Q,response:U}}findRoute(A,B){for(let G=this.routes.length-1;G>=0;G--){let J=this.routes[G];if(J.method===A&&J.params.length===0&&J.pattern.test(B))return J}for(let G=this.routes.length-1;G>=0;G--){let J=this.routes[G];if(J.method===A&&J.params.length>0&&J.pattern.test(B))return J}return}extractParams(A,B){let G=B.match(A.pattern);if(!G)return{};let J={};return A.params.forEach((Q,U)=>{J[Q]=G[U+1]}),J}}function d(A){let B=new w(A||{}),G=(J,Q,U={})=>{return B.defineRoute(J,Q,U),G};return G.pipe=(J)=>{return B.pipe(J),G},G.handle=B.handle.bind(B),G}export{d as schmock,Z as SchmockError,M as SchemaValidationError,N as SchemaGenerationError,L as RouteParseError,_ as RouteNotFoundError,H as RouteDefinitionError,v as ResponseGenerationError,K as ResourceLimitError,O as PluginError};
1
+ import { CallableMockInstance as CallableMockInstanceImpl } from "./builder.js";
2
+ /**
3
+ * Create a new Schmock mock instance with callable API.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * // New callable API (default)
8
+ * const mock = schmock({ debug: true })
9
+ * mock('GET /users', () => [{ id: 1, name: 'John' }])
10
+ * .pipe(authPlugin())
11
+ *
12
+ * const response = await mock.handle('GET', '/users')
13
+ * ```
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Simple usage with defaults
18
+ * const mock = schmock()
19
+ * mock('GET /users', [{ id: 1, name: 'John' }])
20
+ * ```
21
+ *
22
+ * @param config Optional global configuration
23
+ * @returns A callable mock instance
24
+ */
25
+ export function schmock(config) {
26
+ // Always use new callable API
27
+ const instance = new CallableMockInstanceImpl(config || {});
28
+ // Create a callable function that wraps the instance
29
+ const callableInstance = ((route, generator, routeConfig = {}) => {
30
+ instance.defineRoute(route, generator, routeConfig);
31
+ return callableInstance; // Return the callable function for chaining
32
+ });
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
+ return callableInstance;
40
+ }
41
+ // Re-export errors
42
+ export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
package/dist/parser.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { HttpMethod } from "./types";
1
+ import type { HttpMethod } from "./types.js";
2
2
  export interface ParsedRoute {
3
3
  method: HttpMethod;
4
4
  path: string;
@@ -17,3 +17,4 @@ export interface ParsedRoute {
17
17
  * // => { method: 'GET', path: '/users/:id', pattern: /^\/users\/([^/]+)$/, params: ['id'] }
18
18
  */
19
19
  export declare function parseRouteKey(routeKey: string): ParsedRoute;
20
+ //# sourceMappingURL=parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAgB1C,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,CA4C3D"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgB7C,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,CA4C3D"}
package/dist/parser.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RouteParseError } from "./errors";
1
+ import { RouteParseError } from "./errors.js";
2
2
  const HTTP_METHODS = [
3
3
  "GET",
4
4
  "POST",
package/dist/types.d.ts CHANGED
@@ -1,4 +1,16 @@
1
- import type { JSONSchema7 } from "json-schema";
1
+ /**
2
+ * JSON Schema type (simplified for core package)
3
+ * Full schema support available via @schmock/schema
4
+ */
5
+ export interface JSONSchema {
6
+ type?: string | string[];
7
+ properties?: Record<string, JSONSchema>;
8
+ items?: JSONSchema | JSONSchema[];
9
+ required?: string[];
10
+ enum?: any[];
11
+ const?: any;
12
+ [key: string]: any;
13
+ }
2
14
  /**
3
15
  * HTTP methods supported by Schmock
4
16
  */
@@ -94,7 +106,7 @@ export interface RouteConfig {
94
106
  /**
95
107
  * Generator types that can be passed to route definitions
96
108
  */
97
- export type Generator = GeneratorFunction | StaticData | JSONSchema7;
109
+ export type Generator = GeneratorFunction | StaticData | JSONSchema;
98
110
  /**
99
111
  * Function that generates responses
100
112
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/core",
3
3
  "description": "Core functionality for Schmock",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -18,21 +18,21 @@
18
18
  "./package.json": "./package.json"
19
19
  },
20
20
  "scripts": {
21
- "build": "bun build:lib && bun build:types",
22
- "build:lib": "bun build --minify --outdir=dist src/index.ts",
23
- "build:types": "tsc -p tsconfig.json",
21
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc --build",
22
+ "build:types": "rm -f tsconfig.tsbuildinfo && tsc --build",
24
23
  "pretest": "rm -f src/*.js src/*.d.ts || true",
25
24
  "test": "vitest",
26
25
  "test:watch": "vitest --watch",
27
26
  "pretest:bdd": "rm -f src/*.js src/*.d.ts || true",
28
27
  "test:bdd": "vitest run --config vitest.config.bdd.ts",
29
28
  "lint": "biome check src/*.ts",
30
- "lint:fix": "biome check --write --unsafe src/*.ts"
29
+ "lint:fix": "biome check --write --unsafe src/*.ts",
30
+ "check:publish": "publint && attw --pack --ignore-rules cjs-resolves-to-esm"
31
31
  },
32
32
  "license": "MIT",
33
33
  "devDependencies": {
34
34
  "@amiceli/vitest-cucumber": "^6.1.0",
35
- "@types/node": "^24.9.1",
35
+ "@types/node": "^25.0.0",
36
36
  "@vitest/ui": "^4.0.15",
37
37
  "vitest": "^4.0.15"
38
38
  }
package/src/builder.ts CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  RouteDefinitionError,
4
4
  RouteNotFoundError,
5
5
  SchmockError,
6
- } from "./errors";
7
- import { parseRouteKey } from "./parser";
6
+ } from "./errors.js";
7
+ import { parseRouteKey } from "./parser.js";
8
8
  import type {
9
9
  Generator,
10
10
  GeneratorFunction,
@@ -17,7 +17,7 @@ import type {
17
17
  Response,
18
18
  RouteConfig,
19
19
  RouteKey,
20
- } from "./types";
20
+ } from "./types.js";
21
21
 
22
22
  /**
23
23
  * Debug logger that respects debug mode configuration
@@ -525,8 +525,8 @@ export class CallableMockInstance {
525
525
 
526
526
  /**
527
527
  * Find a route that matches the given method and path
528
- * Uses two-pass matching: exact routes first, then parameterized routes
529
- * Searches in reverse order to prefer most recently defined routes
528
+ * Uses two-pass matching: static routes first, then parameterized routes
529
+ * Matches routes in registration order (first registered wins)
530
530
  * @param method - HTTP method to match
531
531
  * @param path - Request path to match
532
532
  * @returns Matched compiled route or undefined if no match
@@ -536,9 +536,8 @@ export class CallableMockInstance {
536
536
  method: HttpMethod,
537
537
  path: string,
538
538
  ): CompiledCallableRoute | undefined {
539
- // First pass: Look for exact matches (routes without parameters)
540
- for (let i = this.routes.length - 1; i >= 0; i--) {
541
- const route = this.routes[i];
539
+ // First pass: Look for static routes (routes without parameters)
540
+ for (const route of this.routes) {
542
541
  if (
543
542
  route.method === method &&
544
543
  route.params.length === 0 &&
@@ -549,8 +548,7 @@ export class CallableMockInstance {
549
548
  }
550
549
 
551
550
  // Second pass: Look for parameterized routes
552
- for (let i = this.routes.length - 1; i >= 0; i--) {
553
- const route = this.routes[i];
551
+ for (const route of this.routes) {
554
552
  if (
555
553
  route.method === method &&
556
554
  route.params.length > 0 &&
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CallableMockInstance as CallableMockInstanceImpl } from "./builder";
1
+ import { CallableMockInstance as CallableMockInstanceImpl } from "./builder.js";
2
2
  import type {
3
3
  CallableMockInstance,
4
4
  Generator,
@@ -6,7 +6,7 @@ import type {
6
6
  Plugin,
7
7
  RouteConfig,
8
8
  RouteKey,
9
- } from "./types";
9
+ } from "./types.js";
10
10
 
11
11
  /**
12
12
  * Create a new Schmock mock instance with callable API.
@@ -66,7 +66,7 @@ export {
66
66
  SchemaGenerationError,
67
67
  SchemaValidationError,
68
68
  SchmockError,
69
- } from "./errors";
69
+ } from "./errors.js";
70
70
 
71
71
  // Re-export types
72
72
  export type {
@@ -84,4 +84,4 @@ export type {
84
84
  ResponseResult,
85
85
  RouteConfig,
86
86
  RouteKey,
87
- } from "./types";
87
+ } from "./types.js";
package/src/parser.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { RouteParseError } from "./errors";
2
- import type { HttpMethod } from "./types";
1
+ import { RouteParseError } from "./errors.js";
2
+ import type { HttpMethod } from "./types.js";
3
3
 
4
4
  const HTTP_METHODS: readonly HttpMethod[] = [
5
5
  "GET",
@@ -128,7 +128,7 @@ describe("route matching", () => {
128
128
  });
129
129
 
130
130
  describe("route precedence and conflicts", () => {
131
- it("matches most recently defined route when patterns overlap", async () => {
131
+ it("prioritizes static routes over parameterized routes", async () => {
132
132
  const mock = schmock();
133
133
  mock("GET /users/:id", "parameterized");
134
134
  mock("GET /users/special", "static");
@@ -136,7 +136,7 @@ describe("route matching", () => {
136
136
  const paramResponse = await mock.handle("GET", "/users/123");
137
137
  const staticResponse = await mock.handle("GET", "/users/special");
138
138
 
139
- // The static route should be matched since it was defined later
139
+ // Static routes should always be checked before parameterized routes
140
140
  expect(paramResponse.body).toBe("parameterized");
141
141
  expect(staticResponse.body).toBe("static");
142
142
  });
@@ -153,18 +153,61 @@ describe("route matching", () => {
153
153
  expect(v1Response.body).toBe("v1-specific");
154
154
  });
155
155
 
156
- it("matches first matching route in definition order", async () => {
156
+ it("matches routes in registration order (first registered wins)", async () => {
157
157
  const mock = schmock();
158
158
  mock("GET /:type/items", "first");
159
159
  mock("GET /shop/:category", "second");
160
160
 
161
161
  const response = await mock.handle("GET", "/shop/items");
162
162
 
163
- // Both routes match, but with reverse order search, the second route should win
164
- // /:type/items matches with type="shop"
165
- // /shop/:category matches with category="items"
166
- // Since we search in reverse, /shop/:category (more specific) should match
167
- expect(response.body).toBe("second");
163
+ // Both routes match, but the first registered route should win
164
+ // This matches the behavior of Express, Hono, Fastify, etc.
165
+ expect(response.body).toBe("first");
166
+ });
167
+
168
+ it("matches specific routes before wildcard when registered in natural order", async () => {
169
+ // Bug report reproduction: natural order (specific before wildcard)
170
+ const mock = schmock();
171
+ mock("GET /api/items/special", () => ({ type: "special" }));
172
+ mock("GET /api/items/:id", () => ({ type: "generic" }));
173
+
174
+ const specialResult = await mock.handle("GET", "/api/items/special");
175
+ const genericResult = await mock.handle("GET", "/api/items/123");
176
+
177
+ // Static route should match for /api/items/special
178
+ expect(specialResult.body).toEqual({ type: "special" });
179
+ // Parameterized route should match for /api/items/123
180
+ expect(genericResult.body).toEqual({ type: "generic" });
181
+ });
182
+
183
+ it("matches multiple specific routes before wildcard", async () => {
184
+ // Bug report scenario with multiple specific routes
185
+ const mock = schmock();
186
+ mock("GET /api/vulns/aggregated", "aggregated");
187
+ mock("GET /api/vulns/count", "count");
188
+ mock("GET /api/vulns/familyList", "familyList");
189
+ mock("GET /api/vulns/:vulnId", "byId");
190
+
191
+ const aggregatedRes = await mock.handle("GET", "/api/vulns/aggregated");
192
+ const countRes = await mock.handle("GET", "/api/vulns/count");
193
+ const familyListRes = await mock.handle("GET", "/api/vulns/familyList");
194
+ const byIdRes = await mock.handle("GET", "/api/vulns/CVE-2024-1234");
195
+
196
+ expect(aggregatedRes.body).toBe("aggregated");
197
+ expect(countRes.body).toBe("count");
198
+ expect(familyListRes.body).toBe("familyList");
199
+ expect(byIdRes.body).toBe("byId");
200
+ });
201
+
202
+ it("matches overlapping parameterized routes in registration order", async () => {
203
+ const mock = schmock();
204
+ mock("GET /api/:org/users/:id", "first-pattern");
205
+ mock("GET /api/:version/users/:userId", "second-pattern");
206
+
207
+ const response = await mock.handle("GET", "/api/acme/users/123");
208
+
209
+ // When both routes are parameterized and match, first registered wins
210
+ expect(response.body).toBe("first-pattern");
168
211
  });
169
212
  });
170
213
 
package/src/types.ts CHANGED
@@ -1,4 +1,16 @@
1
- import type { JSONSchema7 } from "json-schema";
1
+ /**
2
+ * JSON Schema type (simplified for core package)
3
+ * Full schema support available via @schmock/schema
4
+ */
5
+ export interface JSONSchema {
6
+ type?: string | string[];
7
+ properties?: Record<string, JSONSchema>;
8
+ items?: JSONSchema | JSONSchema[];
9
+ required?: string[];
10
+ enum?: any[];
11
+ const?: any;
12
+ [key: string]: any;
13
+ }
2
14
 
3
15
  /**
4
16
  * HTTP methods supported by Schmock
@@ -121,7 +133,7 @@ export interface RouteConfig {
121
133
  /**
122
134
  * Generator types that can be passed to route definitions
123
135
  */
124
- export type Generator = GeneratorFunction | StaticData | JSONSchema7;
136
+ export type Generator = GeneratorFunction | StaticData | JSONSchema;
125
137
 
126
138
  /**
127
139
  * Function that generates responses