@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/mock.js CHANGED
@@ -1,153 +1,199 @@
1
- import { mock, getIsRecording } from './index.js';
1
+ import { getIsRecording, mock } from "./index.js";
2
2
 
3
+ //#region src/mock.ts
3
4
  /**
4
- * @public
5
- */
6
-
7
- /**
8
- * @public
9
- */
10
-
11
- /**
12
- * @public
13
- */
14
-
15
- /**
16
- * Sets up Mocking for a GET request on the mock server
17
- * for the supplied url.
18
- *
19
- * The response body is generated by the supplied response function.
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
- return mock(owner, () => ({
34
- status: options?.status ?? 200,
35
- statusText: options?.statusText ?? 'OK',
36
- headers: options?.headers ?? {},
37
- body: options?.body ?? null,
38
- method: 'GET',
39
- url,
40
- response: response()
41
- }), getIsRecording() || (options?.RECORD ?? false));
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([[200, 'OK'], [201, 'Created'], [202, 'Accepted'], [203, 'Non-Authoritative Information'], [204, 'No Content'], [205, 'Reset Content'], [206, 'Partial Content'], [207, 'Multi-Status'], [208, 'Already Reported'], [226, 'IM Used'], [300, 'Multiple Choices'], [301, 'Moved Permanently'], [302, 'Found'], [303, 'See Other'], [304, 'Not Modified'], [307, 'Temporary Redirect'], [308, 'Permanent Redirect'], [400, 'Bad Request'], [401, 'Unauthorized'], [402, 'Payment Required'], [403, 'Forbidden'], [404, 'Not Found'], [405, 'Method Not Allowed'], [406, 'Not Acceptable'], [407, 'Proxy Authentication Required'], [408, 'Request Timeout'], [409, 'Conflict'], [410, 'Gone'], [411, 'Length Required'], [412, 'Precondition Failed'], [413, 'Payload Too Large'], [414, 'URI Too Long'], [415, 'Unsupported Media Type'], [416, 'Range Not Satisfiable'], [417, 'Expectation Failed'], [419, 'Page Expired'], [420, 'Enhance Your Calm'], [421, 'Misdirected Request'], [422, 'Unprocessable Entity'], [423, 'Locked'], [424, 'Failed Dependency'], [425, 'Too Early'], [426, 'Upgrade Required'], [428, 'Precondition Required'], [429, 'Too Many Requests'], [430, 'Request Header Fields Too Large'], [431, 'Request Header Fields Too Large'], [450, 'Blocked By Windows Parental Controls'], [451, 'Unavailable For Legal Reasons'], [500, 'Internal Server Error'], [501, 'Not Implemented'], [502, 'Bad Gateway'], [503, 'Service Unavailable'], [504, 'Gateway Timeout'], [505, 'HTTP Version Not Supported'], [506, 'Variant Also Negotiates'], [507, 'Insufficient Storage'], [508, 'Loop Detected'], [509, 'Bandwidth Limit Exceeded'], [510, 'Not Extended'], [511, 'Network Authentication Required']]);
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
- * Mock a POST request
47
- */
96
+ * Mock a POST request
97
+ */
48
98
  function POST(owner, url, response, options) {
49
- return mock(owner, () => {
50
- const body = response();
51
- const status = options?.status ?? (body ? 201 : 204);
52
- return {
53
- status: status,
54
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
55
- headers: options?.headers ?? {},
56
- body: options?.body ?? null,
57
- method: 'POST',
58
- url,
59
- response: body
60
- };
61
- }, getIsRecording() || (options?.RECORD ?? false));
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
- * mock a PUT request
66
- */
114
+ * mock a PUT request
115
+ */
67
116
  function PUT(owner, url, response, options) {
68
- return mock(owner, () => {
69
- const body = response();
70
- const status = options?.status ?? (body ? 200 : 204);
71
- return {
72
- status: status,
73
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
74
- headers: options?.headers ?? {},
75
- body: options?.body ?? null,
76
- method: 'PUT',
77
- url,
78
- response: body
79
- };
80
- }, getIsRecording() || (options?.RECORD ?? false));
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
- * mock a PATCH request
84
- *
85
- */
132
+ * mock a PATCH request
133
+ *
134
+ */
86
135
  function PATCH(owner, url, response, options) {
87
- return mock(owner, () => {
88
- const body = response();
89
- const status = options?.status ?? (body ? 200 : 204);
90
- return {
91
- status: status,
92
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
93
- headers: options?.headers ?? {},
94
- body: options?.body ?? null,
95
- method: 'PATCH',
96
- url,
97
- response: body
98
- };
99
- }, getIsRecording() || (options?.RECORD ?? false));
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
- * mock a DELETE request
103
- */
151
+ * mock a DELETE request
152
+ */
104
153
  function DELETE(owner, url, response, options) {
105
- return mock(owner, () => {
106
- const body = response();
107
- const status = options?.status ?? (body ? 200 : 204);
108
- return {
109
- status: status,
110
- statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
111
- headers: options?.headers ?? {},
112
- body: options?.body ?? null,
113
- method: 'DELETE',
114
- url,
115
- response: body
116
- };
117
- }, getIsRecording() || (options?.RECORD ?? false));
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
- * Sets up Mocking for a HEAD request on the mock server
122
- * for the supplied url.
123
- *
124
- * The response body is generated by the supplied response function.
125
- *
126
- * Available options:
127
- * - status: the status code to return (default: 200)
128
- * - headers: the headers to return (default: {})
129
- * - body: the body to match against for the request (default: null)
130
- * - RECORD: whether to record the request (default: false)
131
- *
132
- * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
133
- * @param response a function which generates the response to return
134
- * @param options status, headers for the response, body to match against for the request, and whether to record the request
135
- * @return
136
- */
137
- function HEAD(owner, url, response,
138
- // TODO: should we omit the `body` as well?
139
- // It does _seem_ possible for HEAD requests to have a body, but it should be ignored.
140
- // From MDN: Warning: If a response to a HEAD request has a body, the response body must be ignored.
141
- options) {
142
- return mock(owner, () => ({
143
- status: options?.status ?? 200,
144
- statusText: options?.statusText ?? 'OK',
145
- headers: options?.headers ?? {},
146
- body: options?.body ?? null,
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
@@ -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.63",
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.20",
43
- "@warp-drive/legacy": "5.9.0-alpha.20",
44
- "@warp-drive/core": "5.9.0-alpha.20"
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.20",
61
- "@warp-drive/legacy": "5.9.0-alpha.20",
62
- "@warp-drive/core": "5.9.0-alpha.20",
63
- "@warp-drive/internal-config": "5.9.0-alpha.20",
64
- "vite": "^7.3.1"
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": "./declarations/index.d.ts",
71
+ "types": "./dist/index.d.ts",
73
72
  "default": "./dist/index.js"
74
73
  },
75
74
  "import": {
76
- "types": "./declarations/index.d.ts",
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": "./declarations/mock.d.ts",
81
+ "types": "./dist/mock.d.ts",
83
82
  "default": "./dist/mock.js"
84
83
  }
85
84
  },
86
85
  "scripts": {
87
- "check:pkg-types": "tsc --noEmit",
88
- "build:pkg": "vite build",
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": "vite"
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 = Bun.serve({
311
- fetch: app.fetch,
312
- tls: {
313
- key: KEY,
314
- cert: CERT,
315
- },
316
- port: location.port,
317
- hostname: location.hostname,
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 = serve({
312
- overrideGlobalObjects: true,
313
- fetch: app.fetch,
314
- serverOptions: {
315
- key: KEY,
316
- cert: CERT,
317
- // rejectUnauthorized: false,
318
- // enableTrace: true,
319
- // Allow HTTP/1.1 fallback for ALPN negotiation
320
- // allowHTTP1: true,
321
- // ALPNProtocols: ['h2', 'http/1.1', 'http/1.0'],
322
- // origins: ['*'],
323
- },
324
- createServer: createSecureServer,
325
- port: location.port,
326
- hostname: location.hostname,
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
+ }
@@ -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>;