@waitkit/core 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 전준연
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @waitkit/core
2
+
3
+ Development-only fetch interceptor for intentionally testing loading, skeleton,
4
+ error, timeout, and retry states.
5
+
6
+ `@waitkit/core` patches `globalThis.fetch` and applies rules or named scenarios
7
+ to matching requests. It is intended for local development and UI state testing,
8
+ not for production network control.
9
+
10
+ ## Installation
11
+
12
+ Install it as a development dependency if you only load Waitkit in development:
13
+
14
+ ```bash
15
+ npm install -D @waitkit/core
16
+ # or
17
+ pnpm add -D @waitkit/core
18
+ # or
19
+ yarn add -D @waitkit/core
20
+ # or
21
+ bun add -d @waitkit/core
22
+ ```
23
+
24
+ If your production source imports `@waitkit/core` directly, install it as a
25
+ regular dependency instead:
26
+
27
+ ```bash
28
+ npm install @waitkit/core
29
+ pnpm add @waitkit/core
30
+ yarn add @waitkit/core
31
+ bun add @waitkit/core
32
+ ```
33
+
34
+ A common pattern is to load it only in development:
35
+
36
+ ```ts
37
+ if (import.meta.env.DEV) {
38
+ const { setupWaitKit } = await import('@waitkit/core');
39
+
40
+ setupWaitKit({
41
+ rules: [{ url: '/api/users', delay: 1000 }],
42
+ });
43
+ }
44
+ ```
45
+
46
+ ## Basic Usage
47
+
48
+ ```ts
49
+ import { setupWaitKit } from '@waitkit/core';
50
+
51
+ const waitkit = setupWaitKit({
52
+ enabled: import.meta.env.DEV,
53
+ rules: [{ url: '/api/users', method: 'GET', delay: 1200 }],
54
+ });
55
+
56
+ waitkit.restore();
57
+ ```
58
+
59
+ ## Scenarios
60
+
61
+ Use scenarios when you want to switch between named network states while
62
+ developing a screen.
63
+
64
+ ```ts
65
+ import { setupWaitKit } from '@waitkit/core';
66
+
67
+ const waitkit = setupWaitKit({
68
+ enabled: import.meta.env.DEV,
69
+ activeScenario: 'slow-network',
70
+ scenarios: {
71
+ 'slow-network': [{ url: '/api/users', method: 'GET', delay: [800, 2000] }],
72
+ 'server-error': [
73
+ {
74
+ url: '/api/payment',
75
+ method: 'POST',
76
+ delay: 500,
77
+ errorRate: 1,
78
+ errorResponse: {
79
+ status: 500,
80
+ body: { message: 'Payment failed' },
81
+ },
82
+ },
83
+ ],
84
+ timeout: [{ url: '/api/report', timeoutRate: 1, timeoutMs: 5000 }],
85
+ },
86
+ });
87
+
88
+ waitkit.setScenario('server-error');
89
+ waitkit.resetScenario();
90
+ waitkit.restore();
91
+ ```
92
+
93
+ ## Error Responses
94
+
95
+ ```ts
96
+ setupWaitKit({
97
+ enabled: import.meta.env.DEV,
98
+ rules: [
99
+ {
100
+ url: '/api/login',
101
+ method: 'POST',
102
+ errorRate: 1,
103
+ errorResponse: {
104
+ status: 401,
105
+ body: { message: 'Invalid credentials' },
106
+ },
107
+ },
108
+ ],
109
+ });
110
+ ```
111
+
112
+ ## Timeouts
113
+
114
+ ```ts
115
+ setupWaitKit({
116
+ enabled: import.meta.env.DEV,
117
+ rules: [{ url: '/api/report', timeoutRate: 1, timeoutMs: 5000 }],
118
+ });
119
+ ```
120
+
121
+ ## Controller API
122
+
123
+ `setupWaitKit` returns a controller:
124
+
125
+ - `enable()`: Enables rule matching.
126
+ - `disable()`: Bypasses all rules and calls the original `fetch`.
127
+ - `restore()`: Restores the original `fetch`.
128
+ - `isEnabled()`: Returns whether rule matching is enabled.
129
+ - `setScenario(name)`: Uses a named scenario.
130
+ - `getScenario()`: Returns the active scenario name.
131
+ - `resetScenario()`: Clears the active scenario and falls back to `rules`.
132
+
133
+ ## Rule Options
134
+
135
+ - `url`: `string`, `RegExp`, or `(url: string) => boolean`.
136
+ - `method`: HTTP method or method array.
137
+ - `delay`: Fixed milliseconds or `[min, max]` random range.
138
+ - `errorRate`: Probability from `0` to `1` for returning an error response.
139
+ - `errorResponse`: Simulated response status, headers, status text, and body.
140
+ - `timeoutRate`: Probability from `0` to `1` for throwing a timeout error.
141
+ - `timeoutMs`: Time to wait before throwing the timeout error.
142
+
143
+ The first matching rule is applied.
144
+
145
+ ## Events
146
+
147
+ ```ts
148
+ setupWaitKit({
149
+ enabled: import.meta.env.DEV,
150
+ rules: [{ url: '/api/users', delay: 1000 }],
151
+ onRequest: (event) => console.log(event.url),
152
+ onMatch: (event) => console.log(event.rule),
153
+ onDelayStart: (event) => console.log(event.delayMs),
154
+ onDelayEnd: (event) => console.log(event.delayMs),
155
+ onError: (event) => console.error(event.error),
156
+ });
157
+ ```
158
+
159
+ ## Production Safety
160
+
161
+ Waitkit does not force development-only usage. Gate it explicitly with your
162
+ environment flag:
163
+
164
+ ```ts
165
+ setupWaitKit({
166
+ enabled: import.meta.env.DEV,
167
+ rules: [{ url: '/api/users', delay: 1000 }],
168
+ });
169
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,314 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var WaitKitTimeoutError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "WaitKitTimeoutError";
8
+ }
9
+ };
10
+
11
+ // src/delay.ts
12
+ function resolveDelay(delay) {
13
+ if (delay === void 0) {
14
+ return 0;
15
+ }
16
+ if (typeof delay === "number") {
17
+ return delay;
18
+ }
19
+ const [min, max] = delay;
20
+ return Math.floor(min + Math.random() * (max - min + 1));
21
+ }
22
+ function sleep(ms) {
23
+ return new Promise((resolve) => {
24
+ setTimeout(resolve, ms);
25
+ });
26
+ }
27
+ function validateDelay(delay) {
28
+ if (delay === void 0) {
29
+ return;
30
+ }
31
+ if (typeof delay === "number") {
32
+ assertNonNegativeFiniteNumber(delay, "delay");
33
+ return;
34
+ }
35
+ const [min, max] = delay;
36
+ assertNonNegativeFiniteNumber(min, "delay min");
37
+ assertNonNegativeFiniteNumber(max, "delay max");
38
+ if (min > max) {
39
+ throw new Error("WaitKit delay range must have min less than or equal to max.");
40
+ }
41
+ }
42
+ function validateRate(rate, name) {
43
+ if (rate === void 0) {
44
+ return;
45
+ }
46
+ if (!Number.isFinite(rate) || rate < 0 || rate > 1) {
47
+ throw new Error(`WaitKit ${name} must be a number between 0 and 1.`);
48
+ }
49
+ }
50
+ function validateTimeoutMs(timeoutMs) {
51
+ if (timeoutMs === void 0) {
52
+ return;
53
+ }
54
+ assertNonNegativeFiniteNumber(timeoutMs, "timeoutMs");
55
+ }
56
+ function assertNonNegativeFiniteNumber(value, name) {
57
+ if (!Number.isFinite(value) || value < 0) {
58
+ throw new Error(`WaitKit ${name} must be a non-negative finite number.`);
59
+ }
60
+ }
61
+
62
+ // src/matcher.ts
63
+ function matchesRule(rule, url, method) {
64
+ return matchesUrl(rule.url, url) && matchesMethod(rule.method, method);
65
+ }
66
+ function matchesUrl(ruleUrl, url) {
67
+ if (typeof ruleUrl === "string") {
68
+ return url.includes(ruleUrl);
69
+ }
70
+ if (ruleUrl instanceof RegExp) {
71
+ return ruleUrl.test(url);
72
+ }
73
+ return ruleUrl(url);
74
+ }
75
+ function matchesMethod(ruleMethod, method) {
76
+ if (ruleMethod === void 0) {
77
+ return true;
78
+ }
79
+ const normalizedMethod = normalizeMethod(method);
80
+ if (typeof ruleMethod === "string") {
81
+ return normalizeMethod(ruleMethod) === normalizedMethod;
82
+ }
83
+ return ruleMethod.some((item) => normalizeMethod(item) === normalizedMethod);
84
+ }
85
+ function normalizeMethod(method) {
86
+ return (method ?? "GET").toUpperCase();
87
+ }
88
+ function getRequestUrl(input) {
89
+ if (typeof input === "string") {
90
+ return input;
91
+ }
92
+ if (input instanceof URL) {
93
+ return input.toString();
94
+ }
95
+ return input.url;
96
+ }
97
+ function getRequestMethod(input, init) {
98
+ if (init?.method !== void 0) {
99
+ return normalizeMethod(init.method);
100
+ }
101
+ if (typeof input === "string" || input instanceof URL) {
102
+ return "GET";
103
+ }
104
+ return normalizeMethod(input.method);
105
+ }
106
+
107
+ // src/response.ts
108
+ function createErrorResponse(errorResponse) {
109
+ const status = errorResponse?.status ?? 500;
110
+ const headers = new Headers(errorResponse?.headers);
111
+ const body = serializeBody(errorResponse?.body, headers);
112
+ return new Response(body, {
113
+ status,
114
+ statusText: errorResponse?.statusText,
115
+ headers
116
+ });
117
+ }
118
+ function serializeBody(body, headers) {
119
+ if (body === void 0 || body === null) {
120
+ return null;
121
+ }
122
+ if (typeof body === "string" || isBlob(body) || isFormData(body)) {
123
+ return body;
124
+ }
125
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || isUrlSearchParams(body)) {
126
+ return body;
127
+ }
128
+ if (!headers.has("content-type")) {
129
+ headers.set("content-type", "application/json");
130
+ }
131
+ return JSON.stringify(body);
132
+ }
133
+ function isBlob(body) {
134
+ return typeof Blob !== "undefined" && body instanceof Blob;
135
+ }
136
+ function isFormData(body) {
137
+ return typeof FormData !== "undefined" && body instanceof FormData;
138
+ }
139
+ function isUrlSearchParams(body) {
140
+ return typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
141
+ }
142
+
143
+ // src/setup-wait-kit.ts
144
+ var DEFAULT_TIMEOUT_MS = 3e4;
145
+ function setupWaitKit(options) {
146
+ validateRuntime();
147
+ validateOptions(options);
148
+ const originalFetch = globalThis.fetch;
149
+ let enabled = options.enabled ?? true;
150
+ let activeScenario = options.activeScenario;
151
+ let restored = false;
152
+ const patchedFetch = async (input, init) => {
153
+ if (!enabled) {
154
+ return originalFetch.call(globalThis, input, init);
155
+ }
156
+ const requestEvent = createRequestEvent(input, init);
157
+ options.onRequest?.(requestEvent);
158
+ const rule = findMatchingRule(getActiveRules(), requestEvent.url, requestEvent.method);
159
+ if (rule === void 0) {
160
+ return originalFetch.call(globalThis, input, init);
161
+ }
162
+ const delayMs = resolveDelay(rule.delay);
163
+ const matchEvent = createMatchEvent(requestEvent, rule, activeScenario, delayMs);
164
+ options.onMatch?.(matchEvent);
165
+ if (delayMs > 0) {
166
+ options.onDelayStart?.(matchEvent);
167
+ debug(options, `${requestEvent.method} ${requestEvent.url} delayed by ${delayMs}ms`);
168
+ await sleep(delayMs);
169
+ options.onDelayEnd?.(matchEvent);
170
+ }
171
+ if (shouldTrigger(rule.timeoutRate)) {
172
+ const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
173
+ const error = new WaitKitTimeoutError(
174
+ `WaitKit timed out ${requestEvent.method} ${requestEvent.url} after ${timeoutMs}ms.`
175
+ );
176
+ emitError(options, matchEvent, error);
177
+ debug(options, `${requestEvent.method} ${requestEvent.url} timed out after ${timeoutMs}ms`);
178
+ await sleep(timeoutMs);
179
+ throw error;
180
+ }
181
+ if (shouldTrigger(rule.errorRate)) {
182
+ const response = createErrorResponse(rule.errorResponse);
183
+ const error = new Error(
184
+ `WaitKit simulated ${response.status} response for ${requestEvent.method} ${requestEvent.url}.`
185
+ );
186
+ emitError(options, matchEvent, error);
187
+ debug(options, `${requestEvent.method} ${requestEvent.url} returned ${response.status}`);
188
+ return response;
189
+ }
190
+ return originalFetch.call(globalThis, input, init);
191
+ };
192
+ globalThis.fetch = patchedFetch;
193
+ function getActiveRules() {
194
+ if (activeScenario === void 0) {
195
+ return options.rules ?? [];
196
+ }
197
+ return options.scenarios?.[activeScenario] ?? [];
198
+ }
199
+ return {
200
+ enable() {
201
+ enabled = true;
202
+ if (restored) {
203
+ globalThis.fetch = patchedFetch;
204
+ restored = false;
205
+ }
206
+ },
207
+ disable() {
208
+ enabled = false;
209
+ },
210
+ restore() {
211
+ if (globalThis.fetch === patchedFetch) {
212
+ globalThis.fetch = originalFetch;
213
+ }
214
+ enabled = false;
215
+ restored = true;
216
+ },
217
+ isEnabled() {
218
+ return enabled;
219
+ },
220
+ setScenario(name) {
221
+ if (options.scenarios?.[name] === void 0) {
222
+ throw new Error(`WaitKit scenario "${name}" does not exist.`);
223
+ }
224
+ activeScenario = name;
225
+ },
226
+ getScenario() {
227
+ return activeScenario;
228
+ },
229
+ resetScenario() {
230
+ activeScenario = void 0;
231
+ }
232
+ };
233
+ }
234
+ function validateRuntime() {
235
+ if (typeof globalThis.fetch !== "function") {
236
+ throw new Error("WaitKit requires globalThis.fetch.");
237
+ }
238
+ if (typeof Response !== "function") {
239
+ throw new Error("WaitKit requires globalThis.Response.");
240
+ }
241
+ }
242
+ function validateOptions(options) {
243
+ validateRules(options.rules ?? []);
244
+ if (options.scenarios !== void 0) {
245
+ for (const rules of Object.values(options.scenarios)) {
246
+ validateRules(rules);
247
+ }
248
+ }
249
+ if (options.activeScenario !== void 0 && options.scenarios?.[options.activeScenario] === void 0) {
250
+ throw new Error(`WaitKit scenario "${options.activeScenario}" does not exist.`);
251
+ }
252
+ }
253
+ function validateRules(rules) {
254
+ for (const rule of rules) {
255
+ validateDelay(rule.delay);
256
+ validateRate(rule.errorRate, "errorRate");
257
+ validateRate(rule.timeoutRate, "timeoutRate");
258
+ validateTimeoutMs(rule.timeoutMs);
259
+ validateStatus(rule.errorResponse?.status);
260
+ }
261
+ }
262
+ function validateStatus(status) {
263
+ if (status === void 0) {
264
+ return;
265
+ }
266
+ if (!Number.isInteger(status) || status < 200 || status > 599) {
267
+ throw new Error("WaitKit errorResponse.status must be an integer between 200 and 599.");
268
+ }
269
+ }
270
+ function createRequestEvent(input, init) {
271
+ return {
272
+ input,
273
+ init,
274
+ url: getRequestUrl(input),
275
+ method: getRequestMethod(input, init)
276
+ };
277
+ }
278
+ function createMatchEvent(requestEvent, rule, scenario, delayMs) {
279
+ return {
280
+ ...requestEvent,
281
+ rule,
282
+ scenario,
283
+ delayMs
284
+ };
285
+ }
286
+ function findMatchingRule(rules, url, method) {
287
+ return rules.find((rule) => matchesRule(rule, url, method));
288
+ }
289
+ function shouldTrigger(rate) {
290
+ if (rate === void 0 || rate <= 0) {
291
+ return false;
292
+ }
293
+ if (rate >= 1) {
294
+ return true;
295
+ }
296
+ return Math.random() < rate;
297
+ }
298
+ function emitError(options, matchEvent, error) {
299
+ const errorEvent = {
300
+ ...matchEvent,
301
+ error
302
+ };
303
+ options.onError?.(errorEvent);
304
+ }
305
+ function debug(options, message) {
306
+ if (options.debug === true) {
307
+ console.info(`[waitkit] ${message}`);
308
+ }
309
+ }
310
+
311
+ exports.WaitKitTimeoutError = WaitKitTimeoutError;
312
+ exports.setupWaitKit = setupWaitKit;
313
+ //# sourceMappingURL=index.cjs.map
314
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/delay.ts","../src/matcher.ts","../src/response.ts","../src/setup-wait-kit.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export class WaitKitTimeoutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WaitKitTimeoutError';\n }\n}\n","import type { DelayValue } from './types';\n\nexport function resolveDelay(delay: DelayValue | undefined): number {\n if (delay === undefined) {\n return 0;\n }\n\n if (typeof delay === 'number') {\n return delay;\n }\n\n const [min, max] = delay;\n return Math.floor(min + Math.random() * (max - min + 1));\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport function validateDelay(delay: DelayValue | undefined): void {\n if (delay === undefined) {\n return;\n }\n\n if (typeof delay === 'number') {\n assertNonNegativeFiniteNumber(delay, 'delay');\n return;\n }\n\n const [min, max] = delay;\n assertNonNegativeFiniteNumber(min, 'delay min');\n assertNonNegativeFiniteNumber(max, 'delay max');\n\n if (min > max) {\n throw new Error('WaitKit delay range must have min less than or equal to max.');\n }\n}\n\nexport function validateRate(rate: number | undefined, name: string): void {\n if (rate === undefined) {\n return;\n }\n\n if (!Number.isFinite(rate) || rate < 0 || rate > 1) {\n throw new Error(`WaitKit ${name} must be a number between 0 and 1.`);\n }\n}\n\nexport function validateTimeoutMs(timeoutMs: number | undefined): void {\n if (timeoutMs === undefined) {\n return;\n }\n\n assertNonNegativeFiniteNumber(timeoutMs, 'timeoutMs');\n}\n\nfunction assertNonNegativeFiniteNumber(value: number, name: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`WaitKit ${name} must be a non-negative finite number.`);\n }\n}\n","import type { WaitKitRule } from './types';\n\nexport function matchesRule(rule: WaitKitRule, url: string, method: string): boolean {\n return matchesUrl(rule.url, url) && matchesMethod(rule.method, method);\n}\n\nfunction matchesUrl(ruleUrl: WaitKitRule['url'], url: string): boolean {\n if (typeof ruleUrl === 'string') {\n return url.includes(ruleUrl);\n }\n\n if (ruleUrl instanceof RegExp) {\n return ruleUrl.test(url);\n }\n\n return ruleUrl(url);\n}\n\nfunction matchesMethod(ruleMethod: WaitKitRule['method'], method: string): boolean {\n if (ruleMethod === undefined) {\n return true;\n }\n\n const normalizedMethod = normalizeMethod(method);\n\n if (typeof ruleMethod === 'string') {\n return normalizeMethod(ruleMethod) === normalizedMethod;\n }\n\n return ruleMethod.some((item) => normalizeMethod(item) === normalizedMethod);\n}\n\nexport function normalizeMethod(method: string | undefined): string {\n return (method ?? 'GET').toUpperCase();\n}\n\nexport function getRequestUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') {\n return input;\n }\n\n if (input instanceof URL) {\n return input.toString();\n }\n\n return input.url;\n}\n\nexport function getRequestMethod(input: RequestInfo | URL, init?: RequestInit): string {\n if (init?.method !== undefined) {\n return normalizeMethod(init.method);\n }\n\n if (typeof input === 'string' || input instanceof URL) {\n return 'GET';\n }\n\n return normalizeMethod(input.method);\n}\n","import type { WaitKitErrorResponse } from './types';\n\nexport function createErrorResponse(errorResponse: WaitKitErrorResponse | undefined): Response {\n const status = errorResponse?.status ?? 500;\n const headers = new Headers(errorResponse?.headers);\n const body = serializeBody(errorResponse?.body, headers);\n\n return new Response(body, {\n status,\n statusText: errorResponse?.statusText,\n headers,\n });\n}\n\nfunction serializeBody(body: unknown, headers: Headers): BodyInit | null {\n if (body === undefined || body === null) {\n return null;\n }\n\n if (typeof body === 'string' || isBlob(body) || isFormData(body)) {\n return body;\n }\n\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || isUrlSearchParams(body)) {\n return body as BodyInit;\n }\n\n if (!headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n\n return JSON.stringify(body);\n}\n\nfunction isBlob(body: unknown): body is Blob {\n return typeof Blob !== 'undefined' && body instanceof Blob;\n}\n\nfunction isFormData(body: unknown): body is FormData {\n return typeof FormData !== 'undefined' && body instanceof FormData;\n}\n\nfunction isUrlSearchParams(body: unknown): body is URLSearchParams {\n return typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams;\n}\n","import { resolveDelay, sleep, validateDelay, validateRate, validateTimeoutMs } from './delay';\nimport { WaitKitTimeoutError } from './errors';\nimport { getRequestMethod, getRequestUrl, matchesRule } from './matcher';\nimport { createErrorResponse } from './response';\nimport type {\n WaitKitController,\n WaitKitErrorEvent,\n WaitKitMatchEvent,\n WaitKitOptions,\n WaitKitRequestEvent,\n WaitKitRule,\n} from './types';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function setupWaitKit(options: WaitKitOptions): WaitKitController {\n validateRuntime();\n validateOptions(options);\n\n const originalFetch = globalThis.fetch;\n let enabled = options.enabled ?? true;\n let activeScenario = options.activeScenario;\n let restored = false;\n\n const patchedFetch: typeof fetch = async (input, init) => {\n if (!enabled) {\n return originalFetch.call(globalThis, input, init);\n }\n\n const requestEvent = createRequestEvent(input, init);\n options.onRequest?.(requestEvent);\n\n const rule = findMatchingRule(getActiveRules(), requestEvent.url, requestEvent.method);\n\n if (rule === undefined) {\n return originalFetch.call(globalThis, input, init);\n }\n\n const delayMs = resolveDelay(rule.delay);\n const matchEvent = createMatchEvent(requestEvent, rule, activeScenario, delayMs);\n options.onMatch?.(matchEvent);\n\n if (delayMs > 0) {\n options.onDelayStart?.(matchEvent);\n debug(options, `${requestEvent.method} ${requestEvent.url} delayed by ${delayMs}ms`);\n await sleep(delayMs);\n options.onDelayEnd?.(matchEvent);\n }\n\n if (shouldTrigger(rule.timeoutRate)) {\n const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const error = new WaitKitTimeoutError(\n `WaitKit timed out ${requestEvent.method} ${requestEvent.url} after ${timeoutMs}ms.`,\n );\n emitError(options, matchEvent, error);\n debug(options, `${requestEvent.method} ${requestEvent.url} timed out after ${timeoutMs}ms`);\n await sleep(timeoutMs);\n throw error;\n }\n\n if (shouldTrigger(rule.errorRate)) {\n const response = createErrorResponse(rule.errorResponse);\n const error = new Error(\n `WaitKit simulated ${response.status} response for ${requestEvent.method} ${requestEvent.url}.`,\n );\n emitError(options, matchEvent, error);\n debug(options, `${requestEvent.method} ${requestEvent.url} returned ${response.status}`);\n return response;\n }\n\n return originalFetch.call(globalThis, input, init);\n };\n\n globalThis.fetch = patchedFetch;\n\n function getActiveRules(): readonly WaitKitRule[] {\n if (activeScenario === undefined) {\n return options.rules ?? [];\n }\n\n return options.scenarios?.[activeScenario] ?? [];\n }\n\n return {\n enable() {\n enabled = true;\n\n if (restored) {\n globalThis.fetch = patchedFetch;\n restored = false;\n }\n },\n disable() {\n enabled = false;\n },\n restore() {\n if (globalThis.fetch === patchedFetch) {\n globalThis.fetch = originalFetch;\n }\n\n enabled = false;\n restored = true;\n },\n isEnabled() {\n return enabled;\n },\n setScenario(name: string) {\n if (options.scenarios?.[name] === undefined) {\n throw new Error(`WaitKit scenario \"${name}\" does not exist.`);\n }\n\n activeScenario = name;\n },\n getScenario() {\n return activeScenario;\n },\n resetScenario() {\n activeScenario = undefined;\n },\n };\n}\n\nfunction validateRuntime(): void {\n if (typeof globalThis.fetch !== 'function') {\n throw new Error('WaitKit requires globalThis.fetch.');\n }\n\n if (typeof Response !== 'function') {\n throw new Error('WaitKit requires globalThis.Response.');\n }\n}\n\nfunction validateOptions(options: WaitKitOptions): void {\n validateRules(options.rules ?? []);\n\n if (options.scenarios !== undefined) {\n for (const rules of Object.values(options.scenarios)) {\n validateRules(rules);\n }\n }\n\n if (\n options.activeScenario !== undefined &&\n options.scenarios?.[options.activeScenario] === undefined\n ) {\n throw new Error(`WaitKit scenario \"${options.activeScenario}\" does not exist.`);\n }\n}\n\nfunction validateRules(rules: readonly WaitKitRule[]): void {\n for (const rule of rules) {\n validateDelay(rule.delay);\n validateRate(rule.errorRate, 'errorRate');\n validateRate(rule.timeoutRate, 'timeoutRate');\n validateTimeoutMs(rule.timeoutMs);\n validateStatus(rule.errorResponse?.status);\n }\n}\n\nfunction validateStatus(status: number | undefined): void {\n if (status === undefined) {\n return;\n }\n\n if (!Number.isInteger(status) || status < 200 || status > 599) {\n throw new Error('WaitKit errorResponse.status must be an integer between 200 and 599.');\n }\n}\n\nfunction createRequestEvent(input: RequestInfo | URL, init?: RequestInit): WaitKitRequestEvent {\n return {\n input,\n init,\n url: getRequestUrl(input),\n method: getRequestMethod(input, init),\n };\n}\n\nfunction createMatchEvent(\n requestEvent: WaitKitRequestEvent,\n rule: WaitKitRule,\n scenario: string | undefined,\n delayMs: number,\n): WaitKitMatchEvent {\n return {\n ...requestEvent,\n rule,\n scenario,\n delayMs,\n };\n}\n\nfunction findMatchingRule(\n rules: readonly WaitKitRule[],\n url: string,\n method: string,\n): WaitKitRule | undefined {\n return rules.find((rule) => matchesRule(rule, url, method));\n}\n\nfunction shouldTrigger(rate: number | undefined): boolean {\n if (rate === undefined || rate <= 0) {\n return false;\n }\n\n if (rate >= 1) {\n return true;\n }\n\n return Math.random() < rate;\n}\n\nfunction emitError(options: WaitKitOptions, matchEvent: WaitKitMatchEvent, error: Error): void {\n const errorEvent: WaitKitErrorEvent = {\n ...matchEvent,\n error,\n };\n\n options.onError?.(errorEvent);\n}\n\nfunction debug(options: WaitKitOptions, message: string): void {\n if (options.debug === true) {\n console.info(`[waitkit] ${message}`);\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ declare class WaitKitTimeoutError extends Error {
2
+ constructor(message: string);
3
+ }
4
+
5
+ type DelayValue = number | readonly [min: number, max: number];
6
+ type UrlMatcher = string | RegExp | ((url: string) => boolean);
7
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
8
+ interface WaitKitErrorResponse {
9
+ status?: number;
10
+ statusText?: string;
11
+ headers?: HeadersInit;
12
+ body?: unknown;
13
+ }
14
+ interface WaitKitRule {
15
+ url: UrlMatcher;
16
+ method?: HttpMethod | readonly HttpMethod[];
17
+ delay?: DelayValue;
18
+ errorRate?: number;
19
+ timeoutRate?: number;
20
+ timeoutMs?: number;
21
+ errorResponse?: WaitKitErrorResponse;
22
+ }
23
+ interface WaitKitRequestEvent {
24
+ input: RequestInfo | URL;
25
+ init?: RequestInit;
26
+ url: string;
27
+ method: string;
28
+ }
29
+ interface WaitKitMatchEvent extends WaitKitRequestEvent {
30
+ rule: WaitKitRule;
31
+ scenario?: string;
32
+ delayMs: number;
33
+ }
34
+ type WaitKitDelayEvent = WaitKitMatchEvent;
35
+ interface WaitKitErrorEvent extends WaitKitMatchEvent {
36
+ error: Error;
37
+ }
38
+ interface WaitKitOptions {
39
+ enabled?: boolean;
40
+ rules?: readonly WaitKitRule[];
41
+ scenarios?: Record<string, readonly WaitKitRule[]>;
42
+ activeScenario?: string;
43
+ debug?: boolean;
44
+ onRequest?: (event: WaitKitRequestEvent) => void;
45
+ onMatch?: (event: WaitKitMatchEvent) => void;
46
+ onDelayStart?: (event: WaitKitDelayEvent) => void;
47
+ onDelayEnd?: (event: WaitKitDelayEvent) => void;
48
+ onError?: (event: WaitKitErrorEvent) => void;
49
+ }
50
+ interface WaitKitController {
51
+ enable: () => void;
52
+ disable: () => void;
53
+ restore: () => void;
54
+ isEnabled: () => boolean;
55
+ setScenario: (name: string) => void;
56
+ getScenario: () => string | undefined;
57
+ resetScenario: () => void;
58
+ }
59
+
60
+ declare function setupWaitKit(options: WaitKitOptions): WaitKitController;
61
+
62
+ export { type DelayValue, type HttpMethod, type UrlMatcher, type WaitKitController, type WaitKitDelayEvent, type WaitKitErrorEvent, type WaitKitErrorResponse, type WaitKitMatchEvent, type WaitKitOptions, type WaitKitRequestEvent, type WaitKitRule, WaitKitTimeoutError, setupWaitKit };
@@ -0,0 +1,62 @@
1
+ declare class WaitKitTimeoutError extends Error {
2
+ constructor(message: string);
3
+ }
4
+
5
+ type DelayValue = number | readonly [min: number, max: number];
6
+ type UrlMatcher = string | RegExp | ((url: string) => boolean);
7
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
8
+ interface WaitKitErrorResponse {
9
+ status?: number;
10
+ statusText?: string;
11
+ headers?: HeadersInit;
12
+ body?: unknown;
13
+ }
14
+ interface WaitKitRule {
15
+ url: UrlMatcher;
16
+ method?: HttpMethod | readonly HttpMethod[];
17
+ delay?: DelayValue;
18
+ errorRate?: number;
19
+ timeoutRate?: number;
20
+ timeoutMs?: number;
21
+ errorResponse?: WaitKitErrorResponse;
22
+ }
23
+ interface WaitKitRequestEvent {
24
+ input: RequestInfo | URL;
25
+ init?: RequestInit;
26
+ url: string;
27
+ method: string;
28
+ }
29
+ interface WaitKitMatchEvent extends WaitKitRequestEvent {
30
+ rule: WaitKitRule;
31
+ scenario?: string;
32
+ delayMs: number;
33
+ }
34
+ type WaitKitDelayEvent = WaitKitMatchEvent;
35
+ interface WaitKitErrorEvent extends WaitKitMatchEvent {
36
+ error: Error;
37
+ }
38
+ interface WaitKitOptions {
39
+ enabled?: boolean;
40
+ rules?: readonly WaitKitRule[];
41
+ scenarios?: Record<string, readonly WaitKitRule[]>;
42
+ activeScenario?: string;
43
+ debug?: boolean;
44
+ onRequest?: (event: WaitKitRequestEvent) => void;
45
+ onMatch?: (event: WaitKitMatchEvent) => void;
46
+ onDelayStart?: (event: WaitKitDelayEvent) => void;
47
+ onDelayEnd?: (event: WaitKitDelayEvent) => void;
48
+ onError?: (event: WaitKitErrorEvent) => void;
49
+ }
50
+ interface WaitKitController {
51
+ enable: () => void;
52
+ disable: () => void;
53
+ restore: () => void;
54
+ isEnabled: () => boolean;
55
+ setScenario: (name: string) => void;
56
+ getScenario: () => string | undefined;
57
+ resetScenario: () => void;
58
+ }
59
+
60
+ declare function setupWaitKit(options: WaitKitOptions): WaitKitController;
61
+
62
+ export { type DelayValue, type HttpMethod, type UrlMatcher, type WaitKitController, type WaitKitDelayEvent, type WaitKitErrorEvent, type WaitKitErrorResponse, type WaitKitMatchEvent, type WaitKitOptions, type WaitKitRequestEvent, type WaitKitRule, WaitKitTimeoutError, setupWaitKit };
package/dist/index.js ADDED
@@ -0,0 +1,311 @@
1
+ // src/errors.ts
2
+ var WaitKitTimeoutError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "WaitKitTimeoutError";
6
+ }
7
+ };
8
+
9
+ // src/delay.ts
10
+ function resolveDelay(delay) {
11
+ if (delay === void 0) {
12
+ return 0;
13
+ }
14
+ if (typeof delay === "number") {
15
+ return delay;
16
+ }
17
+ const [min, max] = delay;
18
+ return Math.floor(min + Math.random() * (max - min + 1));
19
+ }
20
+ function sleep(ms) {
21
+ return new Promise((resolve) => {
22
+ setTimeout(resolve, ms);
23
+ });
24
+ }
25
+ function validateDelay(delay) {
26
+ if (delay === void 0) {
27
+ return;
28
+ }
29
+ if (typeof delay === "number") {
30
+ assertNonNegativeFiniteNumber(delay, "delay");
31
+ return;
32
+ }
33
+ const [min, max] = delay;
34
+ assertNonNegativeFiniteNumber(min, "delay min");
35
+ assertNonNegativeFiniteNumber(max, "delay max");
36
+ if (min > max) {
37
+ throw new Error("WaitKit delay range must have min less than or equal to max.");
38
+ }
39
+ }
40
+ function validateRate(rate, name) {
41
+ if (rate === void 0) {
42
+ return;
43
+ }
44
+ if (!Number.isFinite(rate) || rate < 0 || rate > 1) {
45
+ throw new Error(`WaitKit ${name} must be a number between 0 and 1.`);
46
+ }
47
+ }
48
+ function validateTimeoutMs(timeoutMs) {
49
+ if (timeoutMs === void 0) {
50
+ return;
51
+ }
52
+ assertNonNegativeFiniteNumber(timeoutMs, "timeoutMs");
53
+ }
54
+ function assertNonNegativeFiniteNumber(value, name) {
55
+ if (!Number.isFinite(value) || value < 0) {
56
+ throw new Error(`WaitKit ${name} must be a non-negative finite number.`);
57
+ }
58
+ }
59
+
60
+ // src/matcher.ts
61
+ function matchesRule(rule, url, method) {
62
+ return matchesUrl(rule.url, url) && matchesMethod(rule.method, method);
63
+ }
64
+ function matchesUrl(ruleUrl, url) {
65
+ if (typeof ruleUrl === "string") {
66
+ return url.includes(ruleUrl);
67
+ }
68
+ if (ruleUrl instanceof RegExp) {
69
+ return ruleUrl.test(url);
70
+ }
71
+ return ruleUrl(url);
72
+ }
73
+ function matchesMethod(ruleMethod, method) {
74
+ if (ruleMethod === void 0) {
75
+ return true;
76
+ }
77
+ const normalizedMethod = normalizeMethod(method);
78
+ if (typeof ruleMethod === "string") {
79
+ return normalizeMethod(ruleMethod) === normalizedMethod;
80
+ }
81
+ return ruleMethod.some((item) => normalizeMethod(item) === normalizedMethod);
82
+ }
83
+ function normalizeMethod(method) {
84
+ return (method ?? "GET").toUpperCase();
85
+ }
86
+ function getRequestUrl(input) {
87
+ if (typeof input === "string") {
88
+ return input;
89
+ }
90
+ if (input instanceof URL) {
91
+ return input.toString();
92
+ }
93
+ return input.url;
94
+ }
95
+ function getRequestMethod(input, init) {
96
+ if (init?.method !== void 0) {
97
+ return normalizeMethod(init.method);
98
+ }
99
+ if (typeof input === "string" || input instanceof URL) {
100
+ return "GET";
101
+ }
102
+ return normalizeMethod(input.method);
103
+ }
104
+
105
+ // src/response.ts
106
+ function createErrorResponse(errorResponse) {
107
+ const status = errorResponse?.status ?? 500;
108
+ const headers = new Headers(errorResponse?.headers);
109
+ const body = serializeBody(errorResponse?.body, headers);
110
+ return new Response(body, {
111
+ status,
112
+ statusText: errorResponse?.statusText,
113
+ headers
114
+ });
115
+ }
116
+ function serializeBody(body, headers) {
117
+ if (body === void 0 || body === null) {
118
+ return null;
119
+ }
120
+ if (typeof body === "string" || isBlob(body) || isFormData(body)) {
121
+ return body;
122
+ }
123
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || isUrlSearchParams(body)) {
124
+ return body;
125
+ }
126
+ if (!headers.has("content-type")) {
127
+ headers.set("content-type", "application/json");
128
+ }
129
+ return JSON.stringify(body);
130
+ }
131
+ function isBlob(body) {
132
+ return typeof Blob !== "undefined" && body instanceof Blob;
133
+ }
134
+ function isFormData(body) {
135
+ return typeof FormData !== "undefined" && body instanceof FormData;
136
+ }
137
+ function isUrlSearchParams(body) {
138
+ return typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
139
+ }
140
+
141
+ // src/setup-wait-kit.ts
142
+ var DEFAULT_TIMEOUT_MS = 3e4;
143
+ function setupWaitKit(options) {
144
+ validateRuntime();
145
+ validateOptions(options);
146
+ const originalFetch = globalThis.fetch;
147
+ let enabled = options.enabled ?? true;
148
+ let activeScenario = options.activeScenario;
149
+ let restored = false;
150
+ const patchedFetch = async (input, init) => {
151
+ if (!enabled) {
152
+ return originalFetch.call(globalThis, input, init);
153
+ }
154
+ const requestEvent = createRequestEvent(input, init);
155
+ options.onRequest?.(requestEvent);
156
+ const rule = findMatchingRule(getActiveRules(), requestEvent.url, requestEvent.method);
157
+ if (rule === void 0) {
158
+ return originalFetch.call(globalThis, input, init);
159
+ }
160
+ const delayMs = resolveDelay(rule.delay);
161
+ const matchEvent = createMatchEvent(requestEvent, rule, activeScenario, delayMs);
162
+ options.onMatch?.(matchEvent);
163
+ if (delayMs > 0) {
164
+ options.onDelayStart?.(matchEvent);
165
+ debug(options, `${requestEvent.method} ${requestEvent.url} delayed by ${delayMs}ms`);
166
+ await sleep(delayMs);
167
+ options.onDelayEnd?.(matchEvent);
168
+ }
169
+ if (shouldTrigger(rule.timeoutRate)) {
170
+ const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
171
+ const error = new WaitKitTimeoutError(
172
+ `WaitKit timed out ${requestEvent.method} ${requestEvent.url} after ${timeoutMs}ms.`
173
+ );
174
+ emitError(options, matchEvent, error);
175
+ debug(options, `${requestEvent.method} ${requestEvent.url} timed out after ${timeoutMs}ms`);
176
+ await sleep(timeoutMs);
177
+ throw error;
178
+ }
179
+ if (shouldTrigger(rule.errorRate)) {
180
+ const response = createErrorResponse(rule.errorResponse);
181
+ const error = new Error(
182
+ `WaitKit simulated ${response.status} response for ${requestEvent.method} ${requestEvent.url}.`
183
+ );
184
+ emitError(options, matchEvent, error);
185
+ debug(options, `${requestEvent.method} ${requestEvent.url} returned ${response.status}`);
186
+ return response;
187
+ }
188
+ return originalFetch.call(globalThis, input, init);
189
+ };
190
+ globalThis.fetch = patchedFetch;
191
+ function getActiveRules() {
192
+ if (activeScenario === void 0) {
193
+ return options.rules ?? [];
194
+ }
195
+ return options.scenarios?.[activeScenario] ?? [];
196
+ }
197
+ return {
198
+ enable() {
199
+ enabled = true;
200
+ if (restored) {
201
+ globalThis.fetch = patchedFetch;
202
+ restored = false;
203
+ }
204
+ },
205
+ disable() {
206
+ enabled = false;
207
+ },
208
+ restore() {
209
+ if (globalThis.fetch === patchedFetch) {
210
+ globalThis.fetch = originalFetch;
211
+ }
212
+ enabled = false;
213
+ restored = true;
214
+ },
215
+ isEnabled() {
216
+ return enabled;
217
+ },
218
+ setScenario(name) {
219
+ if (options.scenarios?.[name] === void 0) {
220
+ throw new Error(`WaitKit scenario "${name}" does not exist.`);
221
+ }
222
+ activeScenario = name;
223
+ },
224
+ getScenario() {
225
+ return activeScenario;
226
+ },
227
+ resetScenario() {
228
+ activeScenario = void 0;
229
+ }
230
+ };
231
+ }
232
+ function validateRuntime() {
233
+ if (typeof globalThis.fetch !== "function") {
234
+ throw new Error("WaitKit requires globalThis.fetch.");
235
+ }
236
+ if (typeof Response !== "function") {
237
+ throw new Error("WaitKit requires globalThis.Response.");
238
+ }
239
+ }
240
+ function validateOptions(options) {
241
+ validateRules(options.rules ?? []);
242
+ if (options.scenarios !== void 0) {
243
+ for (const rules of Object.values(options.scenarios)) {
244
+ validateRules(rules);
245
+ }
246
+ }
247
+ if (options.activeScenario !== void 0 && options.scenarios?.[options.activeScenario] === void 0) {
248
+ throw new Error(`WaitKit scenario "${options.activeScenario}" does not exist.`);
249
+ }
250
+ }
251
+ function validateRules(rules) {
252
+ for (const rule of rules) {
253
+ validateDelay(rule.delay);
254
+ validateRate(rule.errorRate, "errorRate");
255
+ validateRate(rule.timeoutRate, "timeoutRate");
256
+ validateTimeoutMs(rule.timeoutMs);
257
+ validateStatus(rule.errorResponse?.status);
258
+ }
259
+ }
260
+ function validateStatus(status) {
261
+ if (status === void 0) {
262
+ return;
263
+ }
264
+ if (!Number.isInteger(status) || status < 200 || status > 599) {
265
+ throw new Error("WaitKit errorResponse.status must be an integer between 200 and 599.");
266
+ }
267
+ }
268
+ function createRequestEvent(input, init) {
269
+ return {
270
+ input,
271
+ init,
272
+ url: getRequestUrl(input),
273
+ method: getRequestMethod(input, init)
274
+ };
275
+ }
276
+ function createMatchEvent(requestEvent, rule, scenario, delayMs) {
277
+ return {
278
+ ...requestEvent,
279
+ rule,
280
+ scenario,
281
+ delayMs
282
+ };
283
+ }
284
+ function findMatchingRule(rules, url, method) {
285
+ return rules.find((rule) => matchesRule(rule, url, method));
286
+ }
287
+ function shouldTrigger(rate) {
288
+ if (rate === void 0 || rate <= 0) {
289
+ return false;
290
+ }
291
+ if (rate >= 1) {
292
+ return true;
293
+ }
294
+ return Math.random() < rate;
295
+ }
296
+ function emitError(options, matchEvent, error) {
297
+ const errorEvent = {
298
+ ...matchEvent,
299
+ error
300
+ };
301
+ options.onError?.(errorEvent);
302
+ }
303
+ function debug(options, message) {
304
+ if (options.debug === true) {
305
+ console.info(`[waitkit] ${message}`);
306
+ }
307
+ }
308
+
309
+ export { WaitKitTimeoutError, setupWaitKit };
310
+ //# sourceMappingURL=index.js.map
311
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/delay.ts","../src/matcher.ts","../src/response.ts","../src/setup-wait-kit.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class WaitKitTimeoutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WaitKitTimeoutError';\n }\n}\n","import type { DelayValue } from './types';\n\nexport function resolveDelay(delay: DelayValue | undefined): number {\n if (delay === undefined) {\n return 0;\n }\n\n if (typeof delay === 'number') {\n return delay;\n }\n\n const [min, max] = delay;\n return Math.floor(min + Math.random() * (max - min + 1));\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport function validateDelay(delay: DelayValue | undefined): void {\n if (delay === undefined) {\n return;\n }\n\n if (typeof delay === 'number') {\n assertNonNegativeFiniteNumber(delay, 'delay');\n return;\n }\n\n const [min, max] = delay;\n assertNonNegativeFiniteNumber(min, 'delay min');\n assertNonNegativeFiniteNumber(max, 'delay max');\n\n if (min > max) {\n throw new Error('WaitKit delay range must have min less than or equal to max.');\n }\n}\n\nexport function validateRate(rate: number | undefined, name: string): void {\n if (rate === undefined) {\n return;\n }\n\n if (!Number.isFinite(rate) || rate < 0 || rate > 1) {\n throw new Error(`WaitKit ${name} must be a number between 0 and 1.`);\n }\n}\n\nexport function validateTimeoutMs(timeoutMs: number | undefined): void {\n if (timeoutMs === undefined) {\n return;\n }\n\n assertNonNegativeFiniteNumber(timeoutMs, 'timeoutMs');\n}\n\nfunction assertNonNegativeFiniteNumber(value: number, name: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`WaitKit ${name} must be a non-negative finite number.`);\n }\n}\n","import type { WaitKitRule } from './types';\n\nexport function matchesRule(rule: WaitKitRule, url: string, method: string): boolean {\n return matchesUrl(rule.url, url) && matchesMethod(rule.method, method);\n}\n\nfunction matchesUrl(ruleUrl: WaitKitRule['url'], url: string): boolean {\n if (typeof ruleUrl === 'string') {\n return url.includes(ruleUrl);\n }\n\n if (ruleUrl instanceof RegExp) {\n return ruleUrl.test(url);\n }\n\n return ruleUrl(url);\n}\n\nfunction matchesMethod(ruleMethod: WaitKitRule['method'], method: string): boolean {\n if (ruleMethod === undefined) {\n return true;\n }\n\n const normalizedMethod = normalizeMethod(method);\n\n if (typeof ruleMethod === 'string') {\n return normalizeMethod(ruleMethod) === normalizedMethod;\n }\n\n return ruleMethod.some((item) => normalizeMethod(item) === normalizedMethod);\n}\n\nexport function normalizeMethod(method: string | undefined): string {\n return (method ?? 'GET').toUpperCase();\n}\n\nexport function getRequestUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') {\n return input;\n }\n\n if (input instanceof URL) {\n return input.toString();\n }\n\n return input.url;\n}\n\nexport function getRequestMethod(input: RequestInfo | URL, init?: RequestInit): string {\n if (init?.method !== undefined) {\n return normalizeMethod(init.method);\n }\n\n if (typeof input === 'string' || input instanceof URL) {\n return 'GET';\n }\n\n return normalizeMethod(input.method);\n}\n","import type { WaitKitErrorResponse } from './types';\n\nexport function createErrorResponse(errorResponse: WaitKitErrorResponse | undefined): Response {\n const status = errorResponse?.status ?? 500;\n const headers = new Headers(errorResponse?.headers);\n const body = serializeBody(errorResponse?.body, headers);\n\n return new Response(body, {\n status,\n statusText: errorResponse?.statusText,\n headers,\n });\n}\n\nfunction serializeBody(body: unknown, headers: Headers): BodyInit | null {\n if (body === undefined || body === null) {\n return null;\n }\n\n if (typeof body === 'string' || isBlob(body) || isFormData(body)) {\n return body;\n }\n\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || isUrlSearchParams(body)) {\n return body as BodyInit;\n }\n\n if (!headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n\n return JSON.stringify(body);\n}\n\nfunction isBlob(body: unknown): body is Blob {\n return typeof Blob !== 'undefined' && body instanceof Blob;\n}\n\nfunction isFormData(body: unknown): body is FormData {\n return typeof FormData !== 'undefined' && body instanceof FormData;\n}\n\nfunction isUrlSearchParams(body: unknown): body is URLSearchParams {\n return typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams;\n}\n","import { resolveDelay, sleep, validateDelay, validateRate, validateTimeoutMs } from './delay';\nimport { WaitKitTimeoutError } from './errors';\nimport { getRequestMethod, getRequestUrl, matchesRule } from './matcher';\nimport { createErrorResponse } from './response';\nimport type {\n WaitKitController,\n WaitKitErrorEvent,\n WaitKitMatchEvent,\n WaitKitOptions,\n WaitKitRequestEvent,\n WaitKitRule,\n} from './types';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function setupWaitKit(options: WaitKitOptions): WaitKitController {\n validateRuntime();\n validateOptions(options);\n\n const originalFetch = globalThis.fetch;\n let enabled = options.enabled ?? true;\n let activeScenario = options.activeScenario;\n let restored = false;\n\n const patchedFetch: typeof fetch = async (input, init) => {\n if (!enabled) {\n return originalFetch.call(globalThis, input, init);\n }\n\n const requestEvent = createRequestEvent(input, init);\n options.onRequest?.(requestEvent);\n\n const rule = findMatchingRule(getActiveRules(), requestEvent.url, requestEvent.method);\n\n if (rule === undefined) {\n return originalFetch.call(globalThis, input, init);\n }\n\n const delayMs = resolveDelay(rule.delay);\n const matchEvent = createMatchEvent(requestEvent, rule, activeScenario, delayMs);\n options.onMatch?.(matchEvent);\n\n if (delayMs > 0) {\n options.onDelayStart?.(matchEvent);\n debug(options, `${requestEvent.method} ${requestEvent.url} delayed by ${delayMs}ms`);\n await sleep(delayMs);\n options.onDelayEnd?.(matchEvent);\n }\n\n if (shouldTrigger(rule.timeoutRate)) {\n const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const error = new WaitKitTimeoutError(\n `WaitKit timed out ${requestEvent.method} ${requestEvent.url} after ${timeoutMs}ms.`,\n );\n emitError(options, matchEvent, error);\n debug(options, `${requestEvent.method} ${requestEvent.url} timed out after ${timeoutMs}ms`);\n await sleep(timeoutMs);\n throw error;\n }\n\n if (shouldTrigger(rule.errorRate)) {\n const response = createErrorResponse(rule.errorResponse);\n const error = new Error(\n `WaitKit simulated ${response.status} response for ${requestEvent.method} ${requestEvent.url}.`,\n );\n emitError(options, matchEvent, error);\n debug(options, `${requestEvent.method} ${requestEvent.url} returned ${response.status}`);\n return response;\n }\n\n return originalFetch.call(globalThis, input, init);\n };\n\n globalThis.fetch = patchedFetch;\n\n function getActiveRules(): readonly WaitKitRule[] {\n if (activeScenario === undefined) {\n return options.rules ?? [];\n }\n\n return options.scenarios?.[activeScenario] ?? [];\n }\n\n return {\n enable() {\n enabled = true;\n\n if (restored) {\n globalThis.fetch = patchedFetch;\n restored = false;\n }\n },\n disable() {\n enabled = false;\n },\n restore() {\n if (globalThis.fetch === patchedFetch) {\n globalThis.fetch = originalFetch;\n }\n\n enabled = false;\n restored = true;\n },\n isEnabled() {\n return enabled;\n },\n setScenario(name: string) {\n if (options.scenarios?.[name] === undefined) {\n throw new Error(`WaitKit scenario \"${name}\" does not exist.`);\n }\n\n activeScenario = name;\n },\n getScenario() {\n return activeScenario;\n },\n resetScenario() {\n activeScenario = undefined;\n },\n };\n}\n\nfunction validateRuntime(): void {\n if (typeof globalThis.fetch !== 'function') {\n throw new Error('WaitKit requires globalThis.fetch.');\n }\n\n if (typeof Response !== 'function') {\n throw new Error('WaitKit requires globalThis.Response.');\n }\n}\n\nfunction validateOptions(options: WaitKitOptions): void {\n validateRules(options.rules ?? []);\n\n if (options.scenarios !== undefined) {\n for (const rules of Object.values(options.scenarios)) {\n validateRules(rules);\n }\n }\n\n if (\n options.activeScenario !== undefined &&\n options.scenarios?.[options.activeScenario] === undefined\n ) {\n throw new Error(`WaitKit scenario \"${options.activeScenario}\" does not exist.`);\n }\n}\n\nfunction validateRules(rules: readonly WaitKitRule[]): void {\n for (const rule of rules) {\n validateDelay(rule.delay);\n validateRate(rule.errorRate, 'errorRate');\n validateRate(rule.timeoutRate, 'timeoutRate');\n validateTimeoutMs(rule.timeoutMs);\n validateStatus(rule.errorResponse?.status);\n }\n}\n\nfunction validateStatus(status: number | undefined): void {\n if (status === undefined) {\n return;\n }\n\n if (!Number.isInteger(status) || status < 200 || status > 599) {\n throw new Error('WaitKit errorResponse.status must be an integer between 200 and 599.');\n }\n}\n\nfunction createRequestEvent(input: RequestInfo | URL, init?: RequestInit): WaitKitRequestEvent {\n return {\n input,\n init,\n url: getRequestUrl(input),\n method: getRequestMethod(input, init),\n };\n}\n\nfunction createMatchEvent(\n requestEvent: WaitKitRequestEvent,\n rule: WaitKitRule,\n scenario: string | undefined,\n delayMs: number,\n): WaitKitMatchEvent {\n return {\n ...requestEvent,\n rule,\n scenario,\n delayMs,\n };\n}\n\nfunction findMatchingRule(\n rules: readonly WaitKitRule[],\n url: string,\n method: string,\n): WaitKitRule | undefined {\n return rules.find((rule) => matchesRule(rule, url, method));\n}\n\nfunction shouldTrigger(rate: number | undefined): boolean {\n if (rate === undefined || rate <= 0) {\n return false;\n }\n\n if (rate >= 1) {\n return true;\n }\n\n return Math.random() < rate;\n}\n\nfunction emitError(options: WaitKitOptions, matchEvent: WaitKitMatchEvent, error: Error): void {\n const errorEvent: WaitKitErrorEvent = {\n ...matchEvent,\n error,\n };\n\n options.onError?.(errorEvent);\n}\n\nfunction debug(options: WaitKitOptions, message: string): void {\n if (options.debug === true) {\n console.info(`[waitkit] ${message}`);\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@waitkit/core",
3
+ "version": "0.1.0",
4
+ "description": "Development-only fetch interceptor for testing loading, error, and timeout states.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "junjuny0227",
8
+ "homepage": "https://github.com/junjuny0227/waitkit#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/junjuny0227/waitkit.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/junjuny0227/waitkit/issues"
15
+ },
16
+ "keywords": [
17
+ "fetch",
18
+ "network",
19
+ "latency",
20
+ "loading",
21
+ "timeout",
22
+ "testing"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/index.d.cts",
32
+ "default": "./dist/index.cjs"
33
+ }
34
+ }
35
+ },
36
+ "main": "./dist/index.cjs",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "devDependencies": {
46
+ "eslint": "^9.39.4",
47
+ "tsup": "^8.5.1",
48
+ "typescript": "^5.9.3",
49
+ "vitest": "^4.1.6",
50
+ "@waitkit/typescript-config": "0.0.0",
51
+ "@waitkit/eslint-config": "0.0.0"
52
+ },
53
+ "scripts": {
54
+ "dev": "tsup --watch",
55
+ "build": "tsup",
56
+ "test": "vitest run",
57
+ "lint": "eslint .",
58
+ "lint:fix": "eslint . --fix",
59
+ "check-types": "tsc --noEmit"
60
+ }
61
+ }