{"_id":"@bupkis/msw","_rev":"3-5dfdd118095e9b917d6277329ed3841f","name":"@bupkis/msw","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@bupkis/msw","version":"0.1.0","keywords":["bupkis","msw","mock","http","api","assert","assertion","test","mock-service-worker"],"author":{"name":"Christopher Hiller","email":"boneskull@boneskull.com"},"license":"BlueOak-1.0.0","_id":"@bupkis/msw@0.1.0","maintainers":[{"name":"boneskull","email":"boneskull@boneskull.com"}],"homepage":"https://github.com/boneskull/bupkis#readme","bugs":{"url":"https://github.com/boneskull/bupkis/issues"},"dist":{"shasum":"09c1f491ba35593ce200c6fafe021a169868c322","tarball":"https://registry.npmjs.org/@bupkis/msw/-/msw-0.1.0.tgz","fileCount":58,"integrity":"sha512-kiO99zx66Uv2MC4VGiC2dr2DWqSWLTE5QgqqU4oPCbEC8DEySz40RUIB+Gcjmjo8aDQLYFnsIMjU+kZ45Dw0Rg==","signatures":[{"sig":"MEUCIHm24BTYEDuAYwfiVkzccr2zUpJmtoKCumS5s6snpa4vAiEAveYPF8MGMEYwXlR14wyzT1UQK4qp9s1WM1oVdQ/GITU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":208079},"main":"./dist/index.cjs","type":"module","zshy":{"exports":{".":"./src/index.ts","./package.json":"./package.json"}},"types":"./dist/index.d.cts","module":"./dist/index.js","engines":{"node":"^20.19.0 || ^22.12.0 || >=23"},"exports":{".":{"types":"./dist/index.d.cts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"b24042b1ed34d4cd6095d2790c5117d475da0dc8","scripts":{"test":"npm run test:base -- \"test/*.test.ts\"","build":"zshy","test:dev":"npm run test:base -- --watch \"test/*.test.ts\"","test:base":"node --import tsx --test --test-reporter=spec","test:types":"exit 0","test:node20":"npm run test:base -- test/*.test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"boneskull","email":"boneskull@boneskull.com"},"prettier":{"tsdoc":true,"plugins":["prettier-plugin-jsdoc","prettier-plugin-pkg"],"singleQuote":true,"jsdocPreferCodeFences":true,"jsdocCommentLineStrategy":"keep"},"repository":{"url":"git+https://github.com/boneskull/bupkis.git","type":"git","directory":"packages/msw"},"_npmVersion":"11.6.2","description":"MSW request verification assertions for Bupkis","directories":{},"_nodeVersion":"24.12.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"msw":"2.8.4"},"peerDependencies":{"msw":">=2.0.0","bupkis":">=0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/msw_0.1.0_1769221402367_0.37803426269358376","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@bupkis/msw","version":"0.1.1","keywords":["bupkis","msw","mock","http","api","assert","assertion","test","mock-service-worker"],"author":{"name":"Christopher Hiller","email":"boneskull@boneskull.com"},"license":"BlueOak-1.0.0","_id":"@bupkis/msw@0.1.1","maintainers":[{"name":"boneskull","email":"boneskull@boneskull.com"}],"homepage":"https://github.com/boneskull/bupkis#readme","bugs":{"url":"https://github.com/boneskull/bupkis/issues"},"dist":{"shasum":"59fb648382e5413e868b728b7a793dbe21cfa83e","tarball":"https://registry.npmjs.org/@bupkis/msw/-/msw-0.1.1.tgz","fileCount":10,"integrity":"sha512-XKDyyZ4HN5+7kpL+NXOxj+sFpR3nuYXxYKyKh+rGIoJPEQuAGabk4dYqxoytstLqrmDOkd1ki6jCeYPo6xpJew==","signatures":[{"sig":"MEUCIE3FLjSXKY0mH01UAabWhu4nqSyRHzGLX1f60bcHXKMjAiEAh5qiyxMA0ql4uyNIrgarDOhSq8CAIlNwjvqr2e8rdmM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47976},"main":"./dist/index.cjs","type":"module","zshy":{"exports":{".":"./src/index.ts","./package.json":"./package.json"}},"types":"./dist/index.d.cts","module":"./dist/index.js","engines":{"node":"^20.19.0 || ^22.12.0 || >=23"},"exports":{".":{"types":"./dist/index.d.cts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"64ef732a58e91cc6c0808149500c8eb3de56c4e1","scripts":{"test":"npm run test:base -- \"test/*.test.ts\"","build":"zshy","test:dev":"npm run test:base -- --watch \"test/*.test.ts\"","test:base":"node --import tsx --test --test-reporter=spec","test:types":"exit 0","test:node20":"npm run test:base -- test/*.test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"boneskull","email":"boneskull@boneskull.com"},"prettier":{"tsdoc":true,"plugins":["prettier-plugin-jsdoc","prettier-plugin-pkg"],"singleQuote":true,"jsdocPreferCodeFences":true,"jsdocCommentLineStrategy":"keep"},"repository":{"url":"git+https://github.com/boneskull/bupkis.git","type":"git","directory":"packages/msw"},"_npmVersion":"11.16.0","description":"MSW request verification assertions for Bupkis","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"msw":"2.14.6"},"peerDependencies":{"msw":">=2.0.0","bupkis":">=0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/msw_0.1.1_1782785768308_0.8215518589153417","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@bupkis/msw","version":"1.0.0","type":"module","description":"MSW request verification assertions for Bupkis","repository":{"directory":"packages/msw","type":"git","url":"git+https://github.com/boneskull/bupkis.git"},"author":{"name":"Christopher Hiller","email":"boneskull@boneskull.com"},"license":"BlueOak-1.0.0","engines":{"node":"^22.13.0 || >=24.0.0"},"main":"./dist/index.cjs","types":"./dist/index.d.cts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.cts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"keywords":["bupkis","msw","mock","http","api","assert","assertion","test","mock-service-worker"],"scripts":{"build":"zshy","prepublishOnly":"npm run build","test":"npm run test:base -- \"test/*.test.ts\"","test:base":"node --import tsx --test --test-reporter=spec","test:ci":"npm run test -- --test-reporter=node-test-github-reporter","test:coverage":"c8 --reporter=lcov --reporter=text npm test","test:dev":"npm run test -- --watch"},"peerDependencies":{"bupkis":">=0.15.0","msw":">=2.0.0"},"devDependencies":{"msw":"2.14.6"},"publishConfig":{"access":"public"},"prettier":{"jsdocCommentLineStrategy":"keep","jsdocPreferCodeFences":true,"plugins":["prettier-plugin-jsdoc","prettier-plugin-pkg"],"singleQuote":true,"tsdoc":true},"zshy":{"exports":{".":"./src/index.ts","./package.json":"./package.json"}},"gitHead":"c0f3fcd74ca0f7e307d2a68ce12bba81b4059eb5","_id":"@bupkis/msw@1.0.0","bugs":{"url":"https://github.com/boneskull/bupkis/issues"},"homepage":"https://github.com/boneskull/bupkis#readme","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-zWjto8dRFCIjucHKUpZyYsdWRN09tAH5rcZ7PqZJQRmJSLTajui5O2nnZCB7xhWQbB8UQEGOzJytzG6YZu7Psw==","shasum":"6f609ee32bef38858df998a49fb82744eb0931ef","tarball":"https://registry.npmjs.org/@bupkis/msw/-/msw-1.0.0.tgz","fileCount":58,"unpackedSize":210324,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFFMnUbaiAELNZgW9h4+4RKiG9nHnvHN3F6TKDR4/OfIAiEAvkxBakcnxfTpSgOHzyvpcyAf3tJBCH3GyPjhMdNDC0Y="}]},"_npmUser":{"name":"boneskull","email":"boneskull@boneskull.com"},"directories":{},"maintainers":[{"name":"boneskull","email":"boneskull@boneskull.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/msw_1.0.0_1783650186523_0.8297092334703438"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-24T02:23:22.253Z","modified":"2026-07-10T02:23:06.823Z","0.1.0":"2026-01-24T02:23:22.521Z","0.1.1":"2026-06-30T02:16:08.449Z","1.0.0":"2026-07-10T02:23:06.684Z"},"bugs":{"url":"https://github.com/boneskull/bupkis/issues"},"author":{"name":"Christopher Hiller","email":"boneskull@boneskull.com"},"license":"BlueOak-1.0.0","homepage":"https://github.com/boneskull/bupkis#readme","keywords":["bupkis","msw","mock","http","api","assert","assertion","test","mock-service-worker"],"repository":{"directory":"packages/msw","type":"git","url":"git+https://github.com/boneskull/bupkis.git"},"description":"MSW request verification assertions for Bupkis","maintainers":[{"name":"boneskull","email":"boneskull@boneskull.com"}],"readme":"# @bupkis/msw\n\nMSW (Mock Service Worker) request verification assertions for [Bupkis](https://bupkis.zip).\n\n## Installation\n\n```bash\nnpm install @bupkis/msw bupkis msw -D\n```\n\n## Usage\n\n```typescript\nimport { use } from 'bupkis';\nimport mswAssertions, { createTrackedServer } from '@bupkis/msw';\nimport { http, HttpResponse } from 'msw';\n\nconst { expect, expectAsync } = use(mswAssertions);\n\n// Create a tracked server instead of using setupServer directly\n// `using` automatically calls server.close() when the block exits\n{\n  using server = createTrackedServer(\n    http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Alice' }])),\n    http.post('/api/users', () => HttpResponse.json({ id: 2 })),\n  );\n\n  server.listen();\n\n  // Make requests\n  await fetch('https://api.example.com/api/users');\n  await fetch('https://api.example.com/api/users', {\n    method: 'POST',\n    body: JSON.stringify({ name: 'Bob' }),\n    headers: { 'content-type': 'application/json' },\n  });\n\n  // Verify requests were handled (sync - use expect)\n  expect(server, 'to have handled request to', '/api/users');\n  expect(server, 'to have handled request to', '/api/users', {\n    method: 'POST',\n  });\n\n  // For body matching, use expectAsync - it awaits body parsing automatically\n  await expectAsync(server, 'to have handled request to', '/api/users', {\n    method: 'POST',\n    body: { name: 'Bob' },\n  });\n\n  // Verify request count\n  expect(server, 'to have handled', 2, 'requests');\n\n  // Clear tracked requests between tests\n  server.clearTrackedRequests();\n\n  // server.close() called automatically here!\n}\n```\n\n## Assertions\n\n### {TrackedServer} to have handled request to {path}\n\nAsserts that the server handled a request to the specified path.\n\n**Success**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nexpect(server, 'to have handled request to', '/api/users');\n```\n\n**Failure**:\n\n```js\nexpect(server, 'to have handled request to', '/api/unknown');\n// AssertionError: Expected server to have handled request to \"/api/unknown\"\n```\n\n### {TrackedServer} to have handled request to {path} {options}\n\nAsserts that the server handled a request matching the path and options.\n\n**Options**:\n\n- `method` - HTTP method to match (case-insensitive)\n- `body` - Expected request body (uses \"to satisfy\" semantics)\n- `headers` - Expected headers (string for exact match, RegExp for pattern)\n- `times` - Exact number of times the request should have been made\n- `once` - Shorthand for `times: 1`\n\n**With method**:\n\n```js\nawait fetch('https://api.example.com/api/users', {\n  method: 'POST',\n  body: '{}',\n});\nexpect(server, 'to have handled request to', '/api/users', { method: 'POST' });\n```\n\n**With body** (use `expectAsync`):\n\n```js\nawait fetch('https://api.example.com/api/users', {\n  method: 'POST',\n  body: JSON.stringify({ name: 'Bob', extra: 'field' }),\n  headers: { 'content-type': 'application/json' },\n});\n\n// Use expectAsync for body matching - it awaits body parsing automatically\n// Uses \"to satisfy\" semantics - partial match works\nawait expectAsync(server, 'to have handled request to', '/api/users', {\n  method: 'POST',\n  body: { name: 'Bob' }, // extra field is ignored\n});\n```\n\n**With headers**:\n\n```js\nawait fetch('https://api.example.com/api/users', {\n  headers: { authorization: 'Bearer abc123' },\n});\n\n// Exact match\nexpect(server, 'to have handled request to', '/api/users', {\n  headers: { authorization: 'Bearer abc123' },\n});\n\n// RegExp match\nexpect(server, 'to have handled request to', '/api/users', {\n  headers: { authorization: /^Bearer / },\n});\n```\n\n**With times**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nawait fetch('https://api.example.com/api/users');\nawait fetch('https://api.example.com/api/users');\nexpect(server, 'to have handled request to', '/api/users', { times: 3 });\n```\n\n**With once**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nexpect(server, 'to have handled request to', '/api/users', { once: true });\n```\n\n**Negation** (assert no request was handled):\n\n```js\nexpect(server, 'not to have handled request to', '/api/admin');\n```\n\n### {TrackedServer} to have handled request matching {pattern}\n\nAsserts that the server handled a request matching a RegExp pattern.\n\n**Success**:\n\n```js\nawait fetch('https://api.example.com/api/users/123');\nexpect(server, 'to have handled request matching', /\\/api\\/users\\/\\d+/);\n```\n\n**Failure**:\n\n```js\nexpect(server, 'to have handled request matching', /\\/api\\/admin\\/\\d+/);\n// AssertionError: Expected server to have handled request matching /\\/api\\/admin\\/\\d+/\n```\n\n### {TrackedServer} to have handled request matching {pattern} {options}\n\nAsserts that the server handled a request matching a RegExp pattern with options.\n\n```js\nawait fetch('https://api.example.com/api/users/123', { method: 'DELETE' });\nexpect(server, 'to have handled request matching', /\\/api\\/users\\/\\d+/, {\n  method: 'DELETE',\n});\n```\n\n**Negation** (assert no request matched):\n\n```js\nexpect(server, 'not to have handled request matching', /\\/api\\/admin/);\n```\n\n### {TrackedServer} to have handled {number} requests\n\nAsserts that the server handled exactly the specified number of requests.\n\n**Success**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nawait fetch('https://api.example.com/api/users/1');\nawait fetch('https://api.example.com/api/users/2');\nexpect(server, 'to have handled', 3, 'requests');\n```\n\n**Failure**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nexpect(server, 'to have handled', 5, 'requests');\n// AssertionError: Expected server to have handled 5 request(s), but handled 1\n```\n\n### {TrackedServer} to have handled requests\n\nAsserts that the server has handled at least one request.\n\n**Success**:\n\n```js\nawait fetch('https://api.example.com/api/users');\nexpect(server, 'to have handled requests');\n```\n\n**No requests (use negation)**:\n\n```js\nexpect(server, 'not to have handled requests');\n```\n\n## Exports\n\n```typescript\n// Assertions (default export)\nexport { mswAssertions as default, mswAssertions } from './assertions.js';\n\n// Type guard\nexport { isTrackedServer } from './guards.js';\n\n// Zod schemas\nexport { PathMatcherSchema, TrackedServerSchema } from './schema.js';\n\n// Tracker factory and utilities\nexport { createTrackedServer, waitForBodies } from './tracker.js';\n\n// Types\nexport type {\n  PathMatcher,\n  RequestMatchOptions,\n  TrackedRequest,\n  TrackedServer,\n} from './types.js';\n```\n\n## API\n\n### `createTrackedServer(...handlers)`\n\nCreates an MSW server with request tracking capabilities. This is a drop-in replacement for `setupServer` from `msw/node`.\n\nThe returned server implements `Disposable`, so you can use `using` syntax (TypeScript 5.2+) for automatic cleanup:\n\n```typescript\nimport { createTrackedServer } from '@bupkis/msw';\nimport { http, HttpResponse } from 'msw';\n\n{\n  using server = createTrackedServer(\n    http.get('/api/users', () => HttpResponse.json([])),\n  );\n  server.listen();\n  // ... make requests and assertions ...\n  // server.close() called automatically when block exits\n}\n```\n\n### `isTrackedServer(value)`\n\nType guard that checks if a value is a `TrackedServer` instance.\n\n```typescript\nimport { isTrackedServer, createTrackedServer } from '@bupkis/msw';\nimport { setupServer } from 'msw/node';\n\nconst trackedServer = createTrackedServer();\nconst plainServer = setupServer();\n\nisTrackedServer(trackedServer); // true\nisTrackedServer(plainServer); // false\n```\n\n### `waitForBodies(server)`\n\nWaits for all tracked request bodies to be parsed and returns the requests.\n\n**Note:** When using `expectAsync` for body matching, this is called automatically. Use this when you need to access `req.body` directly.\n\n```typescript\nawait fetch(url, { method: 'POST', body: JSON.stringify({ name: 'Bob' }) });\n\n// Returns requests with bodies resolved\nconst requests = await waitForBodies(server);\nconsole.log(requests[0].body); // { name: 'Bob' }\n\n// For body assertions, prefer expectAsync (body parsing is automatic)\nawait expectAsync(server, 'to have handled request to', '/api', { body: data });\n```\n\n### TrackedServer Properties\n\n- `trackedRequests` - Array of all tracked requests (includes `bodyPromise` for each request)\n- `isTrackedServer` - Always `true` for tracked servers\n- `clearTrackedRequests()` - Clears all tracked request history\n- `[Symbol.dispose]()` - Calls `close()` (enables `using` syntax)\n\n## License\n\nCopyright © 2026 [Christopher \"boneskull\" Hiller][boneskull]. Licensed under [BlueOak-1.0.0](https://blueoakcouncil.org/license/1.0.0).\n\n[boneskull]: https://github.com/boneskull\n","readmeFilename":"README.md"}