@schmock/core 1.7.0 → 1.9.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 +2 -2
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +7 -45
- package/dist/http-helpers.d.ts +22 -0
- package/dist/http-helpers.d.ts.map +1 -0
- package/dist/http-helpers.js +75 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/builder.ts +23 -54
- package/src/http-helpers.ts +91 -0
- package/src/index.ts +17 -4
- package/src/types.ts +9 -0
package/dist/builder.d.ts
CHANGED
|
@@ -24,8 +24,8 @@ export declare class CallableMockInstance {
|
|
|
24
24
|
lastRequest(method?: Schmock.HttpMethod, path?: string): Schmock.RequestRecord | undefined;
|
|
25
25
|
getRoutes(): Schmock.RouteInfo[];
|
|
26
26
|
getState(): Record<string, unknown>;
|
|
27
|
-
on(event:
|
|
28
|
-
off(event:
|
|
27
|
+
on<E extends Schmock.SchmockEvent>(event: E, listener: (data: Schmock.SchmockEventMap[E]) => void): this;
|
|
28
|
+
off<E extends Schmock.SchmockEvent>(event: E, listener: (data: Schmock.SchmockEventMap[E]) => void): this;
|
|
29
29
|
private emit;
|
|
30
30
|
reset(): void;
|
|
31
31
|
resetHistory(): void;
|
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":"AAkFA;;;;GAIG;AACH,qBAAa,oBAAoB;IAYnB,OAAO,CAAC,YAAY;IAXhC,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;IACrD,OAAO,CAAC,WAAW,CAA2C;IAC9D,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAiC;IAEnD,OAAO,CAAC,SAAS,CAAoC;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,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,oBAAoB,GAAG,IAAI;IAIvD,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;IAoBlC,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,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE;IAQhC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAMnC,EAAE,CAAC,CAAC,SAAS,OAAO,CAAC,YAAY,EAC/B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;IAUP,GAAG,CAAC,CAAC,SAAS,OAAO,CAAC,YAAY,EAChC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;IAKP,OAAO,CAAC,IAAI;IAWZ,KAAK,IAAI,IAAI;IAeb,YAAY,IAAI,IAAI;IAKpB,UAAU,IAAI,IAAI;IAWlB,MAAM,CAAC,IAAI,SAAI,EAAE,QAAQ,SAAc,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;IAuCrE,KAAK,IAAI,IAAI;IAUP,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;IAgN5B;;;;OAIG;YACW,UAAU;IAgBxB;;;;;;;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,6 +1,7 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { isStatusTuple, toHttpMethod } from "./constants.js";
|
|
3
3
|
import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors.js";
|
|
4
|
+
import { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
|
|
4
5
|
import { parseRouteKey } from "./parser.js";
|
|
5
6
|
function errorMessage(error) {
|
|
6
7
|
return error instanceof Error ? error.message : "Unknown error";
|
|
@@ -60,6 +61,7 @@ export class CallableMockInstance {
|
|
|
60
61
|
callableRef;
|
|
61
62
|
server;
|
|
62
63
|
serverInfo;
|
|
64
|
+
// biome-ignore lint/complexity/noBannedTypes: internal storage for event listeners with varying signatures
|
|
63
65
|
listeners = new Map();
|
|
64
66
|
constructor(globalConfig = {}) {
|
|
65
67
|
this.globalConfig = globalConfig;
|
|
@@ -252,51 +254,11 @@ export class CallableMockInstance {
|
|
|
252
254
|
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
253
255
|
const method = toHttpMethod(req.method ?? "GET");
|
|
254
256
|
const path = url.pathname;
|
|
255
|
-
const headers =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
const query = {};
|
|
262
|
-
url.searchParams.forEach((value, key) => {
|
|
263
|
-
query[key] = value;
|
|
264
|
-
});
|
|
265
|
-
const chunks = [];
|
|
266
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
267
|
-
req.on("end", () => {
|
|
268
|
-
const raw = Buffer.concat(chunks).toString();
|
|
269
|
-
let body;
|
|
270
|
-
const contentType = headers["content-type"] ?? "";
|
|
271
|
-
if (raw && contentType.includes("json")) {
|
|
272
|
-
try {
|
|
273
|
-
body = JSON.parse(raw);
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
body = raw;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else if (raw) {
|
|
280
|
-
body = raw;
|
|
281
|
-
}
|
|
282
|
-
void this.handle(method, path, { headers, body, query }).then((schmockResponse) => {
|
|
283
|
-
const responseHeaders = {
|
|
284
|
-
...schmockResponse.headers,
|
|
285
|
-
};
|
|
286
|
-
if (!responseHeaders["content-type"] &&
|
|
287
|
-
schmockResponse.body !== undefined &&
|
|
288
|
-
typeof schmockResponse.body !== "string") {
|
|
289
|
-
responseHeaders["content-type"] = "application/json";
|
|
290
|
-
}
|
|
291
|
-
const responseBody = schmockResponse.body === undefined
|
|
292
|
-
? undefined
|
|
293
|
-
: typeof schmockResponse.body === "string"
|
|
294
|
-
? schmockResponse.body
|
|
295
|
-
: JSON.stringify(schmockResponse.body);
|
|
296
|
-
res.writeHead(schmockResponse.status, responseHeaders);
|
|
297
|
-
res.end(responseBody);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
257
|
+
const headers = parseNodeHeaders(req);
|
|
258
|
+
const query = parseNodeQuery(url);
|
|
259
|
+
void collectBody(req, headers).then((body) => this.handle(method, path, { headers, body, query }).then((schmockResponse) => {
|
|
260
|
+
writeSchmockResponse(res, schmockResponse);
|
|
261
|
+
}));
|
|
300
262
|
});
|
|
301
263
|
this.server = httpServer;
|
|
302
264
|
return new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* Convert Node.js IncomingMessage headers to a flat Record<string, string>.
|
|
4
|
+
* Drops array-valued headers (keeps only string values).
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseNodeHeaders(req: IncomingMessage): Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Extract query parameters from a URL as a flat Record<string, string>.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseNodeQuery(url: URL): Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Collect and parse the request body from a Node.js IncomingMessage.
|
|
13
|
+
* Returns parsed JSON if content-type includes "json", otherwise the raw string.
|
|
14
|
+
* Returns undefined for empty bodies.
|
|
15
|
+
*/
|
|
16
|
+
export declare function collectBody(req: IncomingMessage, headers: Record<string, string>): Promise<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Write a Schmock Response to a Node.js ServerResponse.
|
|
19
|
+
* Serializes non-string bodies as JSON and sets content-type when missing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function writeSchmockResponse(res: ServerResponse, response: Schmock.Response, extraHeaders?: Record<string, string>): void;
|
|
22
|
+
//# sourceMappingURL=http-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-helpers.d.ts","sourceRoot":"","sources":["../src/http-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ7E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,IAAI,CAuBN"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert Node.js IncomingMessage headers to a flat Record<string, string>.
|
|
3
|
+
* Drops array-valued headers (keeps only string values).
|
|
4
|
+
*/
|
|
5
|
+
export function parseNodeHeaders(req) {
|
|
6
|
+
const headers = {};
|
|
7
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
headers[key] = value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return headers;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extract query parameters from a URL as a flat Record<string, string>.
|
|
16
|
+
*/
|
|
17
|
+
export function parseNodeQuery(url) {
|
|
18
|
+
const query = {};
|
|
19
|
+
url.searchParams.forEach((value, key) => {
|
|
20
|
+
query[key] = value;
|
|
21
|
+
});
|
|
22
|
+
return query;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Collect and parse the request body from a Node.js IncomingMessage.
|
|
26
|
+
* Returns parsed JSON if content-type includes "json", otherwise the raw string.
|
|
27
|
+
* Returns undefined for empty bodies.
|
|
28
|
+
*/
|
|
29
|
+
export function collectBody(req, headers) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const chunks = [];
|
|
32
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
33
|
+
req.on("end", () => {
|
|
34
|
+
const raw = Buffer.concat(chunks).toString();
|
|
35
|
+
if (!raw) {
|
|
36
|
+
resolve(undefined);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const contentType = headers["content-type"] ?? "";
|
|
40
|
+
if (contentType.includes("json")) {
|
|
41
|
+
try {
|
|
42
|
+
resolve(JSON.parse(raw));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
resolve(raw);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
resolve(raw);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Write a Schmock Response to a Node.js ServerResponse.
|
|
56
|
+
* Serializes non-string bodies as JSON and sets content-type when missing.
|
|
57
|
+
*/
|
|
58
|
+
export function writeSchmockResponse(res, response, extraHeaders) {
|
|
59
|
+
const responseHeaders = {
|
|
60
|
+
...response.headers,
|
|
61
|
+
...extraHeaders,
|
|
62
|
+
};
|
|
63
|
+
if (!responseHeaders["content-type"] &&
|
|
64
|
+
response.body !== undefined &&
|
|
65
|
+
typeof response.body !== "string") {
|
|
66
|
+
responseHeaders["content-type"] = "application/json";
|
|
67
|
+
}
|
|
68
|
+
const responseBody = response.body === undefined
|
|
69
|
+
? undefined
|
|
70
|
+
: typeof response.body === "string"
|
|
71
|
+
? response.body
|
|
72
|
+
: JSON.stringify(response.body);
|
|
73
|
+
res.writeHead(response.status, responseHeaders);
|
|
74
|
+
res.end(responseBody);
|
|
75
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,5 +24,6 @@
|
|
|
24
24
|
export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
|
|
25
25
|
export { HTTP_METHODS, isHttpMethod, isStatusTuple, 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 { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
|
|
27
28
|
export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, RequestRecord, Response, ResponseBody, ResponseResult, RouteConfig, RouteInfo, RouteKey, ServerInfo, StaticData, } from "./types.js";
|
|
28
29
|
//# 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":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,
|
|
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,CAmD9B;AAGD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,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,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,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,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -42,14 +42,14 @@ export function schmock(config) {
|
|
|
42
42
|
reset: instance.reset.bind(instance),
|
|
43
43
|
resetHistory: instance.resetHistory.bind(instance),
|
|
44
44
|
resetState: instance.resetState.bind(instance),
|
|
45
|
-
on
|
|
45
|
+
on(event, listener) {
|
|
46
46
|
instance.on(event, listener);
|
|
47
47
|
return callableInstance;
|
|
48
|
-
}
|
|
49
|
-
off
|
|
48
|
+
},
|
|
49
|
+
off(event, listener) {
|
|
50
50
|
instance.off(event, listener);
|
|
51
51
|
return callableInstance;
|
|
52
|
-
}
|
|
52
|
+
},
|
|
53
53
|
getRoutes: instance.getRoutes.bind(instance),
|
|
54
54
|
getState: instance.getState.bind(instance),
|
|
55
55
|
listen: instance.listen.bind(instance),
|
|
@@ -62,3 +62,5 @@ export function schmock(config) {
|
|
|
62
62
|
export { HTTP_METHODS, isHttpMethod, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
63
63
|
// Re-export errors
|
|
64
64
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
|
65
|
+
// Re-export HTTP server helpers
|
|
66
|
+
export { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -17,4 +17,13 @@ export type StaticData = Schmock.StaticData;
|
|
|
17
17
|
export type RequestRecord = Schmock.RequestRecord;
|
|
18
18
|
export type ServerInfo = Schmock.ServerInfo;
|
|
19
19
|
export type RouteInfo = Schmock.RouteInfo;
|
|
20
|
+
export type SchemaGenerationContext = Schmock.SchemaGenerationContext;
|
|
21
|
+
export type FakerPluginOptions = Schmock.FakerPluginOptions;
|
|
22
|
+
export type ExpressAdapterOptions = Schmock.ExpressAdapterOptions;
|
|
23
|
+
export type AngularAdapterOptions = Schmock.AngularAdapterOptions;
|
|
24
|
+
export type OpenApiOptions = Schmock.OpenApiOptions;
|
|
25
|
+
export type SeedSource = Schmock.SeedSource;
|
|
26
|
+
export type SeedConfig = Schmock.SeedConfig;
|
|
27
|
+
export type CliOptions = Schmock.CliOptions;
|
|
28
|
+
export type CliServer = Schmock.CliServer;
|
|
20
29
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACxC,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACxC,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;AAC9C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;AAC1C,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;AACpC,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACxC,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACxC,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;AAC9C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;AAC1C,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;AACpC,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAChD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;AAC1C,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;AAC5D,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACpD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC"}
|
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.
|
|
4
|
+
"version": "1.9.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@amiceli/vitest-cucumber": "^6.2.0",
|
|
36
|
-
"@types/node": "^25.2.
|
|
36
|
+
"@types/node": "^25.2.3",
|
|
37
37
|
"@vitest/ui": "^4.0.18",
|
|
38
38
|
"vitest": "^4.0.18"
|
|
39
39
|
}
|
package/src/builder.ts
CHANGED
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
RouteNotFoundError,
|
|
8
8
|
SchmockError,
|
|
9
9
|
} from "./errors.js";
|
|
10
|
+
import {
|
|
11
|
+
collectBody,
|
|
12
|
+
parseNodeHeaders,
|
|
13
|
+
parseNodeQuery,
|
|
14
|
+
writeSchmockResponse,
|
|
15
|
+
} from "./http-helpers.js";
|
|
10
16
|
import { parseRouteKey } from "./parser.js";
|
|
11
17
|
|
|
12
18
|
function errorMessage(error: unknown): string {
|
|
@@ -88,7 +94,8 @@ export class CallableMockInstance {
|
|
|
88
94
|
private callableRef: Schmock.CallableMockInstance | undefined;
|
|
89
95
|
private server: Server | undefined;
|
|
90
96
|
private serverInfo: Schmock.ServerInfo | undefined;
|
|
91
|
-
|
|
97
|
+
// biome-ignore lint/complexity/noBannedTypes: internal storage for event listeners with varying signatures
|
|
98
|
+
private listeners = new Map<string, Set<Function>>();
|
|
92
99
|
|
|
93
100
|
constructor(private globalConfig: Schmock.GlobalConfig = {}) {
|
|
94
101
|
this.logger = new DebugLogger(globalConfig.debug || false);
|
|
@@ -272,7 +279,10 @@ export class CallableMockInstance {
|
|
|
272
279
|
|
|
273
280
|
// ===== Lifecycle Events =====
|
|
274
281
|
|
|
275
|
-
on
|
|
282
|
+
on<E extends Schmock.SchmockEvent>(
|
|
283
|
+
event: E,
|
|
284
|
+
listener: (data: Schmock.SchmockEventMap[E]) => void,
|
|
285
|
+
): this {
|
|
276
286
|
let set = this.listeners.get(event);
|
|
277
287
|
if (!set) {
|
|
278
288
|
set = new Set();
|
|
@@ -282,7 +292,10 @@ export class CallableMockInstance {
|
|
|
282
292
|
return this;
|
|
283
293
|
}
|
|
284
294
|
|
|
285
|
-
off
|
|
295
|
+
off<E extends Schmock.SchmockEvent>(
|
|
296
|
+
event: E,
|
|
297
|
+
listener: (data: Schmock.SchmockEventMap[E]) => void,
|
|
298
|
+
): this {
|
|
286
299
|
this.listeners.get(event)?.delete(listener);
|
|
287
300
|
return this;
|
|
288
301
|
}
|
|
@@ -341,60 +354,16 @@ export class CallableMockInstance {
|
|
|
341
354
|
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
342
355
|
const method = toHttpMethod(req.method ?? "GET");
|
|
343
356
|
const path = url.pathname;
|
|
357
|
+
const headers = parseNodeHeaders(req);
|
|
358
|
+
const query = parseNodeQuery(url);
|
|
344
359
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (typeof value === "string") {
|
|
348
|
-
headers[key] = value;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const query: Record<string, string> = {};
|
|
353
|
-
url.searchParams.forEach((value, key) => {
|
|
354
|
-
query[key] = value;
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const chunks: Buffer[] = [];
|
|
358
|
-
req.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
359
|
-
req.on("end", () => {
|
|
360
|
-
const raw = Buffer.concat(chunks).toString();
|
|
361
|
-
let body: unknown;
|
|
362
|
-
const contentType = headers["content-type"] ?? "";
|
|
363
|
-
if (raw && contentType.includes("json")) {
|
|
364
|
-
try {
|
|
365
|
-
body = JSON.parse(raw);
|
|
366
|
-
} catch {
|
|
367
|
-
body = raw;
|
|
368
|
-
}
|
|
369
|
-
} else if (raw) {
|
|
370
|
-
body = raw;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
void this.handle(method, path, { headers, body, query }).then(
|
|
360
|
+
void collectBody(req, headers).then((body) =>
|
|
361
|
+
this.handle(method, path, { headers, body, query }).then(
|
|
374
362
|
(schmockResponse) => {
|
|
375
|
-
|
|
376
|
-
...schmockResponse.headers,
|
|
377
|
-
};
|
|
378
|
-
if (
|
|
379
|
-
!responseHeaders["content-type"] &&
|
|
380
|
-
schmockResponse.body !== undefined &&
|
|
381
|
-
typeof schmockResponse.body !== "string"
|
|
382
|
-
) {
|
|
383
|
-
responseHeaders["content-type"] = "application/json";
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const responseBody =
|
|
387
|
-
schmockResponse.body === undefined
|
|
388
|
-
? undefined
|
|
389
|
-
: typeof schmockResponse.body === "string"
|
|
390
|
-
? schmockResponse.body
|
|
391
|
-
: JSON.stringify(schmockResponse.body);
|
|
392
|
-
|
|
393
|
-
res.writeHead(schmockResponse.status, responseHeaders);
|
|
394
|
-
res.end(responseBody);
|
|
363
|
+
writeSchmockResponse(res, schmockResponse);
|
|
395
364
|
},
|
|
396
|
-
)
|
|
397
|
-
|
|
365
|
+
),
|
|
366
|
+
);
|
|
398
367
|
});
|
|
399
368
|
|
|
400
369
|
this.server = httpServer;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert Node.js IncomingMessage headers to a flat Record<string, string>.
|
|
5
|
+
* Drops array-valued headers (keeps only string values).
|
|
6
|
+
*/
|
|
7
|
+
export function parseNodeHeaders(req: IncomingMessage): Record<string, string> {
|
|
8
|
+
const headers: Record<string, string> = {};
|
|
9
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
headers[key] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return headers;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract query parameters from a URL as a flat Record<string, string>.
|
|
19
|
+
*/
|
|
20
|
+
export function parseNodeQuery(url: URL): Record<string, string> {
|
|
21
|
+
const query: Record<string, string> = {};
|
|
22
|
+
url.searchParams.forEach((value, key) => {
|
|
23
|
+
query[key] = value;
|
|
24
|
+
});
|
|
25
|
+
return query;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collect and parse the request body from a Node.js IncomingMessage.
|
|
30
|
+
* Returns parsed JSON if content-type includes "json", otherwise the raw string.
|
|
31
|
+
* Returns undefined for empty bodies.
|
|
32
|
+
*/
|
|
33
|
+
export function collectBody(
|
|
34
|
+
req: IncomingMessage,
|
|
35
|
+
headers: Record<string, string>,
|
|
36
|
+
): Promise<unknown> {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const chunks: Buffer[] = [];
|
|
39
|
+
req.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
40
|
+
req.on("end", () => {
|
|
41
|
+
const raw = Buffer.concat(chunks).toString();
|
|
42
|
+
if (!raw) {
|
|
43
|
+
resolve(undefined);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const contentType = headers["content-type"] ?? "";
|
|
47
|
+
if (contentType.includes("json")) {
|
|
48
|
+
try {
|
|
49
|
+
resolve(JSON.parse(raw));
|
|
50
|
+
} catch {
|
|
51
|
+
resolve(raw);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
resolve(raw);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write a Schmock Response to a Node.js ServerResponse.
|
|
62
|
+
* Serializes non-string bodies as JSON and sets content-type when missing.
|
|
63
|
+
*/
|
|
64
|
+
export function writeSchmockResponse(
|
|
65
|
+
res: ServerResponse,
|
|
66
|
+
response: Schmock.Response,
|
|
67
|
+
extraHeaders?: Record<string, string>,
|
|
68
|
+
): void {
|
|
69
|
+
const responseHeaders: Record<string, string> = {
|
|
70
|
+
...response.headers,
|
|
71
|
+
...extraHeaders,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
!responseHeaders["content-type"] &&
|
|
76
|
+
response.body !== undefined &&
|
|
77
|
+
typeof response.body !== "string"
|
|
78
|
+
) {
|
|
79
|
+
responseHeaders["content-type"] = "application/json";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const responseBody =
|
|
83
|
+
response.body === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: typeof response.body === "string"
|
|
86
|
+
? response.body
|
|
87
|
+
: JSON.stringify(response.body);
|
|
88
|
+
|
|
89
|
+
res.writeHead(response.status, responseHeaders);
|
|
90
|
+
res.end(responseBody);
|
|
91
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -52,14 +52,20 @@ export function schmock(
|
|
|
52
52
|
reset: instance.reset.bind(instance),
|
|
53
53
|
resetHistory: instance.resetHistory.bind(instance),
|
|
54
54
|
resetState: instance.resetState.bind(instance),
|
|
55
|
-
on
|
|
55
|
+
on<E extends Schmock.SchmockEvent>(
|
|
56
|
+
event: E,
|
|
57
|
+
listener: (data: Schmock.SchmockEventMap[E]) => void,
|
|
58
|
+
) {
|
|
56
59
|
instance.on(event, listener);
|
|
57
60
|
return callableInstance;
|
|
58
|
-
}
|
|
59
|
-
off
|
|
61
|
+
},
|
|
62
|
+
off<E extends Schmock.SchmockEvent>(
|
|
63
|
+
event: E,
|
|
64
|
+
listener: (data: Schmock.SchmockEventMap[E]) => void,
|
|
65
|
+
) {
|
|
60
66
|
instance.off(event, listener);
|
|
61
67
|
return callableInstance;
|
|
62
|
-
}
|
|
68
|
+
},
|
|
63
69
|
getRoutes: instance.getRoutes.bind(instance),
|
|
64
70
|
getState: instance.getState.bind(instance),
|
|
65
71
|
listen: instance.listen.bind(instance),
|
|
@@ -92,6 +98,13 @@ export {
|
|
|
92
98
|
SchemaValidationError,
|
|
93
99
|
SchmockError,
|
|
94
100
|
} from "./errors.js";
|
|
101
|
+
// Re-export HTTP server helpers
|
|
102
|
+
export {
|
|
103
|
+
collectBody,
|
|
104
|
+
parseNodeHeaders,
|
|
105
|
+
parseNodeQuery,
|
|
106
|
+
writeSchmockResponse,
|
|
107
|
+
} from "./http-helpers.js";
|
|
95
108
|
// Re-export types
|
|
96
109
|
export type {
|
|
97
110
|
CallableMockInstance,
|
package/src/types.ts
CHANGED
|
@@ -20,3 +20,12 @@ export type StaticData = Schmock.StaticData;
|
|
|
20
20
|
export type RequestRecord = Schmock.RequestRecord;
|
|
21
21
|
export type ServerInfo = Schmock.ServerInfo;
|
|
22
22
|
export type RouteInfo = Schmock.RouteInfo;
|
|
23
|
+
export type SchemaGenerationContext = Schmock.SchemaGenerationContext;
|
|
24
|
+
export type FakerPluginOptions = Schmock.FakerPluginOptions;
|
|
25
|
+
export type ExpressAdapterOptions = Schmock.ExpressAdapterOptions;
|
|
26
|
+
export type AngularAdapterOptions = Schmock.AngularAdapterOptions;
|
|
27
|
+
export type OpenApiOptions = Schmock.OpenApiOptions;
|
|
28
|
+
export type SeedSource = Schmock.SeedSource;
|
|
29
|
+
export type SeedConfig = Schmock.SeedConfig;
|
|
30
|
+
export type CliOptions = Schmock.CliOptions;
|
|
31
|
+
export type CliServer = Schmock.CliServer;
|