@r0hitsharma/http-client-msw 0.12.0-rohit-fork-ci.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Latency is what makes a mock exercise the states a real API forces an app
3
+ * through — pending spinners, skeletons, optimistic UI, race conditions. The same
4
+ * latency in a test suite is dead time, so the amount is per-environment and the
5
+ * default under test is none.
6
+ */
7
+ /**
8
+ * True when running under a test runner: `NODE_ENV === 'test'` (vitest, jest) or
9
+ * Vite's `MODE === 'test'`. Read defensively because neither `process` nor
10
+ * `import.meta.env` exists in every environment this package runs in.
11
+ */
12
+ export function isTestEnvironment() {
13
+ // `process.env` is optional-chained as well as guarded: a browser shim that
14
+ // defines `process` without an `env` would otherwise throw from inside a
15
+ // request handler. The dot form is deliberate — it is what bundlers rewrite.
16
+ const nodeEnv = typeof process === 'undefined' ? undefined : process.env?.NODE_ENV;
17
+ const viteMode = import.meta.env
18
+ ?.MODE;
19
+ return nodeEnv === 'test' || viteMode === 'test';
20
+ }
21
+ /** The pure resolution `mockDelay` applies, split out so both branches are testable. */
22
+ export function resolveMockDelay(input, isTest) {
23
+ if (typeof input === 'number')
24
+ return isTest ? 0 : input;
25
+ return (isTest ? input.test : input.dev) ?? 0;
26
+ }
27
+ /**
28
+ * Waits, in dev; resolves immediately under test unless a test delay is asked
29
+ * for.
30
+ *
31
+ * ```ts
32
+ * mock.get('/things', async ({ response }) => {
33
+ * await mockDelay(400);
34
+ * return response(200).json(things.list());
35
+ * });
36
+ *
37
+ * // Keep a little latency under test, for a pending-state assertion.
38
+ * await mockDelay({ dev: 400, test: 10 });
39
+ * ```
40
+ */
41
+ export function mockDelay(input = 0) {
42
+ const ms = resolveMockDelay(input, isTestEnvironment());
43
+ // No timer at all for a zero delay: a mock that resolves in the same
44
+ // microtask keeps a suite's timing behaviour unchanged.
45
+ if (ms <= 0)
46
+ return Promise.resolve();
47
+ return new Promise((resolve) => {
48
+ setTimeout(resolve, ms);
49
+ });
50
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The smallest amount of state a mock needs to behave like an API rather than a
3
+ * fixture dump: a POST that shows up in the following GET. Deliberately not a
4
+ * database — no queries, no indexes, no relations. A mock that needs those is a
5
+ * mock standing in for logic the real service owns.
6
+ */
7
+ export type MockStoreOptions<T> = {
8
+ /**
9
+ * How an item's identity is read. Defaults to its `id` property, which must be
10
+ * a string or a number.
11
+ */
12
+ id?: (item: T) => string;
13
+ };
14
+ /**
15
+ * Aliasing, since it is observable: `get`, `insert`, and the elements of
16
+ * `list` are the **stored** objects, so mutating one writes through to the
17
+ * store — only `list`'s array is a copy. `update` replaces the item with
18
+ * a merged copy instead, which leaves any reference taken before the update
19
+ * stale. Read again after an update rather than holding a reference across one.
20
+ */
21
+ export type MockStore<T> = {
22
+ /**
23
+ * Every item, in insertion order. The array is fresh, so sorting, slicing, or
24
+ * splicing it cannot reorder the store — but its elements are the stored
25
+ * objects, not copies, so mutating one writes through like `get` does.
26
+ */
27
+ list: () => T[];
28
+ get: (id: string) => T | undefined;
29
+ has: (id: string) => boolean;
30
+ readonly size: number;
31
+ /** Adds an item. Throws on a duplicate id, which is a fixture bug rather than an API condition. */
32
+ insert: (item: T) => T;
33
+ /**
34
+ * Merges `patch` into an existing item, keeping its position. `undefined`
35
+ * when the id is unknown — a handler turns that into its own 404.
36
+ *
37
+ * A patch may carry the id, which moves the item to it; the old id then
38
+ * misses. Throws if that id is already taken, like `insert`, and leaves the
39
+ * store untouched when it does.
40
+ */
41
+ update: (id: string, patch: Partial<T>) => T | undefined;
42
+ /** `false` when the id is unknown. */
43
+ remove: (id: string) => boolean;
44
+ /** Swaps in a whole collection. Throws if two items share an id, like `insert`. */
45
+ replaceAll: (items: readonly T[]) => void;
46
+ /** Re-seeds from the seed function. Pass this to `setupMocks({ onReset })`. */
47
+ reset: () => void;
48
+ };
49
+ /**
50
+ * An in-memory collection whose `reset` restores the seeded contents.
51
+ *
52
+ * The seed is a **function**, called on construction and again on every reset, so
53
+ * a handler that mutates an item in place cannot corrupt the next test's
54
+ * starting point. That holds only if the function *constructs* its items — a
55
+ * seed that returns a shared module-level array hands out the same objects every
56
+ * time, and a mutation to one of those does survive a reset.
57
+ *
58
+ * ```ts
59
+ * const things = createMockStore(seedThings);
60
+ *
61
+ * export const mocks = setupMocks(
62
+ * [
63
+ * mock.get('/things', ({ response }) => response(200).json(things.list())),
64
+ * mock.get('/things/{id}', ({ params, response }) => {
65
+ * const thing = things.get(params.id);
66
+ *
67
+ * return thing
68
+ * ? response(200).json(thing)
69
+ * : response(404).json({ message: 'not found' });
70
+ * }),
71
+ * ],
72
+ * { onReset: [things.reset] },
73
+ * );
74
+ * ```
75
+ */
76
+ export declare function createMockStore<T>(seed: () => readonly T[], options?: MockStoreOptions<T>): MockStore<T>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The smallest amount of state a mock needs to behave like an API rather than a
3
+ * fixture dump: a POST that shows up in the following GET. Deliberately not a
4
+ * database — no queries, no indexes, no relations. A mock that needs those is a
5
+ * mock standing in for logic the real service owns.
6
+ */
7
+ function defaultIdOf(item) {
8
+ const id = item.id;
9
+ if (typeof id === 'string')
10
+ return id;
11
+ if (typeof id === 'number')
12
+ return String(id);
13
+ throw new Error('http-client-msw: createMockStore items need a string or number `id`, or an explicit `id` selector');
14
+ }
15
+ /**
16
+ * An in-memory collection whose `reset` restores the seeded contents.
17
+ *
18
+ * The seed is a **function**, called on construction and again on every reset, so
19
+ * a handler that mutates an item in place cannot corrupt the next test's
20
+ * starting point. That holds only if the function *constructs* its items — a
21
+ * seed that returns a shared module-level array hands out the same objects every
22
+ * time, and a mutation to one of those does survive a reset.
23
+ *
24
+ * ```ts
25
+ * const things = createMockStore(seedThings);
26
+ *
27
+ * export const mocks = setupMocks(
28
+ * [
29
+ * mock.get('/things', ({ response }) => response(200).json(things.list())),
30
+ * mock.get('/things/{id}', ({ params, response }) => {
31
+ * const thing = things.get(params.id);
32
+ *
33
+ * return thing
34
+ * ? response(200).json(thing)
35
+ * : response(404).json({ message: 'not found' });
36
+ * }),
37
+ * ],
38
+ * { onReset: [things.reset] },
39
+ * );
40
+ * ```
41
+ */
42
+ export function createMockStore(seed, options = {}) {
43
+ const idOf = options.id ?? defaultIdOf;
44
+ const items = new Map();
45
+ /**
46
+ * Built into a staging map first, so the duplicate-id check that `insert`
47
+ * enforces also covers construction and every reset — and so a rejected
48
+ * collection leaves the store as it was rather than half-cleared.
49
+ */
50
+ const replaceAll = (next) => {
51
+ const replacement = new Map();
52
+ for (const item of next) {
53
+ const id = idOf(item);
54
+ if (replacement.has(id)) {
55
+ throw new Error(`http-client-msw: createMockStore was given two items with id "${id}"`);
56
+ }
57
+ replacement.set(id, item);
58
+ }
59
+ items.clear();
60
+ for (const [id, item] of replacement)
61
+ items.set(id, item);
62
+ };
63
+ replaceAll(seed());
64
+ return {
65
+ list: () => [...items.values()],
66
+ get: (id) => items.get(id),
67
+ has: (id) => items.has(id),
68
+ get size() {
69
+ return items.size;
70
+ },
71
+ insert: (item) => {
72
+ const id = idOf(item);
73
+ if (items.has(id)) {
74
+ throw new Error(`http-client-msw: createMockStore already holds an item with id "${id}"`);
75
+ }
76
+ items.set(id, item);
77
+ return item;
78
+ },
79
+ update: (id, patch) => {
80
+ const existing = items.get(id);
81
+ if (existing === undefined)
82
+ return undefined;
83
+ const updated = { ...existing, ...patch };
84
+ // `Partial<T>` admits the id, so the patch can move the item. Re-derived
85
+ // rather than assumed, because leaving it under the old key desyncs the
86
+ // map from the items in it: `get(item.id)` misses and `get(oldId)` hands
87
+ // back an item that no longer claims that id.
88
+ const nextId = idOf(updated);
89
+ if (nextId === id) {
90
+ items.set(id, updated);
91
+ return updated;
92
+ }
93
+ if (items.has(nextId)) {
94
+ throw new Error(`http-client-msw: createMockStore already holds an item with id "${nextId}"`);
95
+ }
96
+ // Rebuilt in order rather than delete-then-set, which would move the item
97
+ // to the end: `update` keeps an item's position, and a patched id is
98
+ // still an update. The collision above throws before anything is
99
+ // touched, so a rejected patch leaves the store as it was.
100
+ const entries = [...items];
101
+ items.clear();
102
+ for (const [key, item] of entries) {
103
+ if (key === id)
104
+ items.set(nextId, updated);
105
+ else
106
+ items.set(key, item);
107
+ }
108
+ return updated;
109
+ },
110
+ remove: (id) => items.delete(id),
111
+ replaceAll,
112
+ reset: () => replaceAll(seed()),
113
+ };
114
+ }
package/dist/node.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Node entry: `@r0hitsharma/http-client-msw/node`.
3
+ *
4
+ * Kept behind its own subpath so an app bundle never resolves `msw/node`, whose
5
+ * interceptors patch node's http modules.
6
+ */
7
+ import { type SetupServer } from 'msw/node';
8
+ import type { MockSetup } from './setup.js';
9
+ export type MockServerOptions = NonNullable<Parameters<SetupServer['listen']>[0]>;
10
+ export type MockServer = {
11
+ /** The underlying msw server, for `use()` and lifecycle events. */
12
+ server: SetupServer;
13
+ /** Starts interception. `onUnhandledRequest` defaults to `'error'`. */
14
+ listen: (overrides?: MockServerOptions) => void;
15
+ close: () => void;
16
+ /** Runs the setup's state resets, then drops runtime handler overrides. */
17
+ reset: () => void;
18
+ };
19
+ /**
20
+ * Serves a {@link MockSetup} from msw's node interceptors — the same handler
21
+ * array a browser worker serves.
22
+ *
23
+ * ```ts
24
+ * // src/test-setup.ts
25
+ * import { setupMockServer } from '@r0hitsharma/http-client-msw/node';
26
+ * import { afterAll, afterEach, beforeAll } from 'vitest';
27
+ *
28
+ * import { mocks } from './mocks';
29
+ *
30
+ * const mockServer = setupMockServer(mocks);
31
+ *
32
+ * beforeAll(() => mockServer.listen());
33
+ * afterEach(() => mockServer.reset());
34
+ * afterAll(() => mockServer.close());
35
+ * ```
36
+ *
37
+ * `onUnhandledRequest` defaults to `'error'` rather than msw's `'warn'`: in a
38
+ * test suite an unmocked request is a hole in the fixtures, and a warning that
39
+ * scrolls past is how a test ends up asserting against real network output.
40
+ */
41
+ export declare function setupMockServer(mocks: MockSetup): MockServer;
package/dist/node.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Node entry: `@r0hitsharma/http-client-msw/node`.
3
+ *
4
+ * Kept behind its own subpath so an app bundle never resolves `msw/node`, whose
5
+ * interceptors patch node's http modules.
6
+ */
7
+ import { setupServer } from 'msw/node';
8
+ /**
9
+ * Serves a {@link MockSetup} from msw's node interceptors — the same handler
10
+ * array a browser worker serves.
11
+ *
12
+ * ```ts
13
+ * // src/test-setup.ts
14
+ * import { setupMockServer } from '@r0hitsharma/http-client-msw/node';
15
+ * import { afterAll, afterEach, beforeAll } from 'vitest';
16
+ *
17
+ * import { mocks } from './mocks';
18
+ *
19
+ * const mockServer = setupMockServer(mocks);
20
+ *
21
+ * beforeAll(() => mockServer.listen());
22
+ * afterEach(() => mockServer.reset());
23
+ * afterAll(() => mockServer.close());
24
+ * ```
25
+ *
26
+ * `onUnhandledRequest` defaults to `'error'` rather than msw's `'warn'`: in a
27
+ * test suite an unmocked request is a hole in the fixtures, and a warning that
28
+ * scrolls past is how a test ends up asserting against real network output.
29
+ */
30
+ export function setupMockServer(mocks) {
31
+ const server = setupServer(...mocks.handlers);
32
+ return {
33
+ server,
34
+ listen: (overrides) => server.listen({ onUnhandledRequest: 'error', ...overrides }),
35
+ close: () => server.close(),
36
+ reset: () => {
37
+ mocks.resetState();
38
+ server.resetHandlers();
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Deterministic randomness for generated fixtures. `Math.random` in a mock makes
3
+ * a failing test unreproducible and a visual snapshot unstable; a seeded
4
+ * generator gives varied-looking data that is identical on every run and on every
5
+ * machine.
6
+ */
7
+ export type SeededRng = {
8
+ /** The next value in `[0, 1)`. */
9
+ next: () => number;
10
+ /**
11
+ * An integer in `[min, max]`, both inclusive. Bounds are trusted: `max` below
12
+ * `min` returns a value outside either, unchecked.
13
+ */
14
+ int: (min: number, max: number) => number;
15
+ /** One element. Throws on an empty list. */
16
+ pick: <T>(items: readonly T[]) => T;
17
+ /** A shuffled copy; the input is left alone. */
18
+ shuffle: <T>(items: readonly T[]) => T[];
19
+ /** Rewinds to the seed. Pass this to `setupMocks({ onReset })`. */
20
+ reset: () => void;
21
+ };
22
+ /**
23
+ * A mulberry32 generator — 32 bits of state, a handful of integer ops, no
24
+ * dependency. Its statistical quality is irrelevant here; reproducibility and
25
+ * being cheap enough to call inside a request handler are the requirements.
26
+ */
27
+ export declare function createSeededRng(seed: number): SeededRng;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Deterministic randomness for generated fixtures. `Math.random` in a mock makes
3
+ * a failing test unreproducible and a visual snapshot unstable; a seeded
4
+ * generator gives varied-looking data that is identical on every run and on every
5
+ * machine.
6
+ */
7
+ /**
8
+ * A mulberry32 generator — 32 bits of state, a handful of integer ops, no
9
+ * dependency. Its statistical quality is irrelevant here; reproducibility and
10
+ * being cheap enough to call inside a request handler are the requirements.
11
+ */
12
+ export function createSeededRng(seed) {
13
+ let state = seed >>> 0;
14
+ const next = () => {
15
+ state = (state + 0x6d2b79f5) >>> 0;
16
+ let t = state;
17
+ t = Math.imul(t ^ (t >>> 15), t | 1);
18
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
19
+ return ((t ^ (t >>> 14)) >>> 0) / 4_294_967_296;
20
+ };
21
+ const int = (min, max) => min + Math.floor(next() * (max - min + 1));
22
+ return {
23
+ next,
24
+ int,
25
+ pick: (items) => {
26
+ // Guard on length, not on the read being `undefined`: `T` may itself
27
+ // include `undefined`, and `pick([undefined, 1])` must return the element
28
+ // rather than report an empty list.
29
+ if (items.length === 0) {
30
+ throw new Error('http-client-msw: cannot pick from an empty list');
31
+ }
32
+ return items[int(0, items.length - 1)];
33
+ },
34
+ shuffle: (items) => {
35
+ const shuffled = [...items];
36
+ for (let i = shuffled.length - 1; i > 0; i--) {
37
+ const j = int(0, i);
38
+ // Both indices are in range by the loop bound, so the swap is
39
+ // unconditional — skipping it on an `undefined` *element* would let the
40
+ // result depend on the values while still consuming the draw above,
41
+ // which is exactly the reproducibility this generator exists to give.
42
+ const a = shuffled[i];
43
+ shuffled[i] = shuffled[j];
44
+ shuffled[j] = a;
45
+ }
46
+ return shuffled;
47
+ },
48
+ reset: () => {
49
+ state = seed >>> 0;
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,54 @@
1
+ import type { RequestHandler, WebSocketHandler } from 'msw';
2
+ /**
3
+ * Anything msw accepts as a handler. Typed handlers from `createMockApi` are
4
+ * `RequestHandler`s; the union leaves room for a websocket mock in the same
5
+ * array without a second setup path.
6
+ */
7
+ export type MockSetupHandler = RequestHandler | WebSocketHandler;
8
+ /**
9
+ * A callback that returns mock state to its seeded starting point. Stores built
10
+ * with `createMockStore` expose exactly this as `store.reset`.
11
+ */
12
+ export type MockResetCallback = () => void;
13
+ export type MockSetupOptions = {
14
+ /**
15
+ * Callbacks that restore mock state, run **in declaration order** by `reset()`
16
+ * on whichever entry serves these handlers. This is how a stateful mock stops
17
+ * leaking writes from one test into the next: the handlers close over a store,
18
+ * and the store's `reset` is declared here once rather than at every call site.
19
+ *
20
+ * Order matters when one reset feeds another. A store whose seed function draws
21
+ * from a seeded rng must be listed *after* that rng, or each reset re-seeds
22
+ * from wherever the previous one left the sequence.
23
+ */
24
+ onReset?: readonly MockResetCallback[];
25
+ };
26
+ /**
27
+ * An environment-neutral description of a mock deployment: the handler array
28
+ * plus the state resets that belong with it. It holds no msw setup object of its
29
+ * own, which is what lets the same value be handed to the browser entry
30
+ * (`setupWorker`) or the node entry (`setupServer`) without either environment's
31
+ * msw import reaching the other's bundle.
32
+ */
33
+ export type MockSetup = {
34
+ handlers: readonly MockSetupHandler[];
35
+ /** Runs every `onReset` callback in declaration order. */
36
+ resetState: () => void;
37
+ };
38
+ /**
39
+ * Bundles handlers with their state resets.
40
+ *
41
+ * ```ts
42
+ * const rng = createSeededRng(42);
43
+ * const things = createMockStore(() => [
44
+ * { id: 't1', name: 'First', size: rng.int(1, 100) },
45
+ * ]);
46
+ *
47
+ * export const mocks = setupMocks(
48
+ * [mock.get('/things', ({ response }) => response(200).json(things.list()))],
49
+ * // The rng first: the store's seed draws from it.
50
+ * { onReset: [rng.reset, things.reset] },
51
+ * );
52
+ * ```
53
+ */
54
+ export declare function setupMocks(handlers: readonly MockSetupHandler[], options?: MockSetupOptions): MockSetup;
package/dist/setup.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Bundles handlers with their state resets.
3
+ *
4
+ * ```ts
5
+ * const rng = createSeededRng(42);
6
+ * const things = createMockStore(() => [
7
+ * { id: 't1', name: 'First', size: rng.int(1, 100) },
8
+ * ]);
9
+ *
10
+ * export const mocks = setupMocks(
11
+ * [mock.get('/things', ({ response }) => response(200).json(things.list()))],
12
+ * // The rng first: the store's seed draws from it.
13
+ * { onReset: [rng.reset, things.reset] },
14
+ * );
15
+ * ```
16
+ */
17
+ export function setupMocks(handlers, options = {}) {
18
+ const onReset = options.onReset ?? [];
19
+ return {
20
+ handlers,
21
+ resetState: () => {
22
+ for (const reset of onReset)
23
+ reset();
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,55 @@
1
+ import type { StartOptions } from 'msw/browser';
2
+ /**
3
+ * The browser entry's decisions, factored out of `browser.ts` so they are
4
+ * testable without a service worker: a vitest run cannot register one, but it
5
+ * can assert the options that would be passed and the start-once behaviour.
6
+ */
7
+ export type MockWorkerOptions = {
8
+ /**
9
+ * The app's **public base path** — `import.meta.env.BASE_URL` under Vite — used
10
+ * to locate the worker script for a subpath deployment. This is not the API
11
+ * base from `createMockApi`: the script is served by the app, the API is
12
+ * whatever the app talks to.
13
+ *
14
+ * @default '/'
15
+ */
16
+ baseUrl?: string;
17
+ /**
18
+ * What msw does with a request no handler matched.
19
+ *
20
+ * Defaults to `'bypass'`, unlike msw's own `'warn'`: an app running against
21
+ * mocks still loads its own documents, modules, images, and fonts, and warning
22
+ * on every one of them buries the requests a developer is actually looking at.
23
+ * Use `'warn'` while filling gaps in a mock set.
24
+ */
25
+ onUnhandledRequest?: StartOptions['onUnhandledRequest'];
26
+ /**
27
+ * Suppress msw's per-request console logging. Defaults to `true` — the request
28
+ * log is long enough to hide application logs, and the network panel already
29
+ * shows the same traffic.
30
+ */
31
+ quiet?: boolean;
32
+ /** Escape hatch merged last into msw's own `worker.start()` options. */
33
+ start?: StartOptions;
34
+ };
35
+ export declare function buildWorkerStartOptions(options?: MockWorkerOptions): StartOptions;
36
+ export type IdempotentStart = {
37
+ /** Runs the wrapped function at most once, until `invalidate()`. */
38
+ start: () => Promise<void>;
39
+ /**
40
+ * Forgets a completed start so the next `start()` runs again. Whatever the
41
+ * start registered has to be torn down first — a memo pointing at a stopped
42
+ * worker resolves immediately and intercepts nothing.
43
+ */
44
+ invalidate: () => void;
45
+ };
46
+ /**
47
+ * Wraps a start function so concurrent and repeated calls share one
48
+ * registration. An app entry, a hot reload, and a Playwright fixture can all
49
+ * call `start()`; registering the worker again mid-session would reset its
50
+ * handler list and drop any runtime overrides a test had installed.
51
+ *
52
+ * A rejected start invalidates itself, so a caller can retry after fixing
53
+ * whatever failed (a missing `mockServiceWorker.js`, most often).
54
+ */
55
+ export declare function createIdempotentStart(start: () => Promise<void>): IdempotentStart;
@@ -0,0 +1,49 @@
1
+ import { resolveWorkerScriptUrl } from './base-url.js';
2
+ export function buildWorkerStartOptions(options = {}) {
3
+ const { baseUrl, onUnhandledRequest, quiet, start } = options;
4
+ return {
5
+ ...start,
6
+ // Resolved after the spread rather than defaulted before it: an options
7
+ // object forwarded with explicit `undefined` values would otherwise put
8
+ // those keys back and hand msw its own defaults instead of ours. `start`
9
+ // still wins over the named options where it sets a value.
10
+ onUnhandledRequest: start?.onUnhandledRequest ?? onUnhandledRequest ?? 'bypass',
11
+ quiet: start?.quiet ?? quiet ?? true,
12
+ serviceWorker: {
13
+ ...start?.serviceWorker,
14
+ // The same hole one level down: a `serviceWorker` object populated from
15
+ // another options bag carries an explicit `url: undefined`, which spread
16
+ // over the resolved path would erase it and send msw to its own default
17
+ // location — the wrong one for a subpath deployment.
18
+ url: start?.serviceWorker?.url ?? resolveWorkerScriptUrl(baseUrl),
19
+ },
20
+ };
21
+ }
22
+ /**
23
+ * Wraps a start function so concurrent and repeated calls share one
24
+ * registration. An app entry, a hot reload, and a Playwright fixture can all
25
+ * call `start()`; registering the worker again mid-session would reset its
26
+ * handler list and drop any runtime overrides a test had installed.
27
+ *
28
+ * A rejected start invalidates itself, so a caller can retry after fixing
29
+ * whatever failed (a missing `mockServiceWorker.js`, most often).
30
+ */
31
+ export function createIdempotentStart(start) {
32
+ let pending;
33
+ const invalidate = () => {
34
+ pending = undefined;
35
+ };
36
+ return {
37
+ start: () => {
38
+ // Called through an async wrapper so a synchronous throw from `start`
39
+ // rejects the returned promise instead of escaping as an exception the
40
+ // caller has no way to `.catch`.
41
+ pending ??= (async () => await start())().catch((error) => {
42
+ invalidate();
43
+ throw error;
44
+ });
45
+ return pending;
46
+ },
47
+ invalidate,
48
+ };
49
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@r0hitsharma/http-client-msw",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "src"
11
+ ],
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./browser": {
20
+ "types": "./dist/browser.d.ts",
21
+ "default": "./dist/browser.js"
22
+ },
23
+ "./node": {
24
+ "types": "./dist/node.d.ts",
25
+ "default": "./dist/node.js"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.build.json",
30
+ "clean": "rm -rf dist",
31
+ "prepare": "npm run build",
32
+ "type:check": "tsc -p tsconfig.json --noEmit",
33
+ "lint": "oxlint -c oxlint.config.ts --max-warnings=0 src",
34
+ "lint:fix": "oxlint -c oxlint.config.ts --fix src",
35
+ "format": "oxfmt -c oxfmt.config.ts --write src",
36
+ "format:check": "oxfmt -c oxfmt.config.ts --check src",
37
+ "test": "vitest run"
38
+ },
39
+ "dependencies": {
40
+ "openapi-msw": "2.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "msw": "^2.10.5"
44
+ },
45
+ "devDependencies": {
46
+ "@r0hitsharma/oxfmt-config": "*",
47
+ "@r0hitsharma/oxlint-config": "*",
48
+ "@r0hitsharma/tsconfig": "*",
49
+ "@types/node": "24.13.3",
50
+ "msw": "2.15.0",
51
+ "oxfmt": "0.67.0",
52
+ "oxlint": "1.82.0",
53
+ "vitest": "^4.1.9"
54
+ },
55
+ "version": "0.12.0-rohit-fork-ci.1",
56
+ "repository": {
57
+ "url": "https://github.com/r0hitsharma/uikit"
58
+ }
59
+ }