{"_id":"@amritk/api","_rev":"24-f3ea43b900648323d86d3eacfa503b54","name":"@amritk/api","dist-tags":{"latest":"0.16.4"},"versions":{"0.0.0":{"name":"@amritk/api","version":"0.0.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.0.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"9b4c5ee42b739766dfc7544af036c320c0caba33","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.0.0.tgz","fileCount":2,"integrity":"sha512-SZzdsIN+SnNSWyppQynAfPnPQFUInx7R93e7sutxidhgJzZEYMsj/Au3tuevQzyVPCHAtUSelVKs1xhzZswAqQ==","signatures":[{"sig":"MEUCIQDpNAtAf0KBkA6Ihl1e/ZOGnc2oJKQTu704qA/ui08KYwIgXjkgWhuZtX37sWPL1TB1/xK/JXyFI+7hPxi0a8r6SqM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12758},"type":"module","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","development":"./src/index.ts"}},"gitHead":"081b1472f1d9d8d75ad57ebc76a17e6277744add","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"amritk","email":"amrit+spam@hockey-community.com"},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.16.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"_nodeVersion":"26.3.0","dependencies":{"@amritk/runtime-validators":"workspace:*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/api_0.0.0_1784237067035_0.9496364045026744","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@amritk/api","version":"0.1.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.1.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"8a55f5108b455d0066e5b4de24b774d8c84bbc0b","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.1.0.tgz","fileCount":56,"integrity":"sha512-ZeHCO90Y2gLrbeuyboOJgDQnoOpEi3KUHzpqZELv95oxNkjCLyESGVdE2AIXFr95754DSS1kMn4D3fQ5mFspQg==","signatures":[{"sig":"MEQCIHBvc8cBF0TCg+wWKo7tEZREX9MSHngY8XOs32ECm2drAiAgMFH9GtclQkT92AqOOJNuU0p5SZx6AQQhc1MirVcuhQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":183442},"type":"module","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"7a2e87cf653a9adf14d2f7c0aa19a366463ed6a8","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.1.0_1784255644550_0.45774321811440455","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@amritk/api","version":"0.2.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.2.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"a75e912d22aec813683b1a58cfec551fbfcb0e12","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.2.0.tgz","fileCount":59,"integrity":"sha512-Zs6BYtLTQaFTvNsMr1Fl+SfghXXMYNhY3ZWiUedcKCW2VQ87AxzMU8UdSiGaw13Ro32ADYxfakia2u9Coytoxg==","signatures":[{"sig":"MEUCIQDv7vgpXPFaYChfm+h49Vz3xYZ5gJnDsPkYOJGwn46zsQIgT+H9lV/955JjwVYut0csKnAsMi4jvIW8WpN7ARvpPnU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":229749},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"29a068e0d1778e8cc7d775b2829cccf572114aad","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.2.0_1784280005884_0.37734297995929733","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@amritk/api","version":"0.3.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.3.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"44109fb5bfd5e49c1aa647205aaf54919d019566","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.3.0.tgz","fileCount":73,"integrity":"sha512-093fc125ddW9Ay8/qiigVvCRrNzmwzLpIDB+3QzJOJZSOkNyKQHGel7M/VYNZm/XFbW4Or6XZsSd9MrOyYUGtQ==","signatures":[{"sig":"MEUCIC00znK2abHM6xX4fdxidixfBFfeH8p7e4W0rHh4EA/wAiEA6oZN1R+QkFn9cIR3BGf18OPmLvIVLSGRCkymPI3BAR4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":291523},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"54ab7f287650c8da6f8603af733d9e1c59854109","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.3.0_1784429672210_0.1788024067561682","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@amritk/api","version":"0.4.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.4.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"89db6d7d17b5ea765cf50b7fbd768b8e054e7160","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.4.0.tgz","fileCount":89,"integrity":"sha512-GiIBPj/yXOzYzTghEpJsvZxH+EvMcx2DvVdGtPuHFeIU00q6cYpytrIt884Sh9+5Yd+bYSJzZeEPsnEozJCdfQ==","signatures":[{"sig":"MEYCIQCnTZAg+L/2a/NlWuLfBvEJ+/ZG6vN0fnXzp6iQJFRzJgIhAMUpXbrXoSbpPinO2T8fnLzy994zxjmtS1VCuWKHsIbp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":262494},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"b90fd1c8c047fc8481705018eddd577d19108c96","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.4.0_1784452128169_0.8521951259300553","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@amritk/api","version":"0.5.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.5.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"a65eaef84017d44a8eb638213cfb3eab8cb720ee","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.5.0.tgz","fileCount":139,"integrity":"sha512-Zv1MgnbfVRcI0O9jLnRDnjPVJGsnLfjOTVfSp0iw5P47DdP7TVkFD6USD2rU3asuMFWGWhJo/p6iAhOtHHJ6jA==","signatures":[{"sig":"MEUCIQCINYVdk0FBCNogOE7NBMkoLKBEv9kVxkLrSgPANB6JtgIgEiJgLD4N6GwLg4OoUyVkWdE/N1Oz4V3Opf/Qcq86chM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":367103},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"ffae0ea7b890071fbb29072cb5e33e9ea513c66b","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.5.0_1784628925985_0.1912684129497184","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@amritk/api","version":"0.6.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.6.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"f7448c78090213773596bea188d9b0e15cbd1a2b","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.6.0.tgz","fileCount":139,"integrity":"sha512-C1F+fovn6PONkSFfrFvqFpo/7vtW6Aao3k4dbEfdhngBHlON4Yr8632H8ZkXh3TcuNWmxicOk1xHT1LW+RxhEA==","signatures":[{"sig":"MEUCIGyD1fmnhhTZEyQYbf7L5mEinYHvvLVCbqxQTZtnW4iyAiEA77Az91h3/BGohtdUWjCBayVvOy4/JDZRZ/6Tnrd64Xg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":372855},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"422339cbd45946cd2ab126a80de8532e39cb5872","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.7.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.6.0_1784665070973_0.43805850140702396","host":"s3://npm-registry-packages-npm-production"}},"0.6.1":{"name":"@amritk/api","version":"0.6.1","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.6.1","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"f074478581a4d667b17733fede7320f21b484f6c","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.6.1.tgz","fileCount":140,"integrity":"sha512-IDMl1HpGDDg4Ru5y1jkiELznrcj7Jg6PIHtFfZRwStt4FYibTjhfCW26Sj8FXbugrLvW24WHwBGSyOa5Mam+Vg==","signatures":[{"sig":"MEUCIEE9+Nioqgvb0G4Q//rJ4TQNVlFiMy+Zr4sAi7kIyqgvAiEA/2xRADNUZIY7Oa+QWzqwM1ZkaIrwbqU3VRRKYBduAZU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.6.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":378330},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"30b4c324c09d2928d62084973cdde49d3356b4b4","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.6.1_1784702755699_0.5769566614603587","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@amritk/api","version":"0.7.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.7.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"187cc3535d82ce8a83a435777d014f2f561bc91c","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.7.0.tgz","fileCount":148,"integrity":"sha512-N/mwHmY80gyg8yYIRE/THc7Ch2u3xrBcGkFgj6Ripm+DsA6RTQRJU7VQBckMM7zCvbFiQFMB8X1TIQN/0Ivcyw==","signatures":[{"sig":"MEUCIQC1jterBb2uDE391i+dIjU024ZfZRWhuF+tLUx1mHJOfgIgEpxA9XsBVy8MvgGb0155w2GNuhXyauMqqiphlAV4NiA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.7.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":400265},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"292547c9c8249547456ab37a6d395ea86c87f177","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.7.0_1784789301149_0.25820233974603224","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@amritk/api","version":"0.8.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.8.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"bef0633966d32146e9d1b3f46485309724e88fd9","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.8.0.tgz","fileCount":150,"integrity":"sha512-C2poarqP8LNrgSmOqnDgBxB3dI2mIUkW7bp8p5DRnguyihyMU97pKt9l6ivH1jCtGRuhk4JEStwyn68/jd6Law==","signatures":[{"sig":"MEQCICuxm+iXqphR6aqfzORfyytn2m+sc/LUxZ6ecQNMKTazAiA/ouA7KceecyUMKFW911qZ46RKPNS5QsCKWqnFSaYmAg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.8.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":429134},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"4008a1da41512933f7d05206b0d7bc6563cea4ce","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.9.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.8.0_1784875831959_0.517075048414193","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@amritk/api","version":"0.9.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.9.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"64d1d24184602053a3f5f6108ec7deb52660bbbc","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.9.0.tgz","fileCount":160,"integrity":"sha512-kqTVDkW/wBCOJrVGOyWZezHRalA9vMtCtRJgomB79jOA1T2g8zQHV0x2twVVyTiy2bL/lzJBOT+KRVZptdMibw==","signatures":[{"sig":"MEYCIQCqxyTw7gjVNsDKP8g5PzmfHURWZ92tP2IASmKGatGlsAIhAN+ILRuyb1er/5w+IKhvwfZzhjY5u3+5yszCAlpTc6ow","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.9.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":484746},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"888fc55e31282d1b18ff9a11ed7ff20bed3ef5fd","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"12.0.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.9.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.9.0_1785023101015_0.6021434922974511","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"@amritk/api","version":"0.10.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.10.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"b1ef560905d5d0d85d25eb52ad5600d1a9435756","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.10.0.tgz","fileCount":162,"integrity":"sha512-SB6eN5oHyrN/HWujePm3Vbr3/QTns5fVs2K6RgXUuKR77esCcQt4oWP/9Ny4SJaJ+oZrOcE/T9pgrtiM6pV0YA==","signatures":[{"sig":"MEUCIBnS9inucZJ+tMS8PefYykcB6rISfS1etbjUWDUMq2urAiEA3kp/IjiiRqAXbGrvvz6r4IETzD4xKXKaTNg2Lfdzuys=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.10.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":487740},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"631a7cd6378310d1088f45ad59945142c6e9fbd1","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.18.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.9.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.10.0_1785045647348_0.934846704296918","host":"s3://npm-registry-packages-npm-production"}},"0.11.0":{"name":"@amritk/api","version":"0.11.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.11.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"dc7c1d6bd23e164e63a0c3c2617d8711cf808cbf","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.11.0.tgz","fileCount":162,"integrity":"sha512-Z37GIxpjM4fOLjE4DKGDfwcJuxH/VqvhqyAZ1EkgKDtA19tddk4Y2ojbcDmYtJHeJiIASUDdLvkjj3lwwl7YQA==","signatures":[{"sig":"MEQCIHOA8SXH63AKN86qBgOmAjk3DPwHkqEDVlud2IatPWVpAiBjaQt3zvYSGyxcAvG4mDbmOHcvNymKGQFxI9GvbiUmiA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.11.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":496100},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"3630667665a873c96f071e3b8d12f538e751d4f0","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.18.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.9.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.11.0_1785262237951_0.6622403167717392","host":"s3://npm-registry-packages-npm-production"}},"0.12.0":{"name":"@amritk/api","version":"0.12.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.12.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"2e604bcde8959919a9707251d6e0588c5e7fdf82","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.12.0.tgz","fileCount":164,"integrity":"sha512-BNITaoXZKivEJlPEVCYgsmTmeOyTxeiH0A0IsfR+BQTxo+9GEoLCgMsrAkvrwW1060hoONsRvLjVI8gNz4k7dw==","signatures":[{"sig":"MEQCIDbmaQSAfITy5su9o7VUgLtRbWnFT4vbZlSVLmhrB/GfAiBoV5uzDxrUfnhig8QRkWfbpo1dCW0YUOlC7URwM4RXtg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.12.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":504849},"type":"module","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"64a723015dc75fcde030b3124e9417bea4e2bb9d","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"0.9.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.12.0_1785471620271_0.2901016697711689","host":"s3://npm-registry-packages-npm-production"}},"0.13.0":{"name":"@amritk/api","version":"0.13.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.13.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"f896e3feb5704c60c4a6d4d845a0498cc9fbb2f4","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.13.0.tgz","fileCount":170,"integrity":"sha512-wKLC7ye8MiV3iBBm0Jz9jIQIZfDdm9ExXJ0CUBjWftfGUinGVwLEYPRxJ6QBhMOHGrRC/w2rQLsSDQwAYD+4PA==","signatures":[{"sig":"MEUCIQDw5VkUF8sTtp+53i8C1kI2oLlWpZPV6TP5Vf1WRTZ38gIgGFWHqxCd4WGsgUJFSbA7SXGpKnL3AfRCwDlnBnupMJ4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.13.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":540166},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"846ea8bfd0ad501a9f7a6d6fa21258b558ac5e4a","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"^0.10.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.32","miniflare":"^4.20260722.0","typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.13.0_1785867565042_0.25951173417136975","host":"s3://npm-registry-packages-npm-production"}},"0.14.0":{"name":"@amritk/api","version":"0.14.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.14.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"a69bc7068d6d7b34a82dae01990648e961c73912","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.14.0.tgz","fileCount":174,"integrity":"sha512-dGx8lSIg0hGnnvlaKC8b3sK3IyJSTia2mbSuKW0BFOObLTR7KRbNTvBBvWNJN01f0tgsCzhQFTwule+LWqyOog==","signatures":[{"sig":"MEUCIQDmjyPDDWmehKPwvWThATz01yt6Ru4PrtSu3ruRXiZmYQIgRVnXaLUe5tjRrWJZhyGrlCAOP56dD5y3qzYSMS+tnTo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.14.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":576401},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"bca90d95d94dfbe78cabd0d47214d245ff56e987","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"^0.10.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.32","miniflare":"^4.20260722.0","typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.14.0_1786003979064_0.43903428457300864","host":"s3://npm-registry-packages-npm-production"}},"0.15.0":{"name":"@amritk/api","version":"0.15.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.15.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"3e0fee8f1d88a2f92d0bffa03e9f7f6e6776f425","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.15.0.tgz","fileCount":166,"integrity":"sha512-gZTpu0OmDaJLuc899isaHaWaDujPiPIh9hDEgodF6DAg+HerDKpKf2xfB6POMYG8QzblB7Ou+ijoFCzG9tLatg==","signatures":[{"sig":"MEYCIQCnm8Q3V/fHxd9Ts29BikarWLidI/VRwuWtZBpUzjp8WAIhAIW3uRrydEJKVJlwLdZxzMpEhuCSEJRnzJs55DTZvndL","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.15.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":573145},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"f4e3fa10cfbe4279547f55836cf7ae638af1e3a4","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"^0.10.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.32","miniflare":"^4.20260722.0","typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.15.0_1786081109976_0.1621416918606624","host":"s3://npm-registry-packages-npm-production"}},"0.15.1":{"name":"@amritk/api","version":"0.15.1","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.15.1","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"2d965be0ccb5dc886715f3c13a2e18f5eee9f60c","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.15.1.tgz","fileCount":166,"integrity":"sha512-/sKzmN3WrlOnQvcdny9+/FbOEENknsIhD/ySIMs0i6q9pPZum7BfKCD2YHbYa2jIl8+t6BFW6dw7L2BCnyWs2Q==","signatures":[{"sig":"MEUCIFtaHFSoeSNowsNd/ysTjpGIw5omvRD+yM1gM13hHDATAiEAq2yy7aNknfLZlEvAIW/rRCaTcyOFk7J7EelwFZJOzQU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.15.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":573145},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"a4683ed81fdf8c600c59b7b2589c019a1615b972","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"^0.10.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.32","miniflare":"^4.20260722.0","typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.15.1_1786173894007_0.6687398206086266","host":"s3://npm-registry-packages-npm-production"}},"0.15.2":{"name":"@amritk/api","version":"0.15.2","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.15.2","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"be220ce10bfb86aae8f422669fde8d11f87a044c","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.15.2.tgz","fileCount":166,"integrity":"sha512-rDI6B/XzWwnh52xEUsPYWxRc4M4W+xYUeYrDXcgFi3fhFzQ+if4d0qvj6RzzWGy4UJuHbe7Km2UpT5vAlQNHRA==","signatures":[{"sig":"MEUCICexu1Ok4E/BoBceey6pDMSqSrfu5cwV79aaVSOJ9oGYAiEAy6k5mmNo2w5oFs7ZYd0QDuZ/sv8IlCO/OHRX0rcI5og=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.15.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":577001},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"605071c88c9f2416d455c091e1c662e17bf104db","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.0","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.1 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/runtime-validators":"^0.11.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","@hey-api/openapi-ts":"^0.99.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.15.2_1786479962475_0.4050808229525771","host":"s3://npm-registry-packages-npm-production"}},"0.16.0":{"name":"@amritk/api","version":"0.16.0","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.16.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"baaf5e24ebb752eb28fabc57eab2e2e25f4d3ce2","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.16.0.tgz","fileCount":194,"integrity":"sha512-CsLLFmhpI3apC2HhWKq9VuUUfx+feqapmt5TGJvWaTvUUJhaTvBTyAjrEJ1IxIPmcKC5SSXTdTG/ZVYPt2LMrg==","signatures":[{"sig":"MEQCIFiheQUqEJQDf8Uch9tHe6OLzWHbdKeF6S9JGosxDDajAiB/PC0KNAc7KkR+0+dCOIq2PFwqnJBdQTq3MeOJWv89CQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.16.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":709406},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"3078ec8c5b266082c862fecd86f122d21c591270","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"@amritk/runtime-validators":"^0.12.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.16.0_1788224630104_0.4429451072264974","host":"s3://npm-registry-packages-npm-production"}},"0.16.1":{"name":"@amritk/api","version":"0.16.1","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.16.1","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"75ae22c453a57c89acaaa394854358f8e8263a02","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.16.1.tgz","fileCount":194,"integrity":"sha512-hMJn5xiipdUyriJpXMIZlYDNyGsQtjlip84ukiFkIojyDAtdiVKzbHUC31Klj9DZMqSHJyA+Kd0jWOrdfart+g==","signatures":[{"sig":"MEUCICkYyvbaJNlDEQnffn6/xOedW+B/yq9eN1u0scGuFkoUAiEAuY0O9InjErJp1wZQ+6dTKu//9kfjvsSaO3q9MBCw6ts=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.16.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":709406},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"86e0964f0c805ea2381abadc17285dfe3efad57f","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"@amritk/runtime-validators":"^0.12.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.16.1_1788332430388_0.5176732461836164","host":"s3://npm-registry-packages-npm-production"}},"0.16.2":{"name":"@amritk/api","version":"0.16.2","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.16.2","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"7df8d988b0dee3c030af59b9dea9070eb6b649da","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.16.2.tgz","fileCount":194,"integrity":"sha512-G5Rh3YAlM4yNEjljlTUE7AJ5L3UPo2xgzU2t+vWMKpcme2ygSWt1FUAY0ZlB2mldoyoRNtTgyF7DvrS28T+2NQ==","signatures":[{"sig":"MEYCIQDnG00978I0QzFh3WnRIuw8eS2hs0fWfTypQ7zHMBHs6wIhAKyqn85kA1e1q+mzWGtNF1njVT7vOl2YU/9XuULTKt6B","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.16.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":710015},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"16855e54c4663acf512473d2387d3a1adba4ce48","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@amritk/runtime-validators":"^0.13.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.16.2_1788381914134_0.536897547504043","host":"s3://npm-registry-packages-npm-production"}},"0.16.3":{"name":"@amritk/api","version":"0.16.3","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/api@0.16.3","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"dist":{"shasum":"729a3ed2136137ad06a0be0a572490a403648dea","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.16.3.tgz","fileCount":194,"integrity":"sha512-k6eH6FDWJK8/6erPXo/tIaa+ciqZ1zogvMMglSi/tM0kXdT9hlwl6e6NLKMtGE5ftJbsjlFeZ1JcLXoaktVaKw==","signatures":[{"sig":"MEUCIBaNHSieBmKaI22vS9Pe3V7PNXt95PqYCW/gvlQcEwrEAiEAnIhSRVTdki4U8FwOPkAy43rWNREZB4W6iYpa5mqDlPE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.16.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":710714},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"}},"gitHead":"1f4bf1313967417d33eef93e6a6c553e12d218c6","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/api","bench":"bun run ./bench/run.ts","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench":"bun run --filter='@amritk/runtime-validators' build","prebench:vs":"bun run bench:vs:build","types:check":"tsgo -p . --noEmit","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","bench:workerd":"bun run ./bench/run-workerd.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","prebench:vs:bun":"bun run bench:vs:build","prebench:workerd":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"repository":{"url":"git+https://github.com/amritk/mjst.git","type":"git","directory":"packages/api"},"_npmVersion":"11.19.1","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@amritk/runtime-validators":"^0.13.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","@hono/zod-validator":"^0.9.0"},"_npmOperationalInternal":{"tmp":"tmp/api_0.16.3_1788568541269_0.5187028249839571","host":"s3://npm-registry-packages-npm-production"}},"0.16.4":{"name":"@amritk/api","version":"0.16.4","description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","type":"module","sideEffects":false,"engines":{"node":">=20"},"license":"MIT","author":{"name":"amritk"},"keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"repository":{"type":"git","url":"git+https://github.com/amritk/mjst.git","directory":"packages/api"},"homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","bugs":{"url":"https://github.com/amritk/mjst/issues"},"publishConfig":{"access":"public"},"scripts":{"build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","types:check":"tsgo -p . --noEmit","test":"NODE_ENV=production vitest run --root ../.. packages/api","prebench":"bun run --filter='@amritk/runtime-validators' build","bench":"bun run ./bench/run.ts","bench:vs:build":"bun run --filter='@amritk/runtime-validators' build && bun run ./bench/emit-compiled.ts && bun build ./bench/vs-frameworks.ts --target=node --external=@amritk/runtime-validators --outfile ./bench/.fixtures/vs-frameworks.mjs","prebench:vs":"bun run bench:vs:build","bench:vs":"node ./bench/.fixtures/vs-frameworks.mjs","prebench:vs:bun":"bun run bench:vs:build","bench:vs:bun":"bun ./bench/.fixtures/vs-frameworks.mjs","prebench:workerd":"bun run bench:vs:build","bench:workerd":"bun run ./bench/run-workerd.ts","bench:workerd:allocations":"bun run ./bench/run-workerd-allocations.ts","prebench:workerd:allocations":"bun run bench:vs:build","bench:workerd:body":"bun run ./bench/run-workerd-body.ts","prebench:workerd:body":"bun run bench:vs:build"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./bundler":{"types":"./dist/bundler/index.d.ts","default":"./dist/bundler/index.js"},"./dev":{"types":"./dist/dev/index.d.ts","default":"./dist/dev/index.js"}},"dependencies":{"@amritk/runtime-validators":"^0.14.0"},"devDependencies":{"@hono/zod-validator":"^0.9.0","hono":"^4.12.34","miniflare":"^4.20260730.0","typescript":"^5.9.0","zod":"^4.4.3"},"gitHead":"43832d7a66b87b09af416dc3d38ee65e54bc4797","_id":"@amritk/api@0.16.4","_nodeVersion":"24.20.0","_npmVersion":"11.19.1","dist":{"integrity":"sha512-UvYQpfHqyzrFfzz8PGFjuwh6Mo80H4Aj1idurXVEqUvtNGFZLyvOM9OJvjjCYxQO1A6W3R7Dcidi8ZVRCoz+8w==","shasum":"87391907ecf450db618b50d87e72902edd2b8afb","tarball":"https://registry.npmjs.org/@amritk/api/-/api-0.16.4.tgz","fileCount":194,"unpackedSize":711238,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2fapi@0.16.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCq/7efrswRcghuRLZq7vKmWaHRTizIvFpHzr43iCh5swIhAJp1hpENoLwwfWSx5ba2QIMZURMX4oMZdBperAN3yT2z"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:141cdcb4-20ff-4c0b-8181-2c61e0677526"}},"directories":{},"maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/api_0.16.4_1789154034698_0.7695025783371106"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-16T21:24:26.846Z","modified":"2026-09-11T19:13:55.189Z","0.0.0":"2026-07-16T21:24:27.201Z","0.1.0":"2026-07-17T02:34:04.707Z","0.2.0":"2026-07-17T09:20:06.021Z","0.3.0":"2026-07-19T02:54:32.353Z","0.4.0":"2026-07-19T09:08:48.300Z","0.5.0":"2026-07-21T10:15:26.167Z","0.6.0":"2026-07-21T20:17:51.159Z","0.6.1":"2026-07-22T06:45:55.856Z","0.7.0":"2026-07-23T06:48:21.298Z","0.8.0":"2026-07-24T06:50:32.112Z","0.9.0":"2026-07-25T23:45:01.219Z","0.10.0":"2026-07-26T06:00:47.511Z","0.11.0":"2026-07-28T18:10:38.097Z","0.12.0":"2026-07-31T04:20:20.448Z","0.13.0":"2026-08-04T18:19:25.272Z","0.14.0":"2026-08-06T08:12:59.238Z","0.15.0":"2026-08-07T05:38:30.145Z","0.15.1":"2026-08-08T07:24:54.158Z","0.15.2":"2026-08-11T20:26:02.666Z","0.16.0":"2026-09-01T01:03:50.239Z","0.16.1":"2026-09-02T07:00:30.547Z","0.16.2":"2026-09-02T20:45:14.289Z","0.16.3":"2026-09-05T00:35:41.434Z","0.16.4":"2026-09-11T19:13:54.873Z"},"bugs":{"url":"https://github.com/amritk/mjst/issues"},"author":{"name":"amritk"},"license":"MIT","homepage":"https://github.com/amritk/mjst/tree/main/packages/api#readme","keywords":["json-schema","typescript","api","openapi","validation","hono","fastify","express","nextjs","mjst"],"repository":{"type":"git","url":"git+https://github.com/amritk/mjst.git","directory":"packages/api"},"description":"Framework-agnostic, contract-first API layer built on mjst JSON Schema tooling. Typed routes, fast request/response validation, and OpenAPI 3.2 generation with no extra code — adapters for fetch (Hono, Next.js, Bun, Workers) and Node (Express, Fastify).","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"readme":"# @amritk/api\n\nContract-first, framework-agnostic API layer built on [mjst](../../README.md)'s\nJSON Schema tooling. Declare each route once — method, path, request schemas, response\nschemas, handler — and get typed handlers, fast request/response validation,\nand an OpenAPI 3.2 document with **no extra code**. Two thin adapters connect\nthe same API to every JavaScript server framework — Bun, Cloudflare Workers,\nDeno, Hono, Next.js, SvelteKit, Nitro/Nuxt, Elysia (fetch), and `node:http`,\nExpress, Fastify, Koa, NestJS (Node) — with a\n[recipe for each](#serving-it).\n\n- **One contract, everything derived.** The JSON Schemas in a route type the\n  handler (via `FromSchema`), validate requests at runtime, and embed verbatim\n  into the OpenAPI document — OpenAPI 3.2's schema dialect *is* JSON Schema\n  Draft 2020-12, so there is no conversion layer to drift.\n- **Fast by structure.** All schema work (validator preparation, coercion\n  planning, path parsing) happens once at startup. Per request: an O(1) map hit\n  for static paths, a boolean guard that short-circuits and never allocates on\n  valid input, and error collection that only runs after a guard has already\n  said no. Query strings and bodies are parsed lazily — routes that do not\n  declare them never pay for them.\n- **Typed end to end.** Handlers receive `params` / `query` / `body` already\n  validated and coerced, typed from the schema literals. The return type is\n  derived from the `responses` map — returning an undeclared status or a wrong\n  body shape is a compile error.\n- **Eval-free.** The default engine is `@amritk/runtime-validators` — no\n  `new Function`, so it runs under strict CSP, Cloudflare Workers, and\n  React Native. Swappable for generated validators when you want maximum\n  steady-state throughput (see below).\n- **The whole HTTP surface.** Streaming/raw replies with client-disconnect\n  signals, raw body access for webhook signatures, body size limits,\n  request-header schemas, hook chains for CORS/rate limits/security headers,\n  and pluggable error envelopes — each shipped in both the runtime and\n  compiled engines.\n- **Contract/handler split with a derived typed client.** Declare contracts as\n  pure data (`defineContract`), bind server handlers separately\n  (`implementRoute`), and derive a typed fetch client (`createClient`) from\n  the same literals — no codegen, browser-safe imports, the `hc` replacement\n  for teams leaving Hono RPC.\n- **One dependency, many integrations.** Drizzle, Better Auth, Sentry, and\n  typed clients connect through seams — `context`, `mounts`, `onError`,\n  `locals`, OpenAPI — not bundled SDKs. Recipes below.\n\n## Contents\n\n**Getting started**\n- [How this compares to a web framework](#how-this-compares-to-a-web-framework) · [what it deliberately does not do](#what-this-deliberately-does-not-do)\n- [Usage](#usage) · [Contracts without handlers (browser-safe)](#contracts-without-handlers-browser-safe) · [Typed client: `createClient`](#typed-client-createclient)\n\n**[Serving it](#serving-it)** — one `Api`, two adapters, a recipe per framework\n- fetch: [Bun](#bun) · [Cloudflare Workers](#cloudflare-workers) · [Deno](#deno) · [Hono](#hono) · [Next.js](#nextjs-app-router) · [SvelteKit](#sveltekit) · [Nitro / Nuxt](#nitro--nuxt) · [Elysia](#elysia)\n- Node: [`node:http`](#nodehttp) · [Express](#express) · [Fastify](#fastify) · [Koa](#koa) · [NestJS](#nestjs) · [anything else](#anything-else)\n\n**Requests and responses**\n- [Options (`createApi`)](#options-createapi) · [Validation semantics](#validation-semantics) · [String formats](#string-formats) · [Branded IDs](#branded-ids-nominal-types-for-params) · [Cross-field refinement](#cross-field-refinement)\n- [Form and multipart bodies](#form-and-multipart-bodies) · [Raw text and binary bodies](#raw-text-and-binary-bodies) · [Raw request bodies and size limits](#raw-request-bodies-and-size-limits)\n- [Streaming and raw responses](#streaming-and-raw-responses) · [Returning a raw `Response`](#returning-a-raw-response-escape-hatch) · [Multiple `set-cookie` headers](#multiple-set-cookie-headers) · [The platform request: `request.raw`](#the-platform-request-requestraw)\n\n**Middleware, security, state**\n- [Hooks: CORS, rate limits, security headers](#hooks-cors-rate-limits-security-headers) · [Built-in security hooks](#built-in-security-hooks) · [Signed cookies](#signed-cookies)\n- [Framework-parity helpers](#framework-parity-helpers) · [Client-side auth refresh](#client-side-auth-refresh) · [Per-request state: `locals`](#per-request-state-locals)\n\n**Engines**\n- [Plugging in generated validators](#plugging-in-generated-validators) · [Development: hot reloading](#development-hot-reloading) · [Production: the compiled engine](#production-the-compiled-engine)\n\n**Integration recipes**\n- [App context: Drizzle, sessions](#app-context-drizzle-sessions-anything-per-request) · [Guards](#guards-authorize-once-declare-the-outcome) · [Deny-by-default: `secureRoutes`](#deny-by-default-secureroutes) · [Auth: Better Auth](#auth-better-auth) · [Sessions: a production setup](#sessions-a-production-setup)\n- [Observability](#observability-metrics-and-request-logs) · [OpenAPI: servers, auth schemes, components](#openapi-servers-auth-schemes-shared-components) · [Error reporting: Sentry](#error-reporting-sentry) · [Typed client for external consumers](#typed-client-for-external-consumers) · [Schemas from Zod, TypeBox, Valibot, Effect](#schemas-from-zod-typebox-valibot-effect)\n\n**About**\n- [Integration philosophy](#integration-philosophy) · [Requirements and stability](#requirements-and-stability) · [Scope notes](#scope-notes)\n\n## How this compares to a web framework\n\n`@amritk/api` is not a server — `handle(ApiRequest) → ApiResponse` is the whole\nruntime, and a framework hosts it ([`app.mount('/', toFetchHandler(api))`](#hono)\nunder Hono, [middleware](#express) under Express). So the question is rarely\n\"this **or** Hono\"; it is this *inside* whatever you already run, weighed against\nthe stack you would otherwise assemble for a validated, documented API — a\nframework plus a validator middleware plus an OpenAPI plugin plus an RPC client\n(`hono` + `@hono/zod-validator` + `@hono/zod-openapi` + `hc`, or the\nExpress/Fastify equivalents).\n\nAgainst that stack:\n\n| | framework + validator + OpenAPI plugin | `@amritk/api` |\n|:--|:--|:--|\n| Declaring a route | a chain — `app.get(path, zValidator('param', schema), handler)` — and the OpenAPI plugin adds a *second* way to declare the same route | one [`defineRoute`](#usage) object: method, path, request schemas, `responses`, handler |\n| Schema language | Zod / Valibot / TypeBox, converted for the document | JSON Schema Draft 2020-12 — or author in [Zod/TypeBox/Valibot/Effect](#schemas-from-zod-typebox-valibot-effect) and convert once, at build time |\n| OpenAPI | a conversion layer between what runs and what is published | OpenAPI 3.2's schema dialect **is** Draft 2020-12, so contract schemas embed verbatim — no conversion to drift |\n| Responses | inferred from whatever the handler returned, and usually unvalidated | [declared](#validation-semantics): an undeclared status is a compile error, and `validateResponses` catches shape drift in dev/test |\n| Typed client | `hc` (coupled to the framework) or codegen from the document | [`createClient`](#typed-client-createclient) from the same literals — no codegen, no round-trip, [browser-safe subpath](#typed-client-createclient) |\n| Authorization | middleware, invisible to the document | [`guards`](#guards-authorize-once-declare-the-outcome) can only deny with a status the contract *declares*, so the 401 is in the OpenAPI output and in the client's union |\n| Production build | none | [`compileToModule`](#production-the-compiled-engine) emits a fused handler — inlined guards, schema-derived serializers, a precomputed document |\n\nThe through-line: elsewhere a route is a chain of functions and the document is\nderived by a second mechanism; here the contract is data, and the handler types,\nthe runtime validation, the OpenAPI document, the typed client, and the compiled\nmodule are all projections of it. There is one place to edit and nothing to keep\nin sync.\n\nOn speed, the [benchmark tables](#production-the-compiled-engine) measure the\nsame three routes through the same web-standard `Request` objects on workerd,\nNode, and Bun. Against `hono + zod` — the other column that actually validates —\nthe compiled engine leads every case on all three runtimes. Against *unvalidated*\nbare Hono it leads or matches the GET cases and trails on the POST case, which is\ndominated by body parsing that every column pays.\n\n### What this deliberately does not do\n\nA framework is still a framework. This package has no:\n\n- **Middleware onion.** Pre-routing gates (`onRequest`), response decorators\n  (`onResponse`), and per-route [`guards`](#guards-authorize-once-declare-the-outcome)\n  cover the same ground with a flatter model — and on the Node adapter there are\n  no hooks at all, [by design](#serving-it): you use the host framework's chain.\n- **Serving WebTransport.** Server-sent events are first class (`sseStream`,\n  `formatSse`, and [streaming responses](#streaming-and-raw-responses)), and\n  WebSocket upgrades (`upgradeWebSocket`, `acceptWebSocket`) plus typed message\n  contracts are covered under [Realtime](#realtime-webtransport-with-a-websocket-fallback);\n  a WebTransport *server* is not (see there for why).\n- **JSX/SSR or template rendering.** Nothing here renders HTML except the\n  Scalar docs page [`createDocs`](#framework-parity-helpers) serves; static\n  files go through the injected-reader [`createStatic`](#static-files) hook, not\n  a built-in filesystem layer.\n- **A plugin ecosystem.** CORS, CSRF, rate limiting, security headers, ETag,\n  compression, request IDs, and health checks ship as\n  [hook factories](#built-in-security-hooks); beyond those you write it or take\n  it from the framework you mounted into.\n- **A router for anything but contracts.** A path with no contract is a 404, or\n  falls through to the host — this serves your API surface, not your whole app.\n\nWhich is the point: run Hono, Express, or Fastify for the app, and let contracts\nown the API surface. The [recipes](#serving-it) mount into either side of an\nexisting app, so adoption is per route, not per repository.\n\n## Usage\n\n```ts\nimport { createApi, defineRoute, toFetchHandler } from '@amritk/api'\n\nconst getUser = defineRoute({\n  method: 'get',\n  path: '/users/{id}',\n  summary: 'Fetch a user',\n  request: {\n    params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] },\n    query: { type: 'object', properties: { verbose: { type: 'boolean' } } },\n  },\n  responses: {\n    200: { body: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' } }, required: ['id', 'name'] } },\n    404: {},\n  },\n  handler: ({ params, query }) => {\n    // params.id is a number, query.verbose is boolean | undefined — already\n    // validated, already coerced from their string transport form.\n    return params.id === 1 ? { status: 200, body: { id: 1, name: 'Ada' } } : { status: 404 }\n  },\n})\n\nconst api = createApi({\n  routes: [getUser],\n  info: { title: 'Users API', version: '1.0.0' },\n})\n```\n\n`api.handle` is the whole runtime; `GET /openapi.json` serves the generated\ndocument (configurable via `openApiPath`) — serialized once per process and\nsent with a strong `etag` + `cache-control: no-cache`, answering `304` to a\nmatching `if-none-match`. Note: for the types to flow, write schemas inline\n(as above) or declare shared ones `as const` — a plain `const` widens the\nliteral before `defineRoute` sees it.\n\n### Contracts without handlers (browser-safe)\n\n`defineRoute` couples the contract to its handler, which is perfect for a\nserver-only codebase — but a frontend that wants the contract types must not\nbundle server code. `defineContract` declares the same contract as **pure\ndata**, `implementRoute` binds the handler server-side, and the one-shot\n`defineRoute` keeps working unchanged (every route *is* a contract):\n\n```ts\n// contracts.ts — imported by server AND browser\nimport { defineContract } from '@amritk/api/client'\n\n// One object is the single source of truth: the client covers exactly these\n// keys, and adding an endpoint here wires it into client and server at once.\nexport const contracts = {\n  getUser: defineContract({\n    method: 'get',\n    path: '/users/{id}',\n    request: { params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] } },\n    responses: {\n      200: { body: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' } }, required: ['id', 'name'] } },\n      404: {},\n    },\n  }),\n  getProfile: defineContract({\n    method: 'get',\n    path: '/users/{id}/profile',\n    request: { params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] } },\n    responses: { 200: {} },\n  }),\n}\n```\n\n```ts\n// routes.ts — server only\nimport { implementRoute, routeImplementer } from '@amritk/api'\nimport { contracts } from './contracts'\n\nexport const getUser = implementRoute(contracts.getUser, ({ params }) =>\n  params.id === 1 ? { status: 200, body: { id: 1, name: 'Ada' } } : { status: 404 },\n)\n\n// With an app context, bind the implementer once (the routeFactory counterpart):\nconst implementAppRoute = routeImplementer<AppContext>()\nexport const getProfile = implementAppRoute(contracts.getProfile, ({ context }) => /* ... */)\n```\n\n### Typed client: `createClient`\n\n`createClient` derives a typed fetch client from a record of contracts — no\ncodegen, no OpenAPI round-trip, works in any browser/worker/Node bundle. The\nsame literals that type the handlers type each call, so client and server\ncannot drift. This is the framework-agnostic replacement for Hono's `hc`:\n\n```ts\n// client.ts — browser bundle; pulls in zero server code\nimport { buildParamPath, createClient, isUnexpectedStatusError, toSearchParams } from '@amritk/api/client'\nimport { contracts } from './contracts'\n\nconst client = createClient(contracts, 'https://api.example.com', {\n  headers: () => ({ authorization: `Bearer ${readToken()}` }), // static record or (async) function\n  fetch: myFetch, // injectable for tests; defaults to global fetch\n  pathParams: buildParamPath, // opt-in: only needed for {param} paths\n  queryParams: toSearchParams, // opt-in: only needed for calls that send query\n  fetchOptions: { credentials: 'include' }, // RequestInit extras (credentials, cache, redirect, …)\n  timeoutMs: 10_000, // default per-call timeout; composes with a per-call signal\n})\n\nconst reply = await client.getUser({ params: { id: 7 }, signal: AbortSignal.timeout(5000) })\nif (reply.status === 200) reply.body.name // typed from the schema — narrowing on status\nif (reply.status === 404) /* declared, typed, no body */;\n```\n\n- **`@amritk/api/client` is the browser-safe entry:** everything above —\n  `createClient`, `defineContract`, the opt-in serializers, the error\n  predicates, the `…Of` type helpers, and the client-side auth helpers\n  (`createCsrfHeader`, `createTokenRefresh`, `createRefreshFetch`) — with an\n  import graph that never\n  touches a server module or a `node:` built-in, guaranteed by a test.\n  Importing from the root `@amritk/api` works too (`sideEffects: false`\n  tree-shakes the server half out of the final bundle), but the root barrel\n  makes bundlers *resolve* the server adapters and print\n  `node:http`/`node:stream` externalization warnings along the way; the\n  subpath never triggers them.\n- **Replies are a discriminated union on `status`,** derived from the\n  `responses` map. JSON statuses carry a typed `body` (parsed eagerly);\n  statuses declared with a raw `contentType` carry only the untouched\n  `Response` — read the stream and headers yourself (the AI-chat shape):\n\n  ```ts\n  const chat = await client.chat({ body: { message: 'hi' }, headers: { 'x-api-key': key } })\n  if (chat.status === 200) for await (const chunk of chat.response.body) render(chunk)\n  ```\n\n- **Inputs are typed per slot:** declared `params`/`query`/`body`/`cookies`\n  are required and schema-typed, `headers` accepts the declared shape plus\n  ad-hoc extras, and a per-call `signal` cancels. Contracts with no request\n  slots call with no argument at all (`client.health()`). Every call also\n  accepts `fetchOptions` (per-call `RequestInit` extras, merged over the\n  client-level ones) and `timeoutMs` (overriding the client default; a\n  timeout and a caller `signal` compose via `AbortSignal.any`). Requests\n  send `accept: application/json` unless a header overrides it.\n- **Cookies and browsers:** the `cookies` slot serializes into the `cookie`\n  request header, which browsers forbid scripts from setting — it works from\n  Node/undici/workers only, and is opt-in for exactly that reason: register\n  `cookies: appendCookies` to use it; a browser bundle omits it and never\n  carries the code. Browser cookie auth uses server-set cookies plus\n  `fetchOptions: { credentials: 'include' }`.\n- **A declared status whose body fails to parse** (a proxy truncation, a\n  gateway HTML page under a JSON status) throws a recognizable error —\n  `isMalformedBodyError(error)` — carrying the consumed `Response` and the\n  parse error as `cause`, instead of a bare `SyntaxError`.\n- **Everything beyond plain JSON calls is an opt-in import:** JSON bodies and\n  the raw `text`/`bytes` bodies (sent verbatim) are built in; the rest is\n  registered explicitly so a JSON-only, static-path app bundles none of it.\n  Contracts with `bodyType: 'form'` / `'multipart'` (urlencoded pairs /\n  `FormData` with `File` values intact) need their serializer, `{param}` path\n  templates need `pathParams: buildParamPath` (segment-encoded; greedy\n  `{path+}` keeps its slashes), query strings need `queryParams:\n  toSearchParams` (array values repeat the key, `undefined` skipped), and the\n  Node-only `cookies` slot needs `cookies: appendCookies`:\n\n  ```ts\n  import {\n    appendCookies,\n    buildParamPath,\n    createClient,\n    formBodySerializer,\n    multipartBodySerializer,\n    toSearchParams,\n  } from '@amritk/api/client'\n\n  const client = createClient(contracts, url, {\n    serializers: [formBodySerializer, multipartBodySerializer], // only what you send\n    pathParams: buildParamPath, // only if any path has {params}\n    queryParams: toSearchParams, // only if any call sends query\n    cookies: appendCookies, // only from Node/undici/workers\n  })\n  ```\n\n  A call that needs an unregistered piece throws with the fix in the\n  message; JSON-only apps with static paths pass nothing and bundle none of\n  it. A custom `BodySerializer` (any `bodyType`, including `'json'` to\n  override the built-in encoder) is a `{ bodyType, serialize, contentType? }`\n  object.\n- **Undeclared statuses throw** (instead of poisoning the union): catch and\n  inspect with `isUnexpectedStatusError(error)` — the unread `Response` rides\n  on the error. Declare the statuses you want to handle in the contract.\n- **Name wire types from the contracts** — the `…Of` helpers extract every\n  schema-typed shape an app would otherwise re-declare by hand or generate:\n  `ResponseBodyOf` (one status's body), `SuccessBodyOf` / `ErrorBodyOf` (the\n  generated-SDK-style data and error unions, split 2xx vs 4xx/5xx),\n  `ResponseStatusOf` (the declared statuses, for exhaustive switches),\n  `RequestParamsOf` / `RequestQueryOf` / `RequestBodyOf` /\n  `RequestHeadersOf` / `RequestCookiesOf` (the request slots, `undefined`\n  when undeclared), and `ClientReplyOf` / `RouteReplyOf` (the client and\n  handler reply unions; `RouteReplyOf` is handler-side, so it comes from the\n  root `@amritk/api` rather than `@amritk/api/client`). Error payloads become named exports instead of\n  inline `as { ... }` casts at every use site:\n\n  ```ts\n  import type { ErrorBodyOf, RequestBodyOf, ResponseBodyOf } from '@amritk/api/client'\n\n  // The 402 body, exactly as the contract declares it — no codegen.\n  export type DemoLimitBody = ResponseBodyOf<typeof contracts.demoChat, 402>\n  // Every declared error payload of the operation, as one union.\n  export type DemoChatError = ErrorBodyOf<typeof contracts.demoChat>\n  // What a form model holds before calling the client.\n  export type DemoChatInput = RequestBodyOf<typeof contracts.demoChat>\n  ```\n\nFor consumers outside the monorepo, the generated OpenAPI document feeds\nwhichever SDK generator they already use; `createClient` is the lighter path\nfor monorepo-internal frontends, and needs no codegen at all.\n\n#### Browser bundle size: the contract strip\n\nAt runtime the client reads only a sliver of each contract — `method`,\n`path`, `request.bodyType`, whether a `body` schema exists, and each response\nstatus's `contentType` marker. The request/response schemas, `refine`,\n`summary`/`description`, and tags are server and OpenAPI freight, and they\nscale with route count. `@amritk/api/bundler` exports the transform that\nremoves them from `defineContract` call sites in browser builds — types are\ncompile-time, so nothing changes for the consumer, and dropped schema\nreferences become tree-shakeable:\n\n- `stripContractFields(source)` — source in, source out, unchanged when there\n  was nothing to rewrite.\n- `isScannableId(id)` — the module-id filter to put in front of it (TS/JS\n  extensions, tolerating Vite's `?query` suffixes).\n\nDeliberately not a plugin per bundler: every bundler exposes a per-module\ntext hook, the wiring against yours is a few lines, and those lines are\nyours to place — which build, which modules, which `exclude`. The subpath\nimports nothing, `node:*` included, so a config file in any runtime can load\nit.\n\n```ts\n// vite.config.ts — Rollup is the same, minus enforce/apply/ssr\nimport { isScannableId, stripContractFields } from '@amritk/api/bundler'\n\nconst stripContracts = {\n  name: 'strip-contracts',\n  enforce: 'pre', // see original sources, ahead of other transforms\n  apply: 'build', // dev-server modules stay untouched, for debuggability\n  transform(code: string, id: string, options?: { ssr?: boolean }) {\n    // SSR modules keep their freight — the server genuinely reads the schemas.\n    if (options?.ssr === true || !isScannableId(id) || !code.includes('defineContract')) return null\n    const stripped = stripContractFields(code)\n    return stripped === code ? null : { code: stripped, map: null }\n  },\n}\n\nexport default defineConfig({ plugins: [stripContracts] })\n```\n\n```ts\n// build.ts — Bun.build (esbuild is the same shape; read the file with\n// node:fs/promises' readFile). Add it to the browser build only.\nimport { stripContractFields } from '@amritk/api/bundler'\n\nconst stripContracts = {\n  name: 'strip-contracts',\n  setup(build: Bun.PluginBuilder) {\n    build.onLoad({ filter: /\\.[cm]?[jt]sx?$/ }, async ({ path }) => {\n      const source = await Bun.file(path).text()\n      if (!source.includes('defineContract')) return undefined\n      const stripped = stripContractFields(source)\n      return stripped === source ? undefined : { contents: stripped, loader: path.endsWith('x') ? 'tsx' : 'ts' }\n    })\n  },\n}\n\nawait Bun.build({ entrypoints: ['./src/client.ts'], target: 'browser', plugins: [stripContracts] })\n```\n\n```js\n// strip-contracts-loader.mjs — rspack and webpack; the package is ESM-only,\n// so the loader is too (both support ESM loaders).\nimport { stripContractFields } from '@amritk/api/bundler'\n\nexport default function stripContractsLoader(source) {\n  return source.includes('defineContract') ? stripContractFields(source) : source\n}\n\n// rspack.config.mjs — scope it to the contracts you want slimmed\n// module: { rules: [{ test: /\\.[cm]?[jt]sx?$/, include: /contracts/, use: ['./strip-contracts-loader.mjs'] }] }\n```\n\nThe strip is line-preserving — removed spans keep their newlines — so\ndownstream sourcemaps stay line-accurate, and returning the source unchanged\nlets the bundler keep the original code and map.\n\n##### Or strip once, at publish time\n\nWhen contracts live in their own package that both the server and the\nfrontend import, the strip can run in *that* package's build instead of in\nevery app downstream — no bundler wiring at all for consumers, whatever they\nbuild with. Emit two artifacts from one source, and split them with\n`exports`:\n\n```ts\n// scripts/build-client.ts — run after tsc has written dist/\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { transformSync } from 'esbuild'\nimport { stripContractFields } from '@amritk/api/bundler'\n\nfor (const file of await readdir('dist', { recursive: true })) {\n  if (!file.endsWith('.js')) continue\n  const source = stripContractFields(await readFile(join('dist', file), 'utf8'))\n  // Reprint to drop the JSDoc tsc copied into the JS — see below.\n  const { code } = transformSync(source, { loader: 'js', format: 'esm' })\n  const out = join('dist-client', file)\n  await mkdir(dirname(out), { recursive: true })\n  await writeFile(out, code)\n}\n```\n\n```jsonc\n// package.json — the server gets the schemas, the browser gets the slim copy\n\"exports\": {\n  \".\": { \"types\": \"./dist/index.d.ts\", \"default\": \"./dist/index.js\" },\n  \"./client\": { \"types\": \"./dist/index.d.ts\", \"default\": \"./dist-client/index.js\" }\n}\n```\n\nBoth entries point at the **same** `.d.ts`, which is the part worth\nunderstanding. Declarations are generated from the original source, so they\ncarry the full types *and* the JSDoc you wrote above each contract — hover,\nautocomplete, and `ResponseBodyOf<…>` are identical on both entries, and only\nthe shipped values differ. Editors and `tsc` never see the strip. That is the\nsame trade the bundler hook makes; it just happens once, in the package that\nowns the contracts.\n\nRunning over emitted JS rather than TypeScript sources is the more reliable\norder, too: `defineContract` survives compilation intact (it is an identity\nfunction, and tsc keeps the call), while the `as const` and `satisfies`\nsuffixes that make the scanner bail on a source file are already gone by\nthen.\n\nThat is also why the script reprints through esbuild. The strip rewrites\ncontract literals and never touches comments, and tsc copies every JSDoc\nblock into the `.js` it emits — so without that step the doc comment above\neach contract ships to the browser, where it is dead weight the docs in the\n`.d.ts` already cover. A consumer's production minifier would drop it, but\nthere is no reason to put it in the package. **Do not reach for tsc's\n`removeComments` instead:** it strips JSDoc from the declaration files too,\nwhich is precisely the hover help this layout exists to keep. Comments out of\nthe values, comments kept in the types.\n\nThe transform is deliberately conservative: call sites it cannot parse with\ncertainty (spreads, computed keys, explicit type arguments, aliased imports\nof `defineContract`) are left byte-for-byte untouched, and unknown contract\nfields are kept — the failure mode is a bigger bundle, never a broken one.\n\nPer-operation `security` is **not** stripped. `createClient` does not read it\neither, but an app plausibly does — attach a bearer token only where a scheme\nis declared, skip a call that will certainly 401, hide a control for a scope\nthe session lacks — and a requirement is tens of bytes against the hundreds a\nrequest schema costs. Everything else on the list is inert in a browser.\n\nThree caveats. First, the strip assumes the browser only calls contracts\nthrough `createClient`. If your app itself reads contract schemas at runtime\n— client-side form validation against `contract.request.body`, in-browser\nOpenAPI rendering — those modules must keep their freight: filter them out in\nthe hook, or leave the strip off. Second, only direct\n`defineContract({ ... })` identifier calls are rewritten; a renamed import\nor a wrapper function keeps its call sites intact (and its bytes). Third,\nthis is a size optimization and nothing more — it is not the way to keep a\n`node:*` built-in out of a browser bundle, because bundlers resolve modules\nbefore they eliminate them. Import contracts from `@amritk/api/client`\ninstead; that graph is guaranteed node-free.\n\nMeasured on a realistic widget consumer — three JSON-only contracts with\nstatic paths, bundled with `Bun.build` (`target: 'browser'`, minified;\nenforced by `src/bundler/strip-contract-fields.bundle.test.ts`, which bundles\nthrough the `Bun.build` wiring above):\n\n| Bundle                                | minified | gzip    |\n| ------------------------------------- | -------- | ------- |\n| 0.3.0 client (everything built in)    | 3.6 kB   | 1.7 kB  |\n| 0.4.0 client, no strip                | 3.7 kB   | 1.7 kB  |\n| 0.4.0 client + strip                  | 2.7 kB   | 1.4 kB  |\n| contract data alone, before → after   | 1.3 kB → 0.31 kB | 0.57 kB → 0.19 kB |\n\nThe contract-data row is the one that scales: the strip removes ~75% of\nevery contract's bytes (~0.3 kB minified per route in this fixture), so the\ngap widens with route count. The client core itself is a fixed cost, and the\nopt-in serializer/path split keeps it flat: form, multipart, and `{param}`\nhandling are no longer bundled unless the app registers them.\n\n### Serving it\n\n`createApi` returns an `Api`, not a server: `handle(ApiRequest) → ApiResponse`\nis the entire runtime. Two adapters bridge it onto the only two HTTP ABIs\nJavaScript has, and every framework below is one of the two — there is no\nper-framework plugin to install, and no framework-specific code in the package.\n\n| Adapter | Signature | Frameworks |\n|:---|:---|:---|\n| `toFetchHandler(api, options?)` | `(Request, env?, executionContext?) => Promise<Response>` | [Bun](#bun) · [Cloudflare Workers](#cloudflare-workers) · [Deno](#deno) · [Hono](#hono) · [Next.js](#nextjs-app-router) · [SvelteKit](#sveltekit) · [Nitro / Nuxt](#nitro--nuxt) · [Elysia](#elysia) |\n| `toNodeHandler(api, options?)` | `(IncomingMessage, ServerResponse, next?) => Promise<void>` | [`node:http`](#nodehttp) · [Express](#express) · [Fastify](#fastify) · [Koa](#koa) · [NestJS](#nestjs) |\n\nThree things every recipe shares:\n\n- **`mounts`, `onRequest`/`onResponse`, and the\n  [built-in security hooks](#built-in-security-hooks) are fetch-adapter\n  features.** `toNodeHandler` deliberately omits them: every Node framework\n  below already has a middleware chain for CORS, rate limits, and security\n  headers, and that chain runs before the handler.\n- **The second argument the host passes becomes `env`** in\n  `createApi({ context })` — Workers bindings on Workers, but the `Server` on\n  Bun and the route context under Next.js. When the context factory needs your\n  own config, pass it explicitly: `(request) => handler(request, config)`.\n- **Contract paths are the full request path.** There is no base-path option\n  on `createApi`, so a route declaring `/users/{id}` matches exactly that.\n  Under a host that serves the handler from `/api/…`, either declare\n  `/api/users/{id}` in the contract or mount somewhere that strips the prefix\n  first (Express's `app.use('/api', …)` does).\n\n#### Bun\n\n```ts\nimport { createApi, toFetchHandler } from '@amritk/api'\nimport { getUser } from './routes'\n\nconst api = createApi({ routes: [getUser] })\n\nBun.serve({ port: 3000, fetch: toFetchHandler(api) })\n```\n\n#### Cloudflare Workers\n\n```ts\nconst handler = toFetchHandler(api)\n\nexport default { fetch: handler } satisfies ExportedHandler<Env>\n```\n\nBindings arrive as `env`, and the `ExecutionContext` — `waitUntil`,\n`passThroughOnException` — as `executionContext`; both reach the\n`createApi({ context })` factory untouched. For production Workers, prefer\nthe [compiled engine](#production-the-compiled-engine) — same contracts, a\nfused handler with inlined guards.\n\n#### Deno\n\n```ts\nDeno.serve(toFetchHandler(api))\n```\n\n#### Hono\n\n```ts\nconst app = new Hono()\n\napp.get('/health', (c) => c.text('ok'))\napp.mount('/', toFetchHandler(api)) // register last: '/' matches everything\n\nexport default app\n```\n\nHono forwards its own `env` and `executionCtx` to the mounted handler, so\nWorkers bindings still reach `createApi({ context })`. Routes registered\n*before* the mount keep winning — that is how a Hono app adopts contracts one\nslice at a time.\n\n#### Next.js (App Router)\n\n```ts\n// app/api/[[...path]]/route.ts\nimport { createApi, toFetchHandler } from '@amritk/api'\nimport { routes } from '@/server/routes'\n\nconst handler = toFetchHandler(createApi({ routes }))\n\n// Next calls route handlers as (request, { params }); passing `env` explicitly\n// keeps Next's route context out of the context factory.\nconst route = (request: Request): Promise<Response> => handler(request, process.env)\n\nexport { route as GET, route as POST, route as PUT, route as PATCH, route as DELETE }\n```\n\nThe file's own path is part of the URL, so contracts declare `/api/...`. Use\n`@amritk/api/bundler` to strip contract schemas from anything the client\nbundle imports.\n\n#### SvelteKit\n\n```ts\n// src/hooks.server.ts\nimport { createApi, toFetchHandler } from '@amritk/api'\nimport type { Handle } from '@sveltejs/kit'\nimport { routes } from '$lib/server/routes'\n\nconst handler = toFetchHandler(createApi({ routes }))\n\nexport const handle: Handle = ({ event, resolve }) =>\n  event.url.pathname.startsWith('/api/') ? handler(event.request, event.platform) : resolve(event)\n```\n\nA `src/routes/api/[...path]/+server.ts` file works too — export\n`({ request, platform }) => handler(request, platform)` as `GET`/`POST`/… —\nbut the hook keeps every contract path in one place.\n\n#### Nitro / Nuxt\n\n```ts\n// server/routes/api/[...].ts (Nuxt) — routes/api/[...].ts (standalone Nitro)\nimport { fromWebHandler } from 'h3'\n\nexport default fromWebHandler(toFetchHandler(api))\n```\n\n`fromWebHandler` exists in both h3 v1 (Nitro 2 / Nuxt 3) and h3 v2 (Nitro 3 /\nNuxt 4). Use `server/routes/api/…` rather than `server/api/…`: the latter\nprefixes `/api` itself, which would double the prefix your contracts declare.\n\n#### Elysia\n\n```ts\nconst app = new Elysia()\n  .get('/health', () => 'ok')\n  .mount(toFetchHandler(api)) // WinterCG mount — raw Request in, Response out\n  .listen(3000)\n```\n\n#### `node:http`\n\n```ts\nimport { createServer } from 'node:http'\nimport { toNodeHandler } from '@amritk/api'\n\ncreateServer(toNodeHandler(api)).listen(3000)\n```\n\nWith no `next` callback the adapter is terminal: unmatched paths get the\npipeline's own 404. Wrap the returned listener to add cross-cutting behavior\n(the fetch adapter's hooks have no counterpart here).\n\n#### Express\n\n```ts\nconst app = express()\n\napp.use(toNodeHandler(api)) // unmatched paths fall through to the rest of the app\napp.get('/legacy/report', legacyReport)\n\napp.listen(3000)\n```\n\nCalled as middleware, the adapter checks `api.matches` first and calls `next()`\nwhen nothing matches, so mounting it early costs unmatched routes one map\nlookup. You do **not** need `express.json()` — the pipeline parses and\nvalidates declared bodies itself — but an app-wide parser is safe: the adapter\ndetects the already-drained stream and reads what the parser left on\n`req.body` instead of hanging.\n\nMounting under a prefix works too — `app.use('/api', toNodeHandler(api))` —\nbecause Express strips the mount path from `req.url` before the handler sees\nit, so contracts stay written as `/users/{id}`.\n\nExpress 5 changed wildcard syntax: a catch-all is `'/api/auth/*splat'`, not\n`'/api/auth/*'`, which now throws at registration.\n\n#### Fastify\n\nFastify routes before it runs hooks, so the adapter attaches as a global\n`onRequest` hook — the last point where the body stream is still untouched by\nFastify's content-type parser. `reply.hijack()` hands the socket over so\nFastify will not also try to answer:\n\n```ts\nconst nodeHandler = toNodeHandler(api)\n\napp.addHook('onRequest', async (request, reply) => {\n  const path = request.url.split('?')[0] ?? '/'\n  // Not ours — returning lets Fastify's router, hooks, and 404 handler take over.\n  if (!api.matches(request.method, path)) return\n  reply.hijack()\n  void nodeHandler(request.raw, reply.raw)\n})\n\napp.get('/health', async () => ({ ok: true }))\n```\n\nGlobal `onRequest` hooks run even when Fastify's own router has no match, which\nis what lets contracts serve paths Fastify never heard of. `void` is safe here:\nthe adapter never rejects — it answers a 500 while the status line is unsent,\nand destroys the socket once bytes are on the wire. Requests handled this way\nbypass Fastify's router, per-route hooks, and serializer by design; its\n`onRequest` hooks registered *before* this one still run, which is where\nFastify-side CORS and rate limits belong.\n\n#### Koa\n\nKoa has no router of its own, so the adapter is just middleware — but\n`ctx.respond = false` is required, or Koa overwrites the reply after the\nadapter has already written it:\n\n```ts\nconst nodeHandler = toNodeHandler(api)\n\napp.use(async (ctx, next) => {\n  if (!api.matches(ctx.method, ctx.path)) {\n    await next()\n    return\n  }\n  ctx.respond = false\n  await nodeHandler(ctx.req, ctx.res)\n})\n```\n\n#### NestJS\n\nOn the default Express platform the adapter is ordinary middleware:\n\n```ts\n// main.ts\nconst app = await NestFactory.create(AppModule)\napp.use(toNodeHandler(api))\nawait app.listen(3000)\n```\n\nOn `FastifyAdapter`, use the [Fastify recipe](#fastify) against\n`app.getHttpAdapter().getInstance()`.\n\n#### Anything else\n\nWriting an adapter is ~15 lines: construct one\n[`ApiRequest`](./src/types.ts) per incoming request and serialize the\n`ApiResponse` that `api.handle` resolves with. If the host already speaks\n`Request`/`Response`, `toFetchHandler` is that adapter;\n[`fetchToNodeHandler`](#production-the-compiled-engine) goes the other way,\nrunning a fetch handler (including a compiled module's `fetch` export) on\n`node:http`.\n\n### Options (`createApi`)\n\n| Option | Default | Description |\n|:---|:---|:---|\n| `routes` | — | The route contracts (from `defineRoute`). Duplicate `method + path` shapes throw at startup. |\n| `info` | placeholder | OpenAPI `info` block (`title`, `version`, `description`). |\n| `openApiPath` | `/openapi.json` | Where the document is served. `false` disables serving. |\n| `compile` | runtime-validators | Swap the validation engine — see below. |\n| `formats` | — | String `format`s to assert: `'all'`, or a list like `['uuid', 'email']`. Off by default — see [String formats](#string-formats). |\n| `context` | — | Per-request app context factory (database handles, sessions). See [App context](#app-context-drizzle-sessions-anything-per-request). |\n| `validateResponses` | `false` | Validate reply bodies (and declared reply headers) against the response contracts; mismatches become a 500. A development/test net. |\n| `onError` | bare 500 | Map a thrown handler error to a response. Receives `(error, request, { route, env, executionContext })` — everything error reporting needs. The default never leaks the error message. |\n| `errors` | built-in bodies | Reshape the pipeline's own cold-path responses (`notFound`, `invalidJson`, `invalidBody`, `unsupportedMediaType`, `payloadTooLarge`, `validationFailed`, `methodNotAllowed`) to match an existing wire format. |\n| `observe` | — | Called once per matched request with `{ route, request, status, durationMs, env, executionContext }` — the seam for per-route latency metrics and structured request logs. See [Observability](#observability-metrics-and-request-logs). |\n| `observeUnmatched` | — | The unmatched-request counterpart: called once per 404/405 with `route: undefined`, for request-logging parity with framework middleware. |\n| `servers` / `securitySchemes` / `security` / `tags` | — | Document-level OpenAPI settings: base URLs, named auth schemes (`components.securitySchemes`), the default security requirement, and tag objects (`name`/`description`/`externalDocs`). Routes add `security` / `deprecated` per operation. |\n\n### Validation semantics\n\n- Path and query parameters arrive as strings, so declared `number` /\n  `integer` / `boolean` / `array` properties are coerced first (from a plan\n  computed at startup — no per-request schema inspection). A value that does\n  not parse stays a string and fails validation with a proper type error.\n- Repeated query keys (`?tag=a&tag=b`) accumulate into arrays when the schema\n  declares an array; undeclared keys pass through as strings so\n  `additionalProperties` rules still apply.\n- Declaring `request.body` makes a body required. The default encoding is\n  JSON; `bodyType: 'form'`, `'multipart'`, `'text'`, and `'bytes'` switch it\n  (see below). A JSON body that fails to parse is a `400 { error:\n  'invalid_json' }`; a form/multipart body that fails to parse is a `400 {\n  error: 'invalid_body' }`.\n- A request whose `content-type` contradicts the declared body type answers\n  `415 { error: 'unsupported_media_type' }` before any read. A request with\n  *no* content-type gets the benefit of the doubt and fails on the parse\n  instead, so bare `curl` and hand-rolled clients keep working. JSON accepts\n  `application/json` and `+json` structured suffixes.\n- `request.headers` takes an object schema whose property names are header\n  names (lookup is case-insensitive; write them lowercase). Only declared\n  headers are read, values coerce like query parameters, and each property\n  becomes an `in: 'header'` OpenAPI parameter — so `x-api-key`-style auth\n  requirements document themselves.\n- `request.cookies` works the same way for the `cookie` header: only declared\n  names are read (tracking cookies never reach validation), values are\n  unquoted and percent-decoded per the usual middleware conventions, and\n  each property becomes an `in: 'cookie'` OpenAPI parameter.\n- `HEAD` is served automatically wherever `GET` is (RFC 9110): the GET\n  pipeline runs — validation, handler, response headers and all — and the\n  adapter discards the body (cancelling streams rather than leaking them).\n  Declaring an explicit `head` route overrides the fallback for its path.\n- A known path requested with the wrong method answers\n  `405 { error: 'method_not_allowed' }` with a sorted `allow` header\n  (advertising `HEAD` whenever `GET` is served, and `OPTIONS` always);\n  unknown paths stay 404.\n- `OPTIONS` on a known path answers `204` with the same `allow` header\n  automatically; declaring an explicit `options` route overrides it. CORS\n  preflights are answered earlier by the `createCors` gate when configured.\n- Validation failures answer `400` with `{ error: 'validation_failed', source,\n  errors }` where `errors` carries the same `{ message, path, keyword, params }`\n  shape as `@amritk/runtime-validators` — `keyword` and `params` being what let a\n  client render or translate a failure rather than only print it — and `source`\n  is `params`, `query`, `headers`,\n  `cookies`, or `body`. The `errors` option reshapes this (and the other built-in\n  bodies) when deployed clients already parse a different envelope.\n\n### String formats\n\n`format` is an **annotation** in JSON Schema, and both Ajv and\n`@amritk/runtime-validators` make asserting it opt-in. The api follows suit: by\ndefault a param declared `{ type: 'string', format: 'uuid' }` documents itself as\na UUID in the OpenAPI output and accepts any string at runtime.\n\nPass `formats` to assert them:\n\n```ts\n// Every built-in format: uuid, email, date-time, date, time, duration, uri,\n// uri-reference, uri-template, hostname, idn-hostname, ipv4, ipv6,\n// json-pointer, relative-json-pointer, regex, and the idn-/iri- variants.\nconst api = createApi({ routes, formats: 'all' })\n\n// Or only the ones you rely on, leaving the rest as documentation.\nconst api = createApi({ routes, formats: ['uuid', 'email'] })\n```\n\nA violation is an ordinary `400 { error: 'validation_failed' }` alongside every\nother constraint. Format checks are pragmatic regexes rather than RFC-perfect\nparsers — they reject obviously-bad input; treat them as a first gate, not as\nproof a value is routable or deliverable.\n\nPass the same value to `compileToModule({ formats })` so the compiled module and\nthe development server agree — a schema carrying `format` then leaves the\ninlinable subset and is checked by the interpreter, which owns the regexes.\n`formats` is ignored when you supply your own `compile`, since that replaces the\nengine it configures.\n\n### Branded IDs (nominal types for params)\n\nPath/query params arrive as plain `string` / `number`, so nothing stops you from\npassing a `userId` where an `orderId` is expected. Add an `x-mjst` **brand** to\nthe param schema and mjst intersects a unique nominal marker onto the inferred\ntype — the runtime still validates the plain base type, but the handler (and the\nderived typed client) see a distinct branded id, the same protection Drizzle's\n`.$type<UserId>()` gives a column:\n\n```ts\nconst getUser = defineRoute({\n  method: 'get',\n  path: '/users/{id}',\n  request: {\n    params: {\n      type: 'object',\n      properties: { id: { type: 'string', format: 'uuid', 'x-mjst': { brand: 'UserId' } } },\n      required: ['id'],\n    },\n  },\n  responses: { 200: { body: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } } },\n  handler: ({ params }) => {\n    params.id // (string & { readonly __brand: 'UserId' }) — not a plain string\n    return { status: 200, body: { id: params.id } }\n  },\n})\n```\n\n`params.id` is now a `UserId`, so `getOrder(params.id)` is a compile error unless\n`getOrder` takes a `UserId`. The brand is **type-level only** — it adds no runtime\ncheck beyond the base type, and the `format: 'uuid'` next to it is an annotation\nuntil you opt in with [`formats`](#string-formats). Keep the schema\nliteral (inline or `as const`) so the brand survives inference, and use the same\nbrand shape (`{ readonly __brand: 'UserId' }`) for your app-side id type — define\nit to match, rather than expecting mjst to reuse Drizzle's own brand symbol. See\n[the `x-mjst` extension](../adapters/README.md#nominal-brands) for the full\nreference.\n\n### Cross-field refinement\n\nPer-slot JSON Schema cannot see across fields. A route (or contract) may\ndeclare `refine`, which runs (sync or async — a returned promise is awaited)\n**after** every declared slot has validated — so its inputs are already typed\nand coerced — and **before** the context factory and handler. Returned issues reject the request through the\nstandard `validation_failed` envelope (and the `validationFailed` formatter),\nwith your own `path`/`message`; `undefined` or `[]` accepts it. A thrown\nrefine takes the `onError` path like any handler error:\n\n```ts\nconst chat = defineRoute({\n  method: 'post',\n  path: '/chat',\n  request: { body: chatBodySchema },\n  refine: ({ body }) => {\n    const total = body.messages.reduce((n, m) => n + m.content.length, 0)\n    return total <= 64_000\n      ? undefined\n      : [{ path: '/messages', message: `total message length ${total} exceeds 64k` }]\n  },\n  responses: { 200: { contentType: 'text/event-stream' } },\n  handler: /* ... */,\n})\n```\n\n### Streaming responses: documenting each item\n\nA raw `contentType` says what the stream *is*; OpenAPI 3.2's `itemSchema` says\nwhat each item in it looks like. Declare it on the response contract beside\n`contentType` and it lands in the document next to `schema`:\n\n```ts\nimport { sseItemSchema, sseStream } from '@amritk/api'\n\nconst tokens = defineRoute({\n  method: 'get',\n  path: '/chat/{id}/stream',\n  request: { params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },\n  responses: {\n    200: {\n      contentType: 'text/event-stream',\n      // One SSE event, not the whole stream.\n      itemSchema: sseItemSchema({ type: 'string' }, { event: 'token' }),\n    },\n  },\n  handler: ({ request }) => ({ status: 200, body: sseStream(stream(), { signal: request.signal }) }),\n})\n```\n\nThe sequential media types OpenAPI recognizes are `text/event-stream`,\n`application/jsonl`, `application/json-seq`, and `multipart/mixed`. For the\nJSON-lines family the item is your record, so pass the schema directly\n(`itemSchema: recordSchema`). For SSE the item is the **event envelope** —\n`{ event, id, data, retry }` — with your payload inside `data`, which is what\n`sseItemSchema` builds so you do not hand-write the wrapper at every route.\n\n`itemSchema` is documentation only, exactly like a `body` schema on a raw\nstatus: adapters pass the stream through untouched, so nothing here is\nvalidated at runtime. It does take part in `components.schemas` hoisting, so a\ntitled event schema shared across routes appears once and is `$ref`erenced.\n\n### Form and multipart bodies\n\n`bodyType` selects how the declared body schema arrives on the wire — the\nparser, the 415 check, and the OpenAPI requestBody content key all follow it:\n\n```ts\nconst signup = defineRoute({\n  method: 'post',\n  path: '/signup',\n  request: {\n    // application/x-www-form-urlencoded: fields coerce like query parameters\n    // (typed keys coerce from strings, array keys accumulate repeats).\n    body: {\n      type: 'object',\n      properties: { name: { type: 'string', minLength: 1 }, age: { type: 'integer', minimum: 18 } },\n      required: ['name', 'age'],\n    },\n    bodyType: 'form',\n  },\n  responses: { 201: {} },\n  handler: ({ body }) => /* body.age is a number */ ({ status: 201 }),\n})\n\nconst upload = defineRoute({\n  method: 'post',\n  path: '/upload',\n  request: {\n    // multipart/form-data: string parts coerce like form fields, file parts\n    // reach the handler as File objects. Declare file properties WITHOUT a\n    // `type` keyword ({} or { contentMediaType: 'image/png' }) — a File is\n    // not a string, so `type: 'string'` would reject it.\n    body: {\n      type: 'object',\n      properties: { title: { type: 'string' }, attachment: {} },\n      required: ['title', 'attachment'],\n    },\n    bodyType: 'multipart',\n  },\n  responses: { 200: {} },\n  handler: async ({ body }) => {\n    const file = body.attachment as File\n    await save(file.name, new Uint8Array(await file.arrayBuffer()))\n    return { status: 200 }\n  },\n})\n```\n\nMultipart parsing is delegated to the platform's `Response#formData` (undici\non Node, native on Workers/Bun/Deno) over the same shared buffered read as\neverything else — `maxBodyBytes` still caps uploads. Repeated file keys keep\nthe last file; repeated string keys accumulate when the schema declares an\narray.\n\n### Raw text and binary bodies\n\n`bodyType: 'text'` and `'bytes'` skip parsing entirely: the body is validated\nverbatim against the schema and handed to the handler as a `string` (decoded)\nor a `Uint8Array` — a `text/csv` upload or a binary blob that still rides the\ntyped contract and the typed client, no hand-rolled `fetch` required. The 415\ncheck is lenient (any `text/*` for text, any media type for bytes), so the\nschema is the real gate.\n\n```ts\nconst importCsv = defineContract({\n  method: 'post',\n  path: '/import',\n  // { type: 'string' } for text; {} accepts any bytes.\n  request: { body: { type: 'string', minLength: 1 }, bodyType: 'text' },\n  responses: { 200: { body: { type: 'object', properties: { rows: { type: 'integer' } }, required: ['rows'] } } },\n})\n\n// server: the handler receives the raw string\nimplementRoute(importCsv, ({ body }) => ({ status: 200, body: { rows: body.split('\\n').length } }))\n\n// client: the body goes on the wire unchanged. text/bytes are built in — no\n// serializer to register. A default content type is stamped only when nothing\n// else set one, so override it per call for a specific media type.\nawait client.importCsv({ body: csvText, headers: { 'content-type': 'text/csv' } })\n```\n\nUnlike `form` / `multipart`, these two need no serializer registered in\n`createClient`: the client sends `text` and `bytes` bodies as they are.\n\n### Streaming and raw responses\n\nDeclare a status with `contentType` and its body becomes a raw payload — a\n`ReadableStream<Uint8Array>`, `Uint8Array`, or string that every adapter\nsends untouched. This is the AI-token-stream / SSE / CSV-download shape; the\nrequest side stays validated and documented, only the reply is raw:\n\n```ts\nconst chat = defineRoute({\n  method: 'post',\n  path: '/chat',\n  request: { body: chatBodySchema },\n  responses: { 200: { contentType: 'text/plain; charset=utf-8' } },\n  handler: ({ body, request }) => ({\n    status: 200,\n    // request.signal aborts when the client disconnects — stop generating.\n    body: streamTokens(body.messages, request.signal),\n  }),\n})\n```\n\nBoth adapters apply backpressure: the fetch adapter hands the stream to the\nplatform `Response`, and the Node adapter awaits `drain` whenever a write\noverruns the socket buffer, so a fast producer never buffers unbounded\nmemory against a slow client.\n\n### Returning a raw `Response` (escape hatch)\n\nA `contentType` status keeps the reply typed and documented while letting the\nbody be raw. When you instead need full control of the *entire* response —\nstatus, headers, and body all outside the contract — a handler may return\n`raw(response)`. The adapters send the wrapped response verbatim (the fetch\nadapter returns it as-is, the Node adapter streams it out), still running the\n`onResponse` decorators, and strip its body for HEAD like any other reply:\n\n```ts\nimport { raw } from '@amritk/api'\n\nconst proxy = defineRoute({\n  method: 'get',\n  path: '/legacy',\n  responses: { 200: { body: legacySchema } },\n  // Reuse an existing Response-building helper (or an upstream fetch) unchanged.\n  handler: async ({ request }) => raw(await fetch((request.raw as Request).url, { redirect: 'manual' })),\n})\n```\n\nThis is a deliberate escape hatch: a `raw` reply skips response validation\nentirely (there is no framework-level body to check), so the status it carries\nneed not appear in `responses`. Reach for it when porting handlers that already\nbuild `Response` objects, or when proxying an upstream response; prefer a typed\n`{ status, body }` reply — or a `contentType` status for raw bodies —\neverywhere else, so the contract stays the source of truth.\n\nThe `raw()` wrapper (`{ raw: Response }`) is not just ergonomics. A bare\n`Response` in the handler's return union carries `status: number`, which matches\nevery declared status and so forces TypeScript to check the reply against\n`Response` too — making an ordinary reply whose own status is a union of\ndeclared statuses fail to compile, with a misleading complaint about the status:\n\n```ts\n// `embed.status` is `502 | 503`, and the contract declares both.\nif (!embed.ok) return { status: embed.status, body: { error: embed.error } }\n```\n\n`raw` carries no `status`, so replies like that infer normally. Returning a bare\n`Response` is no longer accepted as of 0.10.0 — wrap it in `raw()`.\n\n### Raw request bodies and size limits\n\nThe pipeline only consumes the body stream when a `body` schema is declared,\nand all reads share one buffered copy — so `request.readText()` /\n`readBytes()` can be called repeatedly, in any combination, and even\nalongside a declared body schema (parsed access *and* the exact signed bytes\nin the same handler). A route that only needs the raw bytes — webhook\nsignature verification, uploads — simply declares no body schema:\n\n```ts\nconst stripeWebhook = defineRoute({\n  method: 'post',\n  path: '/billing/webhook',\n  request: {\n    headers: { type: 'object', properties: { 'stripe-signature': { type: 'string' } }, required: ['stripe-signature'] },\n  },\n  responses: { 200: {}, 400: {} },\n  handler: async ({ headers, request }) => {\n    const payload = await request.readText() // exact signed bytes, never re-serialized\n    const event = await stripe.webhooks.constructEventAsync(payload, headers['stripe-signature'], secret)\n    // ...\n    return { status: 200 }\n  },\n})\n```\n\n`toFetchHandler(api, { maxBodyBytes: 1_000_000 })` (also on `toNodeHandler`\nand `compileToModule`) rejects larger bodies with a 413 — checked against\n`content-length` up front, enforced on the running byte count as the body\nstreams in, for pipeline and handler-initiated reads alike. **The default is\n1 MiB** — unbounded reads are opt-in via `maxBodyBytes: Infinity`, so an\nunconfigured deployment is not a memory-exhaustion vector.\n\nWhen the API is mounted on another server that reads the body first, that\nserver's own limit can trip before this one. Those foreign body-limit errors\nare recognized too, so they answer 413 rather than a generic 500: Fastify's\n`FST_ERR_CTP_BODY_TOO_LARGE` (its `bodyLimit`), Express's\n`body-parser`/`raw-body` `entity.too.large`, and any thrown HTTP error whose\n`statusCode`/`status` is 413.\n\n### The platform request: `request.raw`\n\n`ApiRequest` is framework-neutral on purpose, but platforms attach real data\nto their native request objects — Cloudflare's `request.cf` carries geo\ncoordinates, ASN, TLS metadata. Each adapter exposes its native request as\n`request.raw`: the Web `Request` on the fetch adapter and compiled engine,\nthe `IncomingMessage` on the Node adapter. It is typed `unknown` because\nreading it is platform-specific **by design** — the cast at the use site is\nthe honest record of that coupling:\n\n```ts\nconst nearby = defineRoute({\n  method: 'get',\n  path: '/nearby',\n  responses: { 200: { body: resultsSchema } },\n  handler: ({ request }) => {\n    const cf = (request.raw as Request & { cf?: IncomingRequestCfProperties }).cf\n    return { status: 200, body: search(cf?.latitude, cf?.longitude) }\n  },\n})\n```\n\nThe context factory sees the same request, so platform data can flow into the\napp context once instead of per handler. Portable code should keep `raw`\nreads behind a seam (a context field) so only one module knows the platform.\n\n### Multiple `set-cookie` headers\n\nReply headers accept `string | string[]` per name. An array is sent as that\nmany separate header lines — the only correct encoding for repeated\n`set-cookie`, which must never be comma-folded (RFC 6265). This is what\nsession + CSRF (Better Auth) or session + Stripe-state flows need:\n\n```ts\nconst login = defineRoute({\n  method: 'post',\n  path: '/login',\n  request: { body: credentialsSchema },\n  responses: { 200: { body: profileSchema } },\n  handler: async ({ body }) => ({\n    status: 200,\n    headers: {\n      'set-cookie': [\n        `session=${await createSession(body)}; Path=/; HttpOnly; Secure`,\n        `csrf=${issueCsrf()}; Path=/; Secure`,\n      ],\n    },\n    body: profile,\n  }),\n})\n```\n\nBoth engines serialize arrays identically (the differential corpus covers\nit), and the Node adapter validates each element before `writeHead`. With\n`validateResponses` on, a declared response-header schema sees the value as\ngiven — a string or the array — so declare `anyOf` if you validate a header\nthat can repeat.\n\n### Hooks: CORS, rate limits, security headers\n\nHooks, `mounts`, and `createCors` are features of the **fetch adapter** —\n`toNodeHandler` deliberately omits them, because every Node framework it\nplugs into already has a middleware chain for CORS, rate limits, and\nsecurity headers (Express/Connect middleware runs before the handler; plain\n`node:http` users can wrap the returned listener).\n\n`toFetchHandler` takes two hook chains over the raw `Request`/`Response` —\ndeliberately not a middleware onion. `onRequest` gates run in order before\nmounts and routing, and the first returned `Response` short-circuits;\n`onResponse` decorators run on **every** outgoing response, including 404s,\ngate replies, and mounted routers, which is what security headers and CORS\nactually require:\n\n```ts\nimport { createCors, toFetchHandler } from '@amritk/api'\n\nconst cors = createCors({ origin: (o) => o, credentials: true, exposeHeaders: ['x-demo-used'] })\n// createCors throws at setup on origin: '*' + credentials: true — a\n// combination every browser rejects.\n\nconst handler = toFetchHandler(api, {\n  onRequest: [\n    cors.onRequest, // answers preflights\n    async (request, env) =>\n      (await allowed(request, env)) ? undefined : new Response('{\"error\":\"rate_limited\"}', { status: 429 }),\n  ],\n  onResponse: [\n    cors.onResponse,\n    (response) => {\n      response.headers.set('x-frame-options', 'DENY')\n    },\n  ],\n})\n// Compiled: compileToModule({ ..., onRequestExports: ['gate'], onResponseExports: ['stamp'] })\n```\n\n### Built-in security hooks\n\nRather than hand-roll the gates above, the package ships the common security\nmiddleware as hook factories — the `helmet` / `secure-headers`, `cors`,\n`rate-limit`, and CSRF features every framework in the ecosystem provides,\nexpressed over the same `onRequest`/`onResponse`/`locals` seams so they work\nidentically under the runtime and the compiled engine.\n\n```ts\nimport {\n  createCors,\n  createCsrf,\n  createRateLimit,\n  createSecurityHeaders,\n  toFetchHandler,\n} from '@amritk/api'\n\nconst cors = createCors({ origin: (o) => o, credentials: true })\nconst csrf = createCsrf()\nconst limit = createRateLimit({ limit: 100, windowMs: 60_000 })\n\nconst handler = toFetchHandler(api, {\n  onRequest: [cors.onRequest, limit.onRequest, csrf.onRequest],\n  onResponse: [cors.onResponse, limit.onResponse, csrf.onResponse, createSecurityHeaders()],\n})\n```\n\n**`createSecurityHeaders(options?)`** — an `onResponse` decorator that stamps\nthe browser-hardening headers (`x-content-type-options: nosniff`,\n`x-frame-options: SAMEORIGIN`, `referrer-policy: no-referrer`,\n`cross-origin-opener-policy` / `cross-origin-resource-policy`,\n`origin-agent-cluster`, …) only when the handler didn't already set them.\n**HSTS and CSP default off** on purpose: `strict-transport-security` on a bare\nIP or a plain-HTTP dev origin locks browsers out, and no single CSP fits every\napp — opt into both explicitly (`strictTransportSecurity: true`,\n`contentSecurityPolicy: \"…\"`) for a production HTTPS deployment. Every field\nexcept `contentSecurityPolicy` (a string only) takes `false` to omit or a\nstring to override.\n\n**`createCors(options)`** — preflight answerer (`onRequest`) plus allow/expose\nstamper (`onResponse`), applied to *every* response including 404s and gate\nshort-circuits, since a browser drops any reply without the allow-origin\nheader. It **throws at setup** on the spec-forbidden `origin: '*'` +\n`credentials: true` pair. A function origin (`(o) => o`) is trusted as\nwritten — reflecting *every* origin with `credentials: true` turns any site\ninto a trusted caller, so validate the origin inside the function rather than\nechoing it blindly.\n\n**`createRateLimit(options)`** — counts each request against a key and\nshort-circuits over-limit ones with a `429` carrying `Retry-After` and the\n`RateLimit-*` headers; under the limit it stamps those headers via `locals`.\nThe default in-process `memoryRateLimitStore()` is single-instance and\nmemory-bounded; pass a shared `store` (Redis, a Durable Object) for a fleet.\n\n> **Keying is a security decision.** The default key is the client IP read\n> from `cf-connecting-ip` / `x-real-ip` / the first `x-forwarded-for` hop —\n> **all client-supplied and spoofable**. An attacker rotating the header gets\n> a fresh bucket per request, defeating the limit. Rely on the default only\n> when a trusted proxy *overwrites* these headers and the origin isn't\n> reachable around it. For a security throttle (login / brute-force), pass a\n> `key` that reads a proxy-verified IP (the rightmost untrusted\n> `x-forwarded-for` hop for your topology) or an authenticated user id from\n> `locals`.\n\n**`createCsrf(options?)`** — stateless double-submit-cookie CSRF (the defense\nRails, Laravel, and Hono ship). The gate rejects an unsafe-method request\nwhose `x-csrf-token` header doesn't match its `csrf_token` cookie with a `403`\n(empty/missing tokens are always rejected — a blank pair never satisfies the\ncheck); the decorator seeds the cookie on any response that lacks one. The\ncookie defaults to `Path=/; SameSite=Lax; Secure` and is intentionally **not**\n`HttpOnly` — the pattern needs page scripts to read and echo it. Drop `Secure`\nvia `cookieAttributes` only for a plain-HTTP dev origin. Use `exempt` to skip\nbearer-token API paths, where CSRF doesn't apply — **`exemptBearer`** is that\npredicate written the safe way (see [native apps](#native-apps-magic-link-without-a-browsers-cookie-jar)\nfor why keying on `authorization` is sound and keying on a missing `Origin` is a\nbypass). On the client, pair it with\n**`createCsrfHeader()`** — a `headers` provider for `createClient` that reads\nthe `csrf_token` cookie and echoes it in `x-csrf-token`:\n\n```ts\nimport { createClient, createCsrfHeader } from '@amritk/api/client'\n\nconst client = createClient(contracts, 'https://api.example.com', {\n  fetchOptions: { credentials: 'include' },\n  headers: createCsrfHeader(),\n})\n```\n\n### Signed cookies\n\n**`signCookie` / `unsignCookie` / `createSignedCookies`** sign a value with\nHMAC-SHA256 over the Web Crypto API (so the same code runs on Workers, Bun,\nDeno, and Node ≥ 20). A signed value is `<value>.<base64url-hmac>`; tampering\nwith either half fails verification, which runs through the constant-time\n`crypto.subtle.verify`. This is **integrity, not secrecy** — the value stays\nreadable, so sign a session id and keep the session server-side; never put a\nsecret in it.\n\n```ts\nimport { createSignedCookies } from '@amritk/api'\n\nconst cookies = createSignedCookies(env.COOKIE_SECRET)\nconst setCookie = `sid=${await cookies.sign(sessionId)}; HttpOnly; Secure; SameSite=Lax`\nconst sessionId = await cookies.unsign(parsedCookie) // undefined if tampered\n// Rotate by unsigning against the current secret first, then older ones.\n```\n\n### Framework-parity helpers\n\nThe gates and decorators above are the security half of what a batteries-included\nframework ships. The rest is here too, each one composing through an existing\nseam (`mounts`, `onRequest`/`onResponse`, `locals`, the raw reply) rather than\nchanging the request pipeline — s","readmeFilename":"README.md"}