@schmock/core 2.2.3 → 2.3.1

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/dist/abort.d.ts +3 -0
  4. package/dist/abort.d.ts.map +1 -0
  5. package/dist/abort.js +30 -0
  6. package/dist/binary.d.ts +8 -0
  7. package/dist/binary.d.ts.map +1 -0
  8. package/dist/binary.js +9 -0
  9. package/dist/builder.d.ts +31 -10
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/builder.js +804 -203
  12. package/dist/constants.d.ts +38 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +123 -2
  15. package/dist/errors.d.ts +14 -3
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +41 -6
  18. package/dist/helpers.d.ts +9 -0
  19. package/dist/helpers.d.ts.map +1 -1
  20. package/dist/helpers.js +18 -2
  21. package/dist/http-helpers.d.ts +52 -5
  22. package/dist/http-helpers.d.ts.map +1 -1
  23. package/dist/http-helpers.js +153 -37
  24. package/dist/index.d.ts +381 -60
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -3
  27. package/dist/interceptor.d.ts +11 -1
  28. package/dist/interceptor.d.ts.map +1 -1
  29. package/dist/interceptor.js +354 -156
  30. package/dist/parser.d.ts.map +1 -1
  31. package/dist/parser.js +12 -3
  32. package/dist/plugin-pipeline.d.ts +11 -4
  33. package/dist/plugin-pipeline.d.ts.map +1 -1
  34. package/dist/plugin-pipeline.js +117 -39
  35. package/dist/response-normalizer.d.ts +16 -0
  36. package/dist/response-normalizer.d.ts.map +1 -0
  37. package/dist/response-normalizer.js +316 -0
  38. package/dist/response-parser.d.ts.map +1 -1
  39. package/dist/response-parser.js +85 -18
  40. package/dist/route-matcher.d.ts +3 -0
  41. package/dist/route-matcher.d.ts.map +1 -1
  42. package/dist/route-matcher.js +12 -6
  43. package/dist/types.d.ts +7 -2
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +18 -8
  46. package/src/audit-core-builder.test.ts +0 -218
  47. package/src/audit-response-guard.test.ts +0 -38
  48. package/src/builder.test.ts +0 -289
  49. package/src/builder.ts +0 -701
  50. package/src/constants.test.ts +0 -99
  51. package/src/constants.ts +0 -73
  52. package/src/debug.test.ts +0 -241
  53. package/src/delay.test.ts +0 -319
  54. package/src/dist-shape.test.ts +0 -35
  55. package/src/errors.test.ts +0 -223
  56. package/src/errors.ts +0 -130
  57. package/src/factory.test.ts +0 -133
  58. package/src/helpers.test.ts +0 -147
  59. package/src/helpers.ts +0 -58
  60. package/src/http-helpers.ts +0 -113
  61. package/src/index.ts +0 -151
  62. package/src/interceptor.test.ts +0 -291
  63. package/src/interceptor.ts +0 -320
  64. package/src/namespace.test.ts +0 -274
  65. package/src/parser.property.test.ts +0 -594
  66. package/src/parser.test.ts +0 -148
  67. package/src/parser.ts +0 -64
  68. package/src/plugin-pipeline.ts +0 -103
  69. package/src/plugin-system.test.ts +0 -602
  70. package/src/response-parser.ts +0 -75
  71. package/src/response-parsing.test.ts +0 -333
  72. package/src/route-matcher.ts +0 -69
  73. package/src/route-matching.test.ts +0 -394
  74. package/src/server.test.ts +0 -165
  75. package/src/smart-defaults.test.ts +0 -361
  76. package/src/steps/async-support.steps.ts +0 -400
  77. package/src/steps/audit-core-builder.steps.ts +0 -188
  78. package/src/steps/audit-onerror-tuple.steps.ts +0 -99
  79. package/src/steps/basic-usage.steps.ts +0 -245
  80. package/src/steps/developer-experience.steps.ts +0 -204
  81. package/src/steps/error-handling.steps.ts +0 -345
  82. package/src/steps/fluent-api.steps.ts +0 -255
  83. package/src/steps/http-methods.steps.ts +0 -331
  84. package/src/steps/interceptor.steps.ts +0 -260
  85. package/src/steps/lifecycle-events.steps.ts +0 -142
  86. package/src/steps/performance-reliability.steps.ts +0 -423
  87. package/src/steps/plugin-integration.steps.ts +0 -280
  88. package/src/steps/request-history.steps.ts +0 -276
  89. package/src/steps/response-delay.steps.ts +0 -88
  90. package/src/steps/route-key-format.steps.ts +0 -99
  91. package/src/steps/standalone-server.steps.ts +0 -233
  92. package/src/steps/state-concurrency.steps.ts +0 -739
  93. package/src/steps/stateful-workflows.steps.ts +0 -353
  94. package/src/types.ts +0 -36
@@ -1,148 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { parseRouteKey } from "./parser";
3
-
4
- describe("parseRouteKey", () => {
5
- describe("valid route keys", () => {
6
- it("parses simple GET route", () => {
7
- const result = parseRouteKey("GET /users");
8
- expect(result).toEqual({
9
- method: "GET",
10
- path: "/users",
11
- pattern: expect.any(RegExp),
12
- params: [],
13
- });
14
- });
15
-
16
- it("parses route with single parameter", () => {
17
- const result = parseRouteKey("GET /users/:id");
18
- expect(result).toEqual({
19
- method: "GET",
20
- path: "/users/:id",
21
- pattern: expect.any(RegExp),
22
- params: ["id"],
23
- });
24
- });
25
-
26
- it("parses route with multiple parameters", () => {
27
- const result = parseRouteKey(
28
- "DELETE /api/posts/:postId/comments/:commentId",
29
- );
30
- expect(result).toEqual({
31
- method: "DELETE",
32
- path: "/api/posts/:postId/comments/:commentId",
33
- pattern: expect.any(RegExp),
34
- params: ["postId", "commentId"],
35
- });
36
- });
37
-
38
- it("supports all HTTP methods", () => {
39
- const methods = [
40
- "GET",
41
- "POST",
42
- "PUT",
43
- "DELETE",
44
- "PATCH",
45
- "HEAD",
46
- "OPTIONS",
47
- ] as const;
48
-
49
- for (const method of methods) {
50
- const result = parseRouteKey(`${method} /test`);
51
- expect(result.method).toBe(method);
52
- }
53
- });
54
-
55
- it("handles paths with namespace", () => {
56
- const result = parseRouteKey("POST /api/v2/users");
57
- expect(result).toEqual({
58
- method: "POST",
59
- path: "/api/v2/users",
60
- pattern: expect.any(RegExp),
61
- params: [],
62
- });
63
- });
64
-
65
- it("creates correct regex pattern for simple path", () => {
66
- const result = parseRouteKey("GET /users");
67
- expect("/users").toMatch(result.pattern);
68
- expect("/users/123").not.toMatch(result.pattern);
69
- });
70
-
71
- it("creates correct regex pattern with parameters", () => {
72
- const result = parseRouteKey("GET /users/:id");
73
- expect("/users/123").toMatch(result.pattern);
74
- expect("/users/abc-def").toMatch(result.pattern);
75
- expect("/users").not.toMatch(result.pattern);
76
- expect("/users/").not.toMatch(result.pattern);
77
- expect("/users/123/posts").not.toMatch(result.pattern);
78
- });
79
- });
80
-
81
- describe("invalid route keys", () => {
82
- it("throws on missing method", () => {
83
- expect(() => parseRouteKey("/users")).toThrow("Invalid route key format");
84
- });
85
-
86
- it("throws on missing path", () => {
87
- expect(() => parseRouteKey("GET")).toThrow("Invalid route key format");
88
- });
89
-
90
- it("throws on invalid method", () => {
91
- expect(() => parseRouteKey("INVALID /users")).toThrow(
92
- "Invalid route key format",
93
- );
94
- });
95
-
96
- it("throws on lowercase method", () => {
97
- expect(() => parseRouteKey("get /users")).toThrow(
98
- "Invalid route key format",
99
- );
100
- });
101
-
102
- it("throws on missing space", () => {
103
- expect(() => parseRouteKey("GET/users")).toThrow(
104
- "Invalid route key format",
105
- );
106
- });
107
-
108
- it("throws on empty string", () => {
109
- expect(() => parseRouteKey("")).toThrow("Invalid route key format");
110
- });
111
- });
112
-
113
- describe("parameter extraction", () => {
114
- it("extracts matched parameters", () => {
115
- const route = parseRouteKey("GET /users/:userId/posts/:postId");
116
- const match = "/users/123/posts/456".match(route.pattern);
117
-
118
- expect(match).toBeTruthy();
119
- expect(match?.[1]).toBe("123");
120
- expect(match?.[2]).toBe("456");
121
- });
122
-
123
- it("handles special characters in parameters", () => {
124
- const route = parseRouteKey("GET /files/:filename");
125
- const match = "/files/report-2023.pdf".match(route.pattern);
126
-
127
- expect(match).toBeTruthy();
128
- expect(match?.[1]).toBe("report-2023.pdf");
129
- });
130
-
131
- it("stops the param name at a literal '.' suffix and escapes the dot", () => {
132
- const route = parseRouteKey("GET /files/:name.json");
133
-
134
- expect(route.params).toEqual(["name"]);
135
- expect("/files/report.json".match(route.pattern)?.[1]).toBe("report");
136
- expect("/files/report.json2".match(route.pattern)).toBeNull();
137
- expect("/files/reportXjson".match(route.pattern)).toBeNull();
138
- });
139
-
140
- it("treats parens around params as literal characters", () => {
141
- const route = parseRouteKey("GET /items/(:id)");
142
-
143
- expect(route.params).toEqual(["id"]);
144
- expect("/items/(42)".match(route.pattern)?.[1]).toBe("42");
145
- expect("/items/42".match(route.pattern)).toBeNull();
146
- });
147
- });
148
- });
package/src/parser.ts DELETED
@@ -1,64 +0,0 @@
1
- import { toHttpMethod } from "./constants.js";
2
- import { RouteParseError } from "./errors.js";
3
- import type { HttpMethod } from "./types.js";
4
-
5
- export interface ParsedRoute {
6
- method: HttpMethod;
7
- path: string;
8
- pattern: RegExp;
9
- params: string[];
10
- }
11
-
12
- /**
13
- * Parse 'METHOD /path' route key format
14
- *
15
- * Design note: We validate the format strictly to catch typos early.
16
- * The 'METHOD /path' format was chosen for its readability and
17
- * similarity to API documentation formats.
18
- *
19
- * @example
20
- * parseRouteKey('GET /users/:id')
21
- * // => { method: 'GET', path: '/users/:id', pattern: /^\/users\/([^/]+)$/, params: ['id'] }
22
- */
23
- export function parseRouteKey(routeKey: string): ParsedRoute {
24
- const match = routeKey.match(
25
- /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/,
26
- );
27
-
28
- if (!match) {
29
- throw new RouteParseError(
30
- routeKey,
31
- 'Expected format: "METHOD /path" (e.g., "GET /users")',
32
- );
33
- }
34
-
35
- const [, method, path] = match;
36
-
37
- // Parameter names are restricted to [A-Za-z0-9_-] so that surrounding
38
- // literals (".json", brackets, parens, etc.) terminate the name and can
39
- // be escaped without bleeding into the parameter regex. Build the
40
- // pattern by splitting the path on the param marker, escaping each
41
- // literal segment, then substituting the capture group for each :name.
42
- const params: string[] = [];
43
-
44
- const regexPath = path
45
- .split(/(:[a-zA-Z0-9_-]+)/g)
46
- .map((segment) => {
47
- const paramMatch = segment.match(/^:([a-zA-Z0-9_-]+)$/);
48
- if (paramMatch) {
49
- params.push(paramMatch[1]);
50
- return "([^/]+)";
51
- }
52
- return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
53
- })
54
- .join("");
55
-
56
- const pattern = new RegExp(`^${regexPath}$`);
57
-
58
- return {
59
- method: toHttpMethod(method),
60
- path,
61
- pattern,
62
- params,
63
- };
64
- }
@@ -1,103 +0,0 @@
1
- import { isStatusTuple } from "./constants.js";
2
- import { errorMessage, PluginError } from "./errors.js";
3
-
4
- /** Structural typing — DebugLogger satisfies this without an import */
5
- interface PipelineLogger {
6
- log(category: string, message: string, data?: unknown): void;
7
- }
8
-
9
- /**
10
- * Run all registered plugins in sequence
11
- * First plugin to set response becomes generator, subsequent plugins transform
12
- * Handles plugin errors via onError hooks
13
- */
14
- export async function runPluginPipeline(
15
- plugins: Schmock.Plugin[],
16
- context: Schmock.PluginContext,
17
- initialResponse: unknown,
18
- logger: PipelineLogger,
19
- ): Promise<{ context: Schmock.PluginContext; response?: unknown }> {
20
- let currentContext = context;
21
- let response: unknown = initialResponse;
22
-
23
- logger.log(
24
- "pipeline",
25
- `Running plugin pipeline for ${plugins.length} plugins`,
26
- );
27
-
28
- for (const plugin of plugins) {
29
- logger.log("pipeline", `Processing plugin: ${plugin.name}`);
30
-
31
- try {
32
- const result = await plugin.process(currentContext, response);
33
-
34
- if (!result?.context) {
35
- throw new Error(`Plugin ${plugin.name} didn't return valid result`);
36
- }
37
-
38
- currentContext = result.context;
39
-
40
- // First plugin to set response becomes the generator
41
- if (
42
- result.response !== undefined &&
43
- (response === undefined || response === null)
44
- ) {
45
- logger.log("pipeline", `Plugin ${plugin.name} generated response`);
46
- response = result.response;
47
- } else if (result.response !== undefined && response !== undefined) {
48
- logger.log("pipeline", `Plugin ${plugin.name} transformed response`);
49
- response = result.response;
50
- }
51
- } catch (error) {
52
- logger.log(
53
- "pipeline",
54
- `Plugin ${plugin.name} failed: ${errorMessage(error)}`,
55
- );
56
-
57
- // Try error handling if plugin has onError hook
58
- if (plugin.onError) {
59
- try {
60
- const pluginError =
61
- error instanceof Error ? error : new Error(errorMessage(error));
62
- const errorResult = await plugin.onError(pluginError, currentContext);
63
- if (errorResult) {
64
- logger.log("pipeline", `Plugin ${plugin.name} handled error`);
65
-
66
- // Error return → transform the thrown error
67
- if (errorResult instanceof Error) {
68
- throw new PluginError(plugin.name, errorResult);
69
- }
70
-
71
- // ResponseResult return → recover, stop pipeline
72
- // Accept both object-with-status and status tuple [code, body] / [code, body, headers]
73
- if (
74
- isStatusTuple(errorResult) ||
75
- (typeof errorResult === "object" &&
76
- errorResult !== null &&
77
- "status" in errorResult)
78
- ) {
79
- response = errorResult;
80
- break;
81
- }
82
- }
83
- // void/falsy return → propagate original error below
84
- } catch (hookError) {
85
- // If the hook itself threw (including our PluginError above), re-throw it
86
- if (hookError instanceof PluginError) {
87
- throw hookError;
88
- }
89
- logger.log(
90
- "pipeline",
91
- `Plugin ${plugin.name} error handler failed: ${errorMessage(hookError)}`,
92
- );
93
- }
94
- }
95
-
96
- const cause =
97
- error instanceof Error ? error : new Error(errorMessage(error));
98
- throw new PluginError(plugin.name, cause);
99
- }
100
- }
101
-
102
- return { context: currentContext, response };
103
- }