@warp-drive/holodeck 0.1.0-alpha.63 → 0.1.0-alpha.65
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/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +154 -210
- package/dist/index.js.map +1 -0
- package/dist/mock.d.ts +89 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +179 -133
- package/dist/mock.js.map +1 -0
- package/package.json +15 -16
- package/server/bun.js +12 -9
- package/server/node.js +20 -17
- package/server/utils.js +55 -0
- package/declarations/index.d.ts +0 -52
- package/declarations/mock.d.ts +0 -85
package/dist/mock.js
CHANGED
|
@@ -1,153 +1,199 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getIsRecording, mock } from "./index.js";
|
|
2
2
|
|
|
3
|
+
//#region src/mock.ts
|
|
3
4
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* Available options:
|
|
22
|
-
* - status: the status code to return (default: 200)
|
|
23
|
-
* - headers: the headers to return (default: {})
|
|
24
|
-
* - body: the body to match against for the request (default: null)
|
|
25
|
-
* - RECORD: whether to record the request (default: false)
|
|
26
|
-
*
|
|
27
|
-
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
28
|
-
* @param response a function which generates the response to return
|
|
29
|
-
* @param options status, headers for the response, body to match against for the request, and whether to record the request
|
|
30
|
-
* @return
|
|
31
|
-
*/
|
|
5
|
+
* Sets up Mocking for a GET request on the mock server
|
|
6
|
+
* for the supplied url.
|
|
7
|
+
*
|
|
8
|
+
* The response body is generated by the supplied response function.
|
|
9
|
+
*
|
|
10
|
+
* Available options:
|
|
11
|
+
* - status: the status code to return (default: 200)
|
|
12
|
+
* - headers: the headers to return (default: {})
|
|
13
|
+
* - body: the body to match against for the request (default: null)
|
|
14
|
+
* - RECORD: whether to record the request (default: false)
|
|
15
|
+
*
|
|
16
|
+
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
17
|
+
* @param response a function which generates the response to return
|
|
18
|
+
* @param options status, headers for the response, body to match against for the request, and whether to record the request
|
|
19
|
+
* @return
|
|
20
|
+
*/
|
|
32
21
|
function GET(owner, url, response, options) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
22
|
+
return mock(owner, () => ({
|
|
23
|
+
status: options?.status ?? 200,
|
|
24
|
+
statusText: options?.statusText ?? "OK",
|
|
25
|
+
headers: options?.headers ?? {},
|
|
26
|
+
body: options?.body ?? null,
|
|
27
|
+
method: "GET",
|
|
28
|
+
url,
|
|
29
|
+
response: response()
|
|
30
|
+
}), getIsRecording() || (options?.RECORD ?? false));
|
|
42
31
|
}
|
|
43
|
-
const STATUS_TEXT_FOR = new Map([
|
|
44
|
-
|
|
32
|
+
const STATUS_TEXT_FOR = /* @__PURE__ */ new Map([
|
|
33
|
+
[200, "OK"],
|
|
34
|
+
[201, "Created"],
|
|
35
|
+
[202, "Accepted"],
|
|
36
|
+
[203, "Non-Authoritative Information"],
|
|
37
|
+
[204, "No Content"],
|
|
38
|
+
[205, "Reset Content"],
|
|
39
|
+
[206, "Partial Content"],
|
|
40
|
+
[207, "Multi-Status"],
|
|
41
|
+
[208, "Already Reported"],
|
|
42
|
+
[226, "IM Used"],
|
|
43
|
+
[300, "Multiple Choices"],
|
|
44
|
+
[301, "Moved Permanently"],
|
|
45
|
+
[302, "Found"],
|
|
46
|
+
[303, "See Other"],
|
|
47
|
+
[304, "Not Modified"],
|
|
48
|
+
[307, "Temporary Redirect"],
|
|
49
|
+
[308, "Permanent Redirect"],
|
|
50
|
+
[400, "Bad Request"],
|
|
51
|
+
[401, "Unauthorized"],
|
|
52
|
+
[402, "Payment Required"],
|
|
53
|
+
[403, "Forbidden"],
|
|
54
|
+
[404, "Not Found"],
|
|
55
|
+
[405, "Method Not Allowed"],
|
|
56
|
+
[406, "Not Acceptable"],
|
|
57
|
+
[407, "Proxy Authentication Required"],
|
|
58
|
+
[408, "Request Timeout"],
|
|
59
|
+
[409, "Conflict"],
|
|
60
|
+
[410, "Gone"],
|
|
61
|
+
[411, "Length Required"],
|
|
62
|
+
[412, "Precondition Failed"],
|
|
63
|
+
[413, "Payload Too Large"],
|
|
64
|
+
[414, "URI Too Long"],
|
|
65
|
+
[415, "Unsupported Media Type"],
|
|
66
|
+
[416, "Range Not Satisfiable"],
|
|
67
|
+
[417, "Expectation Failed"],
|
|
68
|
+
[419, "Page Expired"],
|
|
69
|
+
[420, "Enhance Your Calm"],
|
|
70
|
+
[421, "Misdirected Request"],
|
|
71
|
+
[422, "Unprocessable Entity"],
|
|
72
|
+
[423, "Locked"],
|
|
73
|
+
[424, "Failed Dependency"],
|
|
74
|
+
[425, "Too Early"],
|
|
75
|
+
[426, "Upgrade Required"],
|
|
76
|
+
[428, "Precondition Required"],
|
|
77
|
+
[429, "Too Many Requests"],
|
|
78
|
+
[430, "Request Header Fields Too Large"],
|
|
79
|
+
[431, "Request Header Fields Too Large"],
|
|
80
|
+
[450, "Blocked By Windows Parental Controls"],
|
|
81
|
+
[451, "Unavailable For Legal Reasons"],
|
|
82
|
+
[500, "Internal Server Error"],
|
|
83
|
+
[501, "Not Implemented"],
|
|
84
|
+
[502, "Bad Gateway"],
|
|
85
|
+
[503, "Service Unavailable"],
|
|
86
|
+
[504, "Gateway Timeout"],
|
|
87
|
+
[505, "HTTP Version Not Supported"],
|
|
88
|
+
[506, "Variant Also Negotiates"],
|
|
89
|
+
[507, "Insufficient Storage"],
|
|
90
|
+
[508, "Loop Detected"],
|
|
91
|
+
[509, "Bandwidth Limit Exceeded"],
|
|
92
|
+
[510, "Not Extended"],
|
|
93
|
+
[511, "Network Authentication Required"]
|
|
94
|
+
]);
|
|
45
95
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
96
|
+
* Mock a POST request
|
|
97
|
+
*/
|
|
48
98
|
function POST(owner, url, response, options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
return mock(owner, () => {
|
|
100
|
+
const body = response();
|
|
101
|
+
const status = options?.status ?? (body ? 201 : 204);
|
|
102
|
+
return {
|
|
103
|
+
status,
|
|
104
|
+
statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
|
|
105
|
+
headers: options?.headers ?? {},
|
|
106
|
+
body: options?.body ?? null,
|
|
107
|
+
method: "POST",
|
|
108
|
+
url,
|
|
109
|
+
response: body
|
|
110
|
+
};
|
|
111
|
+
}, getIsRecording() || (options?.RECORD ?? false));
|
|
62
112
|
}
|
|
63
|
-
|
|
64
113
|
/**
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
* mock a PUT request
|
|
115
|
+
*/
|
|
67
116
|
function PUT(owner, url, response, options) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
return mock(owner, () => {
|
|
118
|
+
const body = response();
|
|
119
|
+
const status = options?.status ?? (body ? 200 : 204);
|
|
120
|
+
return {
|
|
121
|
+
status,
|
|
122
|
+
statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
|
|
123
|
+
headers: options?.headers ?? {},
|
|
124
|
+
body: options?.body ?? null,
|
|
125
|
+
method: "PUT",
|
|
126
|
+
url,
|
|
127
|
+
response: body
|
|
128
|
+
};
|
|
129
|
+
}, getIsRecording() || (options?.RECORD ?? false));
|
|
81
130
|
}
|
|
82
131
|
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
132
|
+
* mock a PATCH request
|
|
133
|
+
*
|
|
134
|
+
*/
|
|
86
135
|
function PATCH(owner, url, response, options) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
136
|
+
return mock(owner, () => {
|
|
137
|
+
const body = response();
|
|
138
|
+
const status = options?.status ?? (body ? 200 : 204);
|
|
139
|
+
return {
|
|
140
|
+
status,
|
|
141
|
+
statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
|
|
142
|
+
headers: options?.headers ?? {},
|
|
143
|
+
body: options?.body ?? null,
|
|
144
|
+
method: "PATCH",
|
|
145
|
+
url,
|
|
146
|
+
response: body
|
|
147
|
+
};
|
|
148
|
+
}, getIsRecording() || (options?.RECORD ?? false));
|
|
100
149
|
}
|
|
101
150
|
/**
|
|
102
|
-
|
|
103
|
-
|
|
151
|
+
* mock a DELETE request
|
|
152
|
+
*/
|
|
104
153
|
function DELETE(owner, url, response, options) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
154
|
+
return mock(owner, () => {
|
|
155
|
+
const body = response();
|
|
156
|
+
const status = options?.status ?? (body ? 200 : 204);
|
|
157
|
+
return {
|
|
158
|
+
status,
|
|
159
|
+
statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? "",
|
|
160
|
+
headers: options?.headers ?? {},
|
|
161
|
+
body: options?.body ?? null,
|
|
162
|
+
method: "DELETE",
|
|
163
|
+
url,
|
|
164
|
+
response: body
|
|
165
|
+
};
|
|
166
|
+
}, getIsRecording() || (options?.RECORD ?? false));
|
|
118
167
|
}
|
|
119
|
-
|
|
120
168
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
function HEAD(owner, url, response,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
options
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
method: 'HEAD',
|
|
148
|
-
url,
|
|
149
|
-
response: response()
|
|
150
|
-
}), getIsRecording() || (options?.RECORD ?? false));
|
|
169
|
+
* Sets up Mocking for a HEAD request on the mock server
|
|
170
|
+
* for the supplied url.
|
|
171
|
+
*
|
|
172
|
+
* The response body is generated by the supplied response function.
|
|
173
|
+
*
|
|
174
|
+
* Available options:
|
|
175
|
+
* - status: the status code to return (default: 200)
|
|
176
|
+
* - headers: the headers to return (default: {})
|
|
177
|
+
* - body: the body to match against for the request (default: null)
|
|
178
|
+
* - RECORD: whether to record the request (default: false)
|
|
179
|
+
*
|
|
180
|
+
* @param url the url to mock, relative to the mock server host (e.g. `users/1`)
|
|
181
|
+
* @param response a function which generates the response to return
|
|
182
|
+
* @param options status, headers for the response, body to match against for the request, and whether to record the request
|
|
183
|
+
* @return
|
|
184
|
+
*/
|
|
185
|
+
function HEAD(owner, url, response, options) {
|
|
186
|
+
return mock(owner, () => ({
|
|
187
|
+
status: options?.status ?? 200,
|
|
188
|
+
statusText: options?.statusText ?? "OK",
|
|
189
|
+
headers: options?.headers ?? {},
|
|
190
|
+
body: options?.body ?? null,
|
|
191
|
+
method: "HEAD",
|
|
192
|
+
url,
|
|
193
|
+
response: response()
|
|
194
|
+
}), getIsRecording() || (options?.RECORD ?? false));
|
|
151
195
|
}
|
|
152
196
|
|
|
197
|
+
//#endregion
|
|
153
198
|
export { DELETE, GET, HEAD, PATCH, POST, PUT };
|
|
199
|
+
//# sourceMappingURL=mock.js.map
|
package/dist/mock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","names":[],"sources":["../src/mock.ts"],"sourcesContent":["import { getIsRecording, mock } from '.';\n\n/**\n * @public\n */\nexport interface Scaffold {\n status: number;\n statusText?: string;\n headers: Record<string, string>;\n body: Record<string, string> | string | null;\n method: string;\n url: string;\n response: Record<string, unknown>;\n}\n\n/**\n * @public\n */\nexport type ScaffoldGenerator = () => Scaffold;\n\n/**\n * @public\n */\nexport type ResponseGenerator = () => Record<string, unknown>;\n\n/**\n * Sets up Mocking for a GET request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: whether to record the request (default: false)\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to record the request\n * @return\n */\nexport function GET(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'GET',\n url,\n response: response(),\n }),\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n\nconst STATUS_TEXT_FOR = new Map([\n [200, 'OK'],\n [201, 'Created'],\n [202, 'Accepted'],\n [203, 'Non-Authoritative Information'],\n [204, 'No Content'],\n [205, 'Reset Content'],\n [206, 'Partial Content'],\n [207, 'Multi-Status'],\n [208, 'Already Reported'],\n [226, 'IM Used'],\n [300, 'Multiple Choices'],\n [301, 'Moved Permanently'],\n [302, 'Found'],\n [303, 'See Other'],\n [304, 'Not Modified'],\n [307, 'Temporary Redirect'],\n [308, 'Permanent Redirect'],\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [402, 'Payment Required'],\n [403, 'Forbidden'],\n [404, 'Not Found'],\n [405, 'Method Not Allowed'],\n [406, 'Not Acceptable'],\n [407, 'Proxy Authentication Required'],\n [408, 'Request Timeout'],\n [409, 'Conflict'],\n [410, 'Gone'],\n [411, 'Length Required'],\n [412, 'Precondition Failed'],\n [413, 'Payload Too Large'],\n [414, 'URI Too Long'],\n [415, 'Unsupported Media Type'],\n [416, 'Range Not Satisfiable'],\n [417, 'Expectation Failed'],\n [419, 'Page Expired'],\n [420, 'Enhance Your Calm'],\n [421, 'Misdirected Request'],\n [422, 'Unprocessable Entity'],\n [423, 'Locked'],\n [424, 'Failed Dependency'],\n [425, 'Too Early'],\n [426, 'Upgrade Required'],\n [428, 'Precondition Required'],\n [429, 'Too Many Requests'],\n [430, 'Request Header Fields Too Large'],\n [431, 'Request Header Fields Too Large'],\n [450, 'Blocked By Windows Parental Controls'],\n [451, 'Unavailable For Legal Reasons'],\n [500, 'Internal Server Error'],\n [501, 'Not Implemented'],\n [502, 'Bad Gateway'],\n [503, 'Service Unavailable'],\n [504, 'Gateway Timeout'],\n [505, 'HTTP Version Not Supported'],\n [506, 'Variant Also Negotiates'],\n [507, 'Insufficient Storage'],\n [508, 'Loop Detected'],\n [509, 'Bandwidth Limit Exceeded'],\n [510, 'Not Extended'],\n [511, 'Network Authentication Required'],\n]);\n\n/**\n * Mock a POST request\n */\nexport function POST(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => {\n const body = response();\n const status = options?.status ?? (body ? 201 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'POST',\n url,\n response: body,\n };\n },\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n\n/**\n * mock a PUT request\n */\nexport function PUT(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PUT',\n url,\n response: body,\n };\n },\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n/**\n * mock a PATCH request\n *\n */\nexport function PATCH(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'PATCH',\n url,\n response: body,\n };\n },\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n/**\n * mock a DELETE request\n */\nexport function DELETE(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => {\n const body = response();\n const status = options?.status ?? (body ? 200 : 204);\n\n return {\n status: status,\n statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'DELETE',\n url,\n response: body,\n };\n },\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n\n/**\n * Sets up Mocking for a HEAD request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: whether to record the request (default: false)\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to record the request\n * @return\n */\nexport function HEAD(\n owner: object,\n url: string,\n response: ResponseGenerator,\n // TODO: should we omit the `body` as well?\n // It does _seem_ possible for HEAD requests to have a body, but it should be ignored.\n // From MDN: Warning: If a response to a HEAD request has a body, the response body must be ignored.\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'HEAD',\n url,\n response: response(),\n }),\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warp-drive/holodeck",
|
|
3
3
|
"description": "⚡️ Simple, Fast HTTP Mocking for Tests",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.65",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chris Thoburn <runspired@users.noreply.github.com>",
|
|
7
7
|
"repository": {
|
|
@@ -32,16 +32,15 @@
|
|
|
32
32
|
"README.md",
|
|
33
33
|
"LICENSE.md",
|
|
34
34
|
"server",
|
|
35
|
-
"declarations",
|
|
36
35
|
"logos"
|
|
37
36
|
],
|
|
38
37
|
"bin": {
|
|
39
38
|
"ensure-cert": "./server/ensure-cert.js"
|
|
40
39
|
},
|
|
41
40
|
"peerDependencies": {
|
|
42
|
-
"@warp-drive/utilities": "5.9.0-alpha.
|
|
43
|
-
"@warp-drive/legacy": "5.9.0-alpha.
|
|
44
|
-
"@warp-drive/core": "5.9.0-alpha.
|
|
41
|
+
"@warp-drive/utilities": "5.9.0-alpha.22",
|
|
42
|
+
"@warp-drive/legacy": "5.9.0-alpha.22",
|
|
43
|
+
"@warp-drive/core": "5.9.0-alpha.22"
|
|
45
44
|
},
|
|
46
45
|
"peerDependenciesMeta": {
|
|
47
46
|
"@warp-drive/utilities": {
|
|
@@ -57,11 +56,11 @@
|
|
|
57
56
|
"@babel/preset-env": "^7.28.3",
|
|
58
57
|
"@babel/preset-typescript": "^7.27.1",
|
|
59
58
|
"@babel/runtime": "^7.28.3",
|
|
60
|
-
"@warp-drive/utilities": "5.9.0-alpha.
|
|
61
|
-
"@warp-drive/legacy": "5.9.0-alpha.
|
|
62
|
-
"@warp-drive/core": "5.9.0-alpha.
|
|
63
|
-
"@warp-drive/internal-config": "5.9.0-alpha.
|
|
64
|
-
"
|
|
59
|
+
"@warp-drive/utilities": "5.9.0-alpha.22",
|
|
60
|
+
"@warp-drive/legacy": "5.9.0-alpha.22",
|
|
61
|
+
"@warp-drive/core": "5.9.0-alpha.22",
|
|
62
|
+
"@warp-drive/internal-config": "5.9.0-alpha.22",
|
|
63
|
+
"tsdown": "^0.22.14"
|
|
65
64
|
},
|
|
66
65
|
"exports": {
|
|
67
66
|
".": {
|
|
@@ -69,25 +68,25 @@
|
|
|
69
68
|
"bun": "./server/index.js",
|
|
70
69
|
"deno": "./server/index.js",
|
|
71
70
|
"browser": {
|
|
72
|
-
"types": "./
|
|
71
|
+
"types": "./dist/index.d.ts",
|
|
73
72
|
"default": "./dist/index.js"
|
|
74
73
|
},
|
|
75
74
|
"import": {
|
|
76
|
-
"types": "./
|
|
75
|
+
"types": "./dist/index.d.ts",
|
|
77
76
|
"default": "./dist/index.js"
|
|
78
77
|
},
|
|
79
78
|
"default": "./server/index.js"
|
|
80
79
|
},
|
|
81
80
|
"./mock": {
|
|
82
|
-
"types": "./
|
|
81
|
+
"types": "./dist/mock.d.ts",
|
|
83
82
|
"default": "./dist/mock.js"
|
|
84
83
|
}
|
|
85
84
|
},
|
|
86
85
|
"scripts": {
|
|
87
|
-
"check:
|
|
88
|
-
"build:pkg": "
|
|
86
|
+
"check:types": "tsc --noEmit",
|
|
87
|
+
"build:pkg": "node node_modules/tsdown/dist/run.mjs",
|
|
89
88
|
"sync": "echo \"syncing\"",
|
|
90
89
|
"_temporarily_deactivated_lint": "eslint . --quiet --cache --cache-strategy=content",
|
|
91
|
-
"start": "
|
|
90
|
+
"start": "node node_modules/tsdown/dist/run.mjs --watch"
|
|
92
91
|
}
|
|
93
92
|
}
|
package/server/bun.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { Worker, threadId, parentPort } from 'node:worker_threads';
|
|
10
10
|
import {
|
|
11
|
+
bindWithRetry,
|
|
11
12
|
compress,
|
|
12
13
|
createCloseHandler,
|
|
13
14
|
DEFAULT_PORT,
|
|
@@ -307,15 +308,17 @@ async function _createServer(options) {
|
|
|
307
308
|
hostname: options.hostname ?? 'localhost',
|
|
308
309
|
};
|
|
309
310
|
|
|
310
|
-
const server =
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
const server = await bindWithRetry(() =>
|
|
312
|
+
Bun.serve({
|
|
313
|
+
fetch: app.fetch,
|
|
314
|
+
tls: {
|
|
315
|
+
key: KEY,
|
|
316
|
+
cert: CERT,
|
|
317
|
+
},
|
|
318
|
+
port: location.port,
|
|
319
|
+
hostname: location.hostname,
|
|
320
|
+
})
|
|
321
|
+
);
|
|
319
322
|
|
|
320
323
|
console.log(
|
|
321
324
|
`\tServing Holodeck HTTP Mocks from ${chalk.yellow('https://') + chalk.magenta(location.hostname + ':') + chalk.yellow(location.port)}\n`
|
package/server/node.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { Worker, threadId, parentPort } from 'node:worker_threads';
|
|
11
11
|
import {
|
|
12
|
+
bindWithRetry,
|
|
12
13
|
compress,
|
|
13
14
|
createCloseHandler,
|
|
14
15
|
DEFAULT_PORT,
|
|
@@ -308,23 +309,25 @@ async function _createServer(options) {
|
|
|
308
309
|
hostname: options.hostname ?? 'localhost',
|
|
309
310
|
};
|
|
310
311
|
|
|
311
|
-
const server =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
312
|
+
const server = await bindWithRetry(() =>
|
|
313
|
+
serve({
|
|
314
|
+
overrideGlobalObjects: true,
|
|
315
|
+
fetch: app.fetch,
|
|
316
|
+
serverOptions: {
|
|
317
|
+
key: KEY,
|
|
318
|
+
cert: CERT,
|
|
319
|
+
// rejectUnauthorized: false,
|
|
320
|
+
// enableTrace: true,
|
|
321
|
+
// Allow HTTP/1.1 fallback for ALPN negotiation
|
|
322
|
+
// allowHTTP1: true,
|
|
323
|
+
// ALPNProtocols: ['h2', 'http/1.1', 'http/1.0'],
|
|
324
|
+
// origins: ['*'],
|
|
325
|
+
},
|
|
326
|
+
createServer: createSecureServer,
|
|
327
|
+
port: location.port,
|
|
328
|
+
hostname: location.hostname,
|
|
329
|
+
})
|
|
330
|
+
);
|
|
328
331
|
|
|
329
332
|
console.log(
|
|
330
333
|
`\tServing Holodeck HTTP Mocks from ${chalk.yellow('https://') + chalk.magenta(location.hostname + ':') + chalk.yellow(location.port)}\n`
|
package/server/utils.js
CHANGED
|
@@ -127,3 +127,58 @@ export function createCloseHandler(cb) {
|
|
|
127
127
|
cb();
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Binds a server created by `createServerFn()`, retrying on the same port if
|
|
133
|
+
* it's already in use.
|
|
134
|
+
*
|
|
135
|
+
* Holodeck's port is never renegotiated on conflict -- every test app's
|
|
136
|
+
* client-side test-helper derives holodeck's URL as `window.location.port +
|
|
137
|
+
* 1` (the diagnostic server's own port, plus one), so holodeck must bind
|
|
138
|
+
* exactly the port it was asked for or the browser can never find it.
|
|
139
|
+
* Instead, this retries the *same* port a few times with a short delay, to
|
|
140
|
+
* ride out the narrow window where a concurrently-starting sibling process's
|
|
141
|
+
* diagnostic server hasn't released this exact port yet (see
|
|
142
|
+
* packages/diagnostic/server/utils/port-lock.js for the reservation scheme
|
|
143
|
+
* that's supposed to prevent that from happening at all -- this is a
|
|
144
|
+
* defense-in-depth fallback for anything outside that convention).
|
|
145
|
+
*
|
|
146
|
+
* `createServerFn` must synchronously create the server and start binding,
|
|
147
|
+
* returning the server instance. Some implementations (Bun.serve) throw
|
|
148
|
+
* synchronously on a bind failure; others (Node's http/http2 `.listen()`,
|
|
149
|
+
* which is what @hono/node-server uses under the hood) bind asynchronously
|
|
150
|
+
* and emit an 'error' event on the returned server instead of throwing. This
|
|
151
|
+
* handles both.
|
|
152
|
+
*/
|
|
153
|
+
export async function bindWithRetry(createServerFn, { maxAttempts = 5, retryDelayMs = 150 } = {}) {
|
|
154
|
+
for (let attempt = 1; ; attempt++) {
|
|
155
|
+
try {
|
|
156
|
+
const server = createServerFn();
|
|
157
|
+
if (typeof server?.on !== 'function') {
|
|
158
|
+
// no event emitter to observe an async bind failure on -- the
|
|
159
|
+
// synchronous creation above already succeeded, so assume the bind
|
|
160
|
+
// did too (this is the Bun.serve path, which throws synchronously
|
|
161
|
+
// instead)
|
|
162
|
+
return server;
|
|
163
|
+
}
|
|
164
|
+
const bindError = await new Promise((resolve) => {
|
|
165
|
+
const onError = (e) => resolve(e);
|
|
166
|
+
server.once('error', onError);
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
server.off('error', onError);
|
|
169
|
+
resolve(null);
|
|
170
|
+
}, retryDelayMs);
|
|
171
|
+
});
|
|
172
|
+
if (!bindError) {
|
|
173
|
+
return server;
|
|
174
|
+
}
|
|
175
|
+
throw bindError;
|
|
176
|
+
} catch (e) {
|
|
177
|
+
if (e?.code !== 'EADDRINUSE' || attempt >= maxAttempts) {
|
|
178
|
+
throw e;
|
|
179
|
+
}
|
|
180
|
+
console.log(`\tPort in use, retrying bind (attempt ${attempt}/${maxAttempts})...`);
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/declarations/index.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { Handler, NextFn } from "@warp-drive/core/request";
|
|
2
|
-
import type { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/request";
|
|
3
|
-
import type { Store } from "@warp-drive/legacy/store";
|
|
4
|
-
import type { ScaffoldGenerator } from "./mock.js";
|
|
5
|
-
/**
|
|
6
|
-
* @public
|
|
7
|
-
*/
|
|
8
|
-
export declare function setConfig({ host }: {
|
|
9
|
-
host: string;
|
|
10
|
-
}): void;
|
|
11
|
-
/**
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export declare function setTestId(context: object, str: string | null): void;
|
|
15
|
-
/**
|
|
16
|
-
* @public
|
|
17
|
-
*/
|
|
18
|
-
export declare function setIsRecording(value: boolean): void;
|
|
19
|
-
/**
|
|
20
|
-
* @public
|
|
21
|
-
*/
|
|
22
|
-
export declare function getIsRecording(): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* A request handler that intercepts requests and routes them through
|
|
25
|
-
* the Holodeck mock server.
|
|
26
|
-
*
|
|
27
|
-
* This handler modifies the request URL to include test identifiers
|
|
28
|
-
* and manages request counts for accurate mocking.
|
|
29
|
-
*
|
|
30
|
-
* Requires that the test context be configured with a testId using `setTestId`.
|
|
31
|
-
*
|
|
32
|
-
* @param owner - the test context object used to retrieve the test ID.
|
|
33
|
-
*/
|
|
34
|
-
export declare class MockServerHandler implements Handler {
|
|
35
|
-
owner: object;
|
|
36
|
-
constructor(owner: object);
|
|
37
|
-
request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>>;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Creates an adapterFor function that wraps the provided adapterFor function
|
|
41
|
-
* to override the adapter's _fetchRequest method to route requests through
|
|
42
|
-
* the Holodeck mock server.
|
|
43
|
-
*
|
|
44
|
-
* @param owner - The test context object used to retrieve the test ID.
|
|
45
|
-
*/
|
|
46
|
-
export declare function installAdapterFor(owner: object, store: Store): void;
|
|
47
|
-
/**
|
|
48
|
-
* Mock a request by sending the scaffold to the mock server.
|
|
49
|
-
*
|
|
50
|
-
* @public
|
|
51
|
-
*/
|
|
52
|
-
export declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
|