{"_id":"@1gr14/route0","_rev":"9-e632cbf4e55d58a5608d5dc0bbc00e8b","name":"@1gr14/route0","dist-tags":{"latest":"0.4.1"},"versions":{"0.0.0":{"name":"@1gr14/route0","version":"0.0.0","license":"MIT","_id":"@1gr14/route0@0.0.0","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"dist":{"shasum":"98b4d1d29a691bfbcad5da49dbfcfd36d5cbe52e","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.0.0.tgz","fileCount":1,"integrity":"sha512-cDKAPRnTHbd6FKdCO4KsevMeM/8nMuRS7JrVVqFxKsq/AHlHmPPQgpk4GbAIkjmqp7KYG459lTYP3l4c7KTppw==","signatures":[{"sig":"MEUCIQDo+t86ML3R9/jjMMPQ/uBwmFS8lal+kbrOCAngZ6vFHAIgU0TI4wJkGanXtc3J/msPoIfEWN/TdSnpF96z0m1ANhg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":137},"_npmUser":{"name":"iserdmi","email":"iserdmi@gmail.com"},"_npmVersion":"10.9.4","description":"Placeholder — real release follows via CI.","directories":{},"_nodeVersion":"22.21.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/route0_0.0.0_1780908008947_0.9895661404147764","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@1gr14/route0","version":"0.1.0","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.1.0","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"74d21d7bfc78e3a54f89895ca01b2b40432fafb8","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.1.0.tgz","fileCount":5,"integrity":"sha512-ldYdq/QIRG5iKbfoIX1yDWLiWIIDYuqUwzsbyjbadvQvJE15KEh+KIrpmwVyr2/b2HEZGRLi928FSIL0DdKaIg==","signatures":[{"sig":"MEQCICL0PKijZmbHEf1c6NbRaH6qn8zKyOkDZDSexMeWmXuFAiBs24glsBGP7vSapDWIOFbPSCc+Nnka0qcoZMQJDer7nA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71442},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"04836f032a9db22eb5378524806654de0ca8dc07","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"bun run types:dev","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","test:us":"bun test ./src --update-snapshots","typesgo":"tsgo --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","types:dev":"tsc --noEmit","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"tsc --noEmit --project tsconfig.build.json","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.7.0","description":"A strongly-typed URL router for TypeScript — define routes once, build paths and parse URLs with full type inference, search params, and Standard Schema validation.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","cross-env":"^10.1.0","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^5.9.3","@1gr14/flat":"^0.1.0","@types/node":"^20.0.0","lint-staged":"^17.0.7","@commitlint/cli":"^21.0.2","semantic-release":"^25.0.3","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^13.1.5","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","@semantic-release/github":"^12.0.8","@typescript/native-preview":"^7.0.0-dev.20260602.1","@semantic-release/changelog":"^6.0.3","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.1.0_1780908753437_0.21648803274866402","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@1gr14/route0","version":"0.1.1","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.1.1","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"c812b9a66344ee7966d82cbaf2b663cfebe032f5","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.1.1.tgz","fileCount":5,"integrity":"sha512-WjvW1/r1+jYHWWgsVGSYvfqbVH1cWHy7/QImlkbW2uqeLzXY5i3iFf+kQH9edJu4ruW3OQTp1+MU1ZkFZjumhw==","signatures":[{"sig":"MEUCICmTG6dN6ZD/FWDJ+5ijj1V1mYeQFli4wtvm9+yzkpctAiEA4YyD+AQaRRIkB0Wma3lmbr7dMfLFXYjgcGwP4gnFLMY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71395},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"71e40e5e7ea06f142b2832b3719946de40355cea","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"bun run types:dev","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","typesgo":"tsgo --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","types:dev":"tsc --noEmit","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.17.0","description":"A strongly-typed URL router for TypeScript — define routes once, build paths and parse URLs with full type inference, search params, and Standard Schema validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","cross-env":"^10.1.0","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^5.9.3","@1gr14/flat":"^0.1.0","@types/node":"^20.0.0","lint-staged":"^17.0.7","@commitlint/cli":"^21.0.2","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","@typescript/native-preview":"^7.0.0-dev.20260602.1","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.1.1_1781520882597_0.753306666164484","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@1gr14/route0","version":"0.1.2","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.1.2","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"d78cf253d4c3b225d4e269e840aea06323d2a2a4","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.1.2.tgz","fileCount":5,"integrity":"sha512-YbbxkbSuNY82ITyaU4bhkqsTlJfMsiq7+98w/XMOYVNIpUdk0jQMEYxaLxytUXJI4VFCvwIho790UFqF36FZ/g==","signatures":[{"sig":"MEQCIGz/7u9m0ZbQQ7M6VPAecD1IOpft3Rq7mvCVkz5zPTngAiBLlwPiUMWcxl6doHRGFLLdhAGj6CCA7kObHLfaek1b7A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":76413},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"b6f89f30fce7776cf5bfd2f02e6e59b598ac6cb9","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.17.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.0.7","typescript-7":"npm:typescript@7.0.1-rc","@commitlint/cli":"^21.0.2","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.1.2_1782131487557_0.11047475686194308","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@1gr14/route0","version":"0.1.3","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.1.3","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"1104799cb08a6566c0d074793d8e44e31eba906e","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.1.3.tgz","fileCount":5,"integrity":"sha512-/eMOzgH8B8eWic7JLUux/74Jh6uRoVDyTQR2LJgNwCUKaUnK1zQQ5elSZI1dFd69S9H5kq5o57/yvAU1e1iRZA==","signatures":[{"sig":"MEQCIHq8WPNL5t0cgAIMxiWerFPS8wEhaRMptXwAal/Ri/SzAiBO1C6Zr18HYxBT45Yu83mXP/BuKRfDJSDM5SDumkmiUg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":76774},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"b5fe636dbf8a7d1f0bf6e10e572a682560ce19c1","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.18.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.0.7","typescript-7":"npm:typescript@^7.0.2","@commitlint/cli":"^21.0.2","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.1.3_1783694064459_0.234636812033296","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@1gr14/route0","version":"0.2.0","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.2.0","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"ff328c07bc247e92490b5a3c6a6a6d5870799355","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.2.0.tgz","fileCount":5,"integrity":"sha512-W/s8arSjgUeXIXcNOJF6K3oj7P78uwydh5N6EiwTz1GcI9yds3XUu0IdVg7z3Lrm392D67l5dxVn1EI2WMIPqg==","signatures":[{"sig":"MEQCIGkAynuHRLr58dAbgBZFCYo9G4WPxzvLMRq3Hh6zQvUTAiBvaFgyOpuVv5xrjFvGXVTW8Rb2yS7JblfNjLHnsR2WLw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":99666},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"cd390cb80c29889ea56f9a275e7386b44285cdb3","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.18.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.0.7","typescript-7":"npm:typescript@^7.0.2","@commitlint/cli":"^21.0.2","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.2.0_1784555137228_0.49477202165334666","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@1gr14/route0","version":"0.3.0","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.3.0","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"17419547800b805a273afaf3da2c5d86360ba9a3","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.3.0.tgz","fileCount":5,"integrity":"sha512-WtXMAuK2hutfC95+u/1jViSkzlypTZPGYY3YBP38tar8SIgNWE9nc2+60R+B9zfyFH2ZQtyasb4OT4qsApIKFQ==","signatures":[{"sig":"MEUCIQCE4ak1QN0nUGakJ/n2ufPNDrabPuu4epaeWHWITZr0bwIgXosUQK1aSVN9OJoVCOdErKM0QUtEF2KbKjjcoo3WZ3w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":98742},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"a28b635c836b848aed640219cabb3d8486f11c21","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.18.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.4.1","rimraf":"^6.1.3","tsdown":"^0.22.1","publint":"^0.3.21","prettier":"^3.8.3","tsc-watch":"^7.2.0","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.0.7","typescript-7":"npm:typescript@^7.0.2","@commitlint/cli":"^21.0.2","typescript-eslint":"^8.60.1","@arethetypeswrong/cli":"^0.18.3","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.0.2"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.3.0_1784568723048_0.3560316319961121","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@1gr14/route0","version":"0.4.0","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","_id":"@1gr14/route0@0.4.0","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"homepage":"https://github.com/1gr14/route0#readme","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"4c20da37e520bb55ccce0f194d8dd12c89c56e35","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.4.0.tgz","fileCount":5,"integrity":"sha512-s+voQ/O5cj8FL7C5yGuQaZoKMEKDWvzGpK66GiX819lJBF7AwDHnNGifQAi+/ybFRfLlxL9wRYTjsDtyjsEvBw==","signatures":[{"sig":"MEUCIHoQv7oBo72SBlElLG2JP5ukBLusnu1BmN+KFUyFa2ZqAiEAz80UUp+myW/bpZca8SEYOWOnmTtuECHlW4BlGIh+3zU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":192683},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"f2a6db6a85a39980535ca176cfef4997962da3da","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.19.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.8.0","rimraf":"^6.1.3","tsdown":"^0.22.14","publint":"^0.3.22","prettier":"^3.9.6","tsc-watch":"^7.2.1","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.3.0","typescript-7":"npm:typescript@^7.0.2","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","@arethetypeswrong/cli":"^0.18.5","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.2.0"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/route0_0.4.0_1787775329748_0.8959401949201284","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"_id":"@1gr14/route0@0.4.1","bugs":{"url":"https://github.com/1gr14/route0/issues"},"dist":{"shasum":"b61e29f638f721907335c2f3e863d79c2bb70705","tarball":"https://registry.npmjs.org/@1gr14/route0/-/route0-0.4.1.tgz","fileCount":5,"integrity":"sha512-BZ5gPsaR+0P75/JEvi842u6q4zSnPf7TQP06zcql4J9AQTu6fRQ8KlofF9oBti9m6thBtolxs9B55t4n8jKF2g==","signatures":[{"sig":"MEQCIDu5SvmFeLxyRP//P4u3elNjnZmMf0jkNnTWtTbT3W2TAiB4nURsTGswndvSMjbxByMvUS3Qw5KI1boC65HTcTtdNw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAteZS7JIKOKQmpYYHLe9GbsBQqjnm3+ceSMExTKb247AiABn64VEtaiKVlWw4EonzFG0y8L8O/7J8GccbBeKBtXbA=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@1gr14%2froute0@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":199170},"name":"@1gr14/route0","type":"module","types":"./dist/index.d.ts","author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"module":"./dist/index.js","engines":{"bun":">=1.0.0","node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":"https://1gr14.dev/support","gitHead":"366bc76a8263d2c2679424acd83091dae0a1a879","license":"MIT","scripts":{"dev":"tsc-watch --preserveWatchOutput --project tsconfig.build.json","lint":"bun run lint:fix .","test":"bun test ./src","build":"tsdown","clean":"rimraf dist && rimraf dist-npm && rimraf node_modules/.cache","smoke":"node scripts/smoke.mjs","types":"node node_modules/typescript-7/bin/tsc --noEmit","format":"bun run format:fix .","dev:npm":"tsc-watch --preserveWatchOutput --project tsconfig.build.json --onSuccess 'bun run pack:dist'","prepare":"husky || true","release":"bun run scripts/release.ts","test:us":"bun test ./src --update-snapshots","types:6":"node node_modules/typescript/bin/tsc --noEmit","lint:fix":"bun run lint:base --fix","pack:dry":"npm pack --dry-run","lint:base":"eslint --cache --cache-location node_modules/.cache/eslint/.eslintcache","pack:dist":"mkdir -p dist-npm && npm pack --pack-destination dist-npm --silent && cd dist-npm && tar -xzf *.tgz && cp -r package/* . && rm -rf package && rm *.tgz","format:fix":"bun run format:base --write","test:watch":"bun test --watch","format:base":"prettier --cache --cache-location node_modules/.cache/prettier/.prettiercache --ignore-path .gitignore --ignore-path .prettierignore","types:build":"node node_modules/typescript/bin/tsc --noEmit --project tsconfig.build.json","check:channel":"bun run scripts/check-channel.ts","check:package":"publint && attw --pack . --profile esm-only","test:coverage":"bun test ./src --coverage","pack:dist:hard":"rimraf dist-npm && bun run pack:dist","publish:packages":"bun run scripts/publish.ts"},"version":"0.4.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:42aec008-2ce6-49cb-96cc-86b7bc752780"}},"homepage":"https://github.com/1gr14/route0#readme","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"_npmVersion":"11.20.0","description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","directories":{},"maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{},"publishConfig":{"access":"public","provenance":true},"typesVersions":{"*":{"":["./dist/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^10.8.0","rimraf":"^6.1.3","tsdown":"^0.22.14","publint":"^0.3.22","prettier":"^3.9.6","tsc-watch":"^7.2.1","@types/bun":"^1.3.14","typescript":"^6.0.0","@1gr14/flat":"^0.1.4","@types/node":"^20.0.0","lint-staged":"^17.3.0","typescript-7":"npm:typescript@^7.0.2","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","@arethetypeswrong/cli":"^0.18.5","@standard-schema/spec":"^1.1.0","prettier-plugin-jsdoc":"^1.8.1","eslint-config-prettier":"^10.1.8","eslint-plugin-no-only-tests":"^3.4.0","@commitlint/config-conventional":"^21.2.0"},"peerDependencies":{"@1gr14/flat":"^0.1.0","@standard-schema/spec":"^1.0.0"},"peerDependenciesMeta":{"@standard-schema/spec":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/route0_0.4.1_1790158331797_0.7038362118165566"}}},"time":{"created":"2026-06-08T08:40:08.770Z","modified":"2026-09-23T10:12:12.286Z","0.0.0":"2026-06-08T08:40:09.084Z","0.1.0":"2026-06-08T08:52:33.565Z","0.1.1":"2026-06-15T10:54:42.734Z","0.1.2":"2026-06-22T12:31:27.741Z","0.1.3":"2026-07-10T14:34:24.591Z","0.2.0":"2026-07-20T13:45:37.399Z","0.3.0":"2026-07-20T17:32:03.182Z","0.4.0":"2026-08-26T20:15:29.913Z","0.4.1":"2026-09-23T10:12:11.887Z"},"bugs":{"url":"https://github.com/1gr14/route0/issues"},"author":{"url":"https://1gr14.dev","name":"Sergei Dmitriev"},"license":"MIT","homepage":"https://github.com/1gr14/route0#readme","keywords":["router","routing","route","url","path","typescript","type-safe","query-string","search-params","standard-schema"],"repository":{"url":"git+https://github.com/1gr14/route0.git","type":"git"},"description":"Type-safe URL paths for TypeScript — define a pattern once, then build paths and parse URLs with full type inference, search params, and Standard Schema validation. Not a router; the typed path toolkit you build one on.","maintainers":[{"name":"iserdmi","email":"iserdmi@gmail.com"}],"readme":"# @1gr14/route0\n\n> Type-safe URL paths for TypeScript. Write a pattern like `/users/:id` once and\n> get a fully-typed path builder and URL parser out of it — params inferred from\n> the string. Not a router: the typed path toolkit you build your own router on,\n> or wire into the one you already use.\n\n[![CI](https://github.com/1gr14/route0/actions/workflows/ci.yml/badge.svg)](https://github.com/1gr14/route0/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@1gr14/route0.svg)](https://www.npmjs.com/package/@1gr14/route0)\n[![coverage](https://codecov.io/gh/1gr14/route0/branch/main/graph/badge.svg)](https://codecov.io/gh/1gr14/route0)\n[![gzip](https://deno.bundlejs.com/badge?q=@1gr14/route0)](https://bundlejs.com/?q=@1gr14/route0)\n[![license](https://img.shields.io/npm/l/@1gr14/route0.svg)](./LICENSE)\n\n<!-- docs:start -->\n\nroute0 turns a URL pattern into a set of fully-typed helpers. You write the\npattern — `/idea/:id[int]&page[int]=0` — **once**, and from that single string\nyou get a typed path builder, a URL parser, typed path _and_ search params, a\n[Standard Schema](https://standardschema.dev) validator with JSON Schema output,\nand the matching primitives you'd build a router from. Params are inferred from\nthe pattern — types, enums, defaults and all; you never hand-write them.\n\n## Why\n\nMost apps — whatever the framework — need to declare paths, for pages or for API\nendpoints. The crude way is to scatter string literals:\n`<Route path=\"/idea/:id\" />` in one place, `<Link to=\"/idea/123\" />` in another.\nRename the path and you're hand-fixing every call site, with no type checker to\ncatch the one you missed.\n\nA tidier attempt is a `routes.ts` full of functions like\n`const ideaView = (id: string) => '/idea/' + id`. Better — but you still declare\nevery argument by hand, and the moment you want search params, a hash, or an\nabsolute URL, you're back to gluing strings together.\n\nroute0 derives all of that from the pattern itself:\n\n```ts\nimport { Route0 } from '@1gr14/route0'\n\nconst ideaView = Route0.create('/idea/:id')\n\nideaView({ id: 123 }) // '/idea/123'\nideaView.abs({ id: 123 }) // 'https://example.com/idea/123'\nideaView({ id: 123, '?': { ref: 'feed' } }) // '/idea/123?ref=feed'\nideaView({ id: 123, '#': 'comments' }) // '/idea/123#comments'\nideaView.definition // '/idea/:id'\n```\n\n## Not a router\n\nroute0 doesn't match requests or render pages — it's the typed-path layer that\nsits _under_ a router. Bring your own, plug it into an existing one, or use\n[Point0](https://1gr14.dev/point0), which has route0 built in. The matching\nprimitives further down (`getRelation`, the `is*` checks, specificity ordering)\nare exactly what you need to wire one up.\n\n## Install\n\n```sh\nbun add @1gr14/route0 @1gr14/flat\n# or: npm install / pnpm add / yarn add\n```\n\nBun 1+ or Node.js 20+. ESM only. `@1gr14/flat` is a required peer dependency\n(used for search-string encoding) — install it alongside route0, since pnpm and\nyarn don't auto-install peers. `@standard-schema/spec` is an optional peer.\n\n## Build a path\n\n`Route0.create(pattern)` returns a route. The route is **callable** — call it\ndirectly, or use `.get()`; they do the same thing. Params named in the pattern\n(`:org`, `:id`) are required, typed, and accept a `string` or a `number`:\n\n```ts\nconst route = Route0.create('/org/:org/users/:id')\n\nroute({ org: 'acme', id: 42 }) // '/org/acme/users/42'  — callable form\nroute.get({ org: 'acme', id: 42 }) // same thing\nroute.definition // '/org/:org/users/:id'  — the pattern back out\nroute.params // { org: { required: true, type: 'string' }, id: { required: true, type: 'string' } }\n```\n\nOne rule to know up front: **building never throws.** The TS types are the\nstrict layer; at runtime `get()` is best-effort — a value that slipped past the\ntypes (a form field, a DB row, JSON) produces a link that at worst matches\nnothing, a missing (or empty) required param emits the literal `undefined`, and\nan unset or malformed `origin` just keeps the URL relative. A broken href is a\nsmall bug; a page that dies rendering one is a big one. When you want loud\nvalidation, that's what `.schema` and `.searchSchema` are for.\n\n## Optional and wildcard params\n\nMark a param optional with a trailing `?`, or capture the rest of the path with\n`*`:\n\n```ts\nconst post = Route0.create('/users/:id/posts/:slug?')\npost.get({ id: '1', slug: 'hello' }) // '/users/1/posts/hello'\npost.get({ id: '1' }) // '/users/1/posts'  — optional param dropped\n\nconst files = Route0.create('/files/*')\nfiles.get({ '*': 'a/b/c.txt' }) // '/files/a/b/c.txt'\nfiles.getRelation('/files/a/b/c.txt').params // { '*': 'a/b/c.txt' }\n```\n\nA wildcard always lives under the `'*'` key. It may be a whole segment (`/*`) or\ninline within one (`/files/x*`); only one wildcard is allowed, and it must come\nlast.\n\n## Restrict a param to a set of values\n\nList the values a param accepts in parentheses. The param then matches only\nthose values, and its type narrows from `string` to the literal union:\n\n```ts\nconst post = Route0.create('/:locale(ru|en)/post/:slug')\n\npost.get({ locale: 'ru', slug: 'hello' }) // '/ru/post/hello'\npost.get({ locale: 'fr', slug: 'hello' }) // ✗ type error — at runtime builds '/fr/post/hello', which matches nothing\n\npost.getRelation('/ru/post/hello').params // { locale: 'ru', slug: 'hello' }\npost.isExact('/fr/post/hello') // false — 'fr' is not a locale\n\ntype Params = typeof post.Infer.ParamsOutput\n// { locale: 'ru' | 'en'; slug: string }\n```\n\nThe narrowing runs in both directions: `get()` rejects a value you didn't list\nat the type level, and parsed params come back as the union — so a `switch` over\n`locale` is exhaustive and a typo is a compile error instead of a 404 you find\nin production. `.schema` validates against the same set (loudly, unlike\n`get()`), and the JSON Schema it emits carries it as an `enum`.\n\nAdd `?` after the closing parenthesis to make the param optional, exactly as\nwith a plain one — which is how you get an optional locale prefix:\n\n```ts\nconst routes = Routes.create({\n  home: '/:locale(ru|en)?',\n  author: '/:locale(ru|en)?/author',\n})\n\nroutes._.getLocation('/author').params // { locale: undefined }\nroutes._.getLocation('/ru/author').params // { locale: 'ru' }\nroutes._.getLocation('/zz/author').route // undefined — a real 404\n```\n\nWithout the constraint, `/zz/author` would match with `locale: 'zz'`, and any\nunknown first segment would quietly become a valid locale.\n\nTwo routes whose value sets don't overlap can share a shape without becoming\nambiguous:\n\n```ts\nRoute0.create('/:locale(ru|en)/x').isConflict('/:kind(new|old)/x') // false\nRoute0.create('/:locale(ru|en)/x').isConflict('/:other(en|de)/x') // true — 'en' is in both\n```\n\n### What a value may contain\n\nA value is built from URL-unreserved characters — letters, digits, `_`, `.`,\n`~`, `-`. That keeps its encoded and decoded forms identical, so a match never\ndepends on how the client encoded the URL. Anything else is rejected when the\nroute is created, as are duplicates and empty values:\n\n```ts\nRoute0.create('/:locale(ru|en)') // fine\nRoute0.create('/:locale(ru|EN)') // fine — matching is case-sensitive\nRoute0.create('/:locale(ru|рус)') // ✗ throws — non-ASCII\nRoute0.create('/:path(a/b)') // ✗ throws — a '/' can't live inside one segment\nRoute0.create('/:locale(ru|ru)') // ✗ throws — duplicate value\n```\n\nRejection happens at creation, with a message naming the grammar. A malformed\nparam never degrades into a literal path segment that silently matches nothing.\n\nA param may list at most **32 values**. The cap exists because the union is\nbuilt in the type system: somewhere past fifty alternatives TypeScript gives up\nwith an \"excessively deep\" error at the call site, which tells you nothing about\nwhat went wrong. The cap turns that into a clear message instead. If you need a\nlarger set, use a plain param and check the value yourself.\n\n## Give a param a type\n\nSquare brackets give a param a type. The param then matches only that type's\ncanonical string form, `get()` demands the matching JS type, and parsed params\ncome back converted — a real `number`, `boolean`, `bigint`, or `Date`:\n\n```ts\nconst user = Route0.create('/users/:id[int]')\n\nuser({ id: 42 }) // '/users/42'\nuser({ id: '42' }) // ✗ type error — but the canonical string coerces at runtime: '/users/42'\nuser.getRelation('/users/42').params // { id: 42 }  — a number, not a string\nuser.isExact('/users/abc') // false\nuser.isExact('/users/007') // false — leading zeros are not canonical\n\ntype Params = typeof user.Infer.ParamsOutput // { id: number }\n```\n\n| Type         | Matches                                | JS value              |\n| ------------ | -------------------------------------- | --------------------- |\n| `[str]`      | anything — the default, spelled out    | `string`              |\n| `[bool]`     | `true` · `false`                       | `boolean`             |\n| `[int]`      | `0`, `42` — non-negative integers      | `number`              |\n| `[-int]`     | `-7` too (never `-0`)                  | `number`              |\n| `[num]`      | `3`, `1.5` — non-negative decimals     | `number`              |\n| `[-num]`     | `-1.5`, `-0.5` too                     | `number`              |\n| `[bigint]`   | like `[int]`, unlimited length         | `bigint`              |\n| `[-bigint]`  | like `[-int]`, unlimited length        | `bigint`              |\n| `[uuid]`     | `8-4-4-4-12` hex, either case          | `string`              |\n| `[date]`     | `2026-08-26`                           | `Date` (UTC midnight) |\n| `[datetime]` | ISO 8601 with a zone (`Z` or `±HH:MM`) | `Date`                |\n\nNumeric types are **non-negative by default** — URL params are mostly ids, pages\nand counts. The `-` modifier (`[-int]`, `[-num]`, `[-bigint]`) also allows the\nminus.\n\nMatching accepts **canonical forms only**: no leading zeros, no `+`, no\nexponents, no trailing fraction zeros, no `-0`. That keeps building and parsing\na bijection — every URL that matches parses to a value that builds back into the\nsame URL. `.schema` enforces the same canon from the other side (`{ id: 1.5 }`\nfor `[int]` fails validation, so do `'007'` and an `Invalid Date`), while\n`get()` — never-throw by policy — emits such values best-effort into a link that\nmatches nothing. At runtime both accept a typed value **or its canonical\nstring** (`'3'` for `[int]`, an ISO string for `[date]`) — everything on the\nwire is a string anyway; the TS types stay strict. Combine with `?` for optional\nas usual: `:page[int]?`. An id that can outgrow 2^53 belongs in `[bigint]` —\n`[int]` matches any digit count, but `number` loses precision past\n`Number.MAX_SAFE_INTEGER`.\n\nDates follow the JS spec's UTC convention: `[date]` parses to UTC midnight and\nbuilds from the Date's UTC components — so create link dates as\n`new Date('2026-08-26')`, not `new Date(2026, 7, 26)` (that's a _local_\nmidnight, which shifts a day in some timezones). `[datetime]` is the one\ndeliberate exception to strict bijection: parsing is lenient (a `Z` or a\n`±HH:MM` offset, optional seconds — a `Date` holds the exact instant either\nway), while building always emits the `toISOString()` canon.\n\nTypes feed the conflict analysis too: routes whose types can't share a URL\ncoexist freely, and genuinely ambiguous pairs are reported:\n\n```ts\nRoute0.create('/x/:a[int]').isOverlap('/x/:b[uuid]') // false — no common URL\nRoute0.create('/x/:a[int]').isConflict('/x/:b[num]') // false — int is tried first\nRoute0.create('/x/:a[int]').isConflict('/x/:b[bigint]') // true — same strings, different values\n```\n\n`.schema` validates the typed values, and the JSON Schema it emits carries the\nright shapes: `[int]` → `{ type: 'integer', minimum: 0 }`, `[bool]` →\n`{ type: 'boolean' }`, `[uuid]` → `format: 'uuid'`, `[date]`/`[datetime]` →\n`format: 'date'`/`'date-time'`. Two documented gaps, because JSON has neither\ntype: `[bigint]` is emitted as `integer` and the `Date`-valued types as\nformatted strings.\n\n## Prefix and suffix inside a segment\n\nA param doesn't have to own its whole segment — wrap it in literal text:\n\n```ts\nconst image = Route0.create('/files/img-:id[int].png')\n\nimage({ id: 7 }) // '/files/img-7.png'\nimage.getRelation('/files/img-7.png').params // { id: 7 }\nimage.isExact('/files/img-7.jpg') // false\nimage.isExact('/files/img-x.png') // false — the body is still [int]\n\nRoute0.create('/v:major[int]')({ major: 2 }) // '/v2'\nRoute0.create('/:file.mp4').getRelation('/talk.mp4').params // { file: 'talk' }\n```\n\nPrefix and suffix are URL-unreserved literals (`[A-Za-z0-9_.~-]`); a second\nparam is possible too, but only behind a delimiter — that's the next section. A\ntrailing `?` makes the whole segment optional — prefix and suffix drop out\ntogether with the value:\n\n```ts\nconst paged = Route0.create('/files/page-:n[int]?')\npaged({}) // '/files'\npaged({ n: 2 }) // '/files/page-2'\n```\n\nThe param name is a `[A-Za-z0-9_]+` run, so a suffix starts at the first `.`,\n`~` or `-` (`:file.mp4` — name `file`, suffix `.mp4`), and any `:` in a\nnon-wildcard segment is param intent: what doesn't parse is rejected at\ncreation, never silently downgraded to a static segment.\n\n## A second param after a delimiter\n\nThe classic file-extension shape — and ranges, and versions. A segment may carry\na **tail param** after a one-character delimiter (`.`, `-` or `~`):\n\n```ts\nconst file = Route0.create('/my/:slug.:ext')\nfile({ slug: 'talk', ext: 'md' }) // '/my/talk.md'\nfile.getRelation('/my/a.b.md').params // { slug: 'a.b', ext: 'md' }\n\nconst version = Route0.create('/v:maj[int].:min[int]')\nversion.getRelation('/v2.7').params // { maj: 2, min: 7 }\n\nconst range = Route0.create('/range/:from-:to')\nrange.getRelation('/range/a-b-c').params // { from: 'a-b', to: 'c' }\n```\n\nThe rule that keeps this deterministic: **the tail can never contain its own\ndelimiter** — a plain tail matches everything but it, an enum/typed tail is\nchecked at creation (`:a-:d[date]` is rejected: a date contains `-`). So the\nsplit always lands on the _last_ delimiter, the first param may contain it\nfreely, and building + parsing stay a round-trip (feeding a delimiter-carrying\nvalue INTO a tail is the one thing `.schema` flags for it). At most two params\nper segment, the tail always ends it.\n\nA trailing `?` on a two-param segment makes the **tail** optional — the\ndelimiter disappears with it (on a single-param segment `?` still means the\nwhole segment):\n\n```ts\nconst doc = Route0.create('/my/:slug.:ext?')\ndoc.getRelation('/my/talk.md').params // { slug: 'talk', ext: 'md' }\ndoc.getRelation('/my/talk').params // { slug: 'talk', ext: undefined }\ndoc({ slug: 'talk' }) // '/my/talk'\ndoc.schema.safeParse({ slug: 'a.md' }).success // false — would re-parse as slug 'a' + ext 'md'\ndoc({ slug: 'a.md', ext: 'txt' }) // '/my/a.md.txt' — providing the tail disambiguates\n```\n\nThat validation failure is the bijection guard: a URL built without the tail\nmust not come back _with_ one (`get()` itself stays never-throw and emits what\nyou gave it). An enum tail narrows the guard to its own values —\n`/:name.:ext(md|txt)?` parses `/a.pdf` as `name: 'a.pdf'` (`.pdf` is no valid\ntail) and builds it back untouched.\n\n### Wildcard tails\n\nThe same mechanics work on a wildcard — the file-server shapes:\n\n```ts\nconst markdown = Route0.create('/docs/*.md')\nmarkdown.isExact('/docs/a/b/c.md') // true\nmarkdown.getRelation('/docs/a/b/c.md').params // { '*': 'a/b/c' }\n\nconst raw = Route0.create('/raw/*.:ext')\nraw.getRelation('/raw/a/b.md').params // { '*': 'a/b', ext: 'md' }\nraw.isExact('/raw/a.b/c') // false — no extension in the last segment\n```\n\nA wildcard with a suffix or tail demands a non-empty body (`/docs/.md` and\n`/docs` don't match), the extension can only come from the **last** segment (the\ntail never contains `/` or the delimiter), and `*.:ext?` behaves like the param\nversion, guard included. `*?` cannot carry a tail. Unlike a param's suffix, a\nwildcard's _literal_ suffix may even start with a letter (`/files/*_thumb`) —\nthe star itself is the boundary, no name-run ambiguity to protect.\n\n## Search params and hash\n\nPass search params under the `?` key and a fragment under `#`. Arrays and deeply\nnested objects are encoded for you (this is what the `@1gr14/flat` peer is for):\n\n```ts\nconst search = Route0.create('/search')\n\nsearch.get({\n  '?': {\n    q: 'shoes',\n    tags: ['sale', 'new'],\n    filters: { price: { min: 10, max: 50 } },\n  },\n})\n// '/search?q=shoes&tags[]=sale&tags[]=new&filters[price][min]=10&filters[price][max]=50'\n// (the brackets are percent-encoded in the returned string)\n\nideaView.get({ id: 9, '#': 'reviews' }) // '/idea/9#reviews'\n```\n\n## Absolute URLs\n\nPass an `origin` in the options object — `true` uses the route's configured\norigin (or `window.location.origin` in the browser), or hand it an explicit\nstring:\n\n```ts\nconst ideaView = Route0.create('/idea/:id', { origin: 'https://1gr14.dev' })\n\nideaView.get({ id: 1 }, { origin: true }) // 'https://1gr14.dev/idea/1'\nideaView.get({ id: 1 }, { origin: 'https://cdn.1gr14.dev' }) // 'https://cdn.1gr14.dev/idea/1'\n```\n\n`route.abs()` is the same as `get()` but defaults `origin` to `true`, so it's\nthe shorthand when you always want an absolute URL:\n\n```ts\nideaView.abs({ id: 1 }) // 'https://1gr14.dev/idea/1'\nideaView.abs({ id: 1 }, { origin: false }) // '/idea/1'  — opt back out\n```\n\n## Pretty, unencoded paths\n\nBy default path params and the search string are percent-encoded. Pass\n`encode: false` for a human-readable URL — handy for display:\n\n```ts\nconst file = Route0.create('/files/:name')\nfile.get({ name: 'a b' }) // '/files/a%20b'\nfile.get({ name: 'a b', '?': { q: 'x y' } }) // '/files/a%20b?q=x%20y'\nfile.get({ name: 'a b', '?': { q: 'x y' } }, { encode: false }) // '/files/a b?q=x y'\n```\n\n## Extend a route\n\nNeed a shared prefix for a whole section? `route.extend(suffix)` appends to an\nexisting route and returns a new one — types and all — so you declare the base\nonce and grow from it:\n\n```ts\nconst ideaBase = Route0.create('/idea')\nconst ideaView = ideaBase.extend('/:id')\nconst ideaEdit = ideaView.extend('/edit')\n\nideaView.definition // '/idea/:id'\nideaView({ id: '123' }) // '/idea/123'\n\nideaEdit.definition // '/idea/:id/edit'\nideaEdit({ id: '123' }) // '/idea/123/edit'\n```\n\n## Declare search params in the pattern\n\nSearch params can live right in the pattern, after the path, `&`-separated. Each\ndeclaration is a name plus optional modifiers, in this order: a `[type]` **or**\nan `(enum)`, then `[]` for an array, then `!` for required, then `=default`:\n\n```ts\nconst list = Route0.create('/ideas&q&page[int]=0&sort(new|top)=new&ids[int][]')\n\nlist({ '?': { page: 2, sort: 'top' } }) // '/ideas?page=2&sort=top'\nlist({ '?': { page: '2' } }) // ✗ type error — but '2' coerces at runtime: '/ideas?page=2'\nlist({}) // '/ideas' — defaults are never inserted into a URL\n\nlist.searchParams.page // { required: false, array: false, type: 'int', default: 0 }\n\ntype Search = typeof list.Infer.SearchInput\n// { q?: string | number; page?: number; sort?: 'new' | 'top'; ids?: number[] }\n```\n\nThe rules, in brief:\n\n- **Optional by default.** `&token!` makes one required: `get()`'s input type\n  demands it and `searchSchema` fails without it (the never-throw `get()` just\n  omits it from the URL). Matching is never affected — see below.\n- **Defaults are parse-side only.** `&page[int]=0` means \"absent in the URL ⇒\n  `0` in the parsed output\". Building never inserts a default; a `!` or a `[]`\n  can't combine with `=` (rejected at creation).\n- **Arrays.** `&ids[int][]` — the wire format is `ids[]=1&ids[]=2`, a single\n  bare value counts as an array of one, and an absent array parses to `[]`,\n  never `undefined`.\n- **Strict vs loose.** Declaring params closes the `?` object to exactly those\n  keys. A trailing `&` (`/ideas&page[int]&`) keeps it open — declared keys\n  typed, anything else allowed. No `&` at all is today's fully-open behavior.\n- **Matching stays pathname-only.** Search never decides `isExact` or\n  `getRelation` — a route with a required search param still matches its path;\n  validation is the schema's job.\n\n### Parse and validate search\n\nEvery route carries a `searchSchema` (Standard Schema, like `.schema`): it takes\na parsed search object — raw URL strings and typed values alike — coerces\ndeclared keys, fills defaults, wraps arrays, and drops unknown keys in strict\nmode:\n\n```ts\nlist.searchSchema.parse({ page: '2', ids: '7' })\n// { q: undefined, page: 2, sort: 'new', ids: [7] }\nlist.searchSchema.safeParse({ page: 'abc' }).success // false\n```\n\nIts JSON Schemas mirror the coercion (input accepts the string forms,\n`additionalProperties` follows strict/loose). For a forgiving view there's\n`route.coerceSearch(obj)` — same conversion, but an invalid value degrades to\nits absent case instead of failing. That's exactly what a collection applies on\na match, so `loc.search` arrives typed:\n\n```ts\nconst routes = Routes.create({ list: '/ideas&q&page[int]=0&ids[int][]' })\n\nroutes._.getLocation('/ideas?q=x&page=2&ids=7').search\n// { q: 'x', page: 2, ids: [7] }\nroutes._.getLocation('/ideas?page=abc').search\n// { q: undefined, page: 0, ids: [] } — invalid degrades to the default\n```\n\n`extend()` concatenates declarations from both sides (duplicate names are\nrejected), so a section base can declare shared search params once.\n\n### `.search<T>()` — the escape hatch\n\nThe string can only declare flat scalars. For nested shapes, call `.search<…>()`\n— a type-only refinement that merges _on top of_ the declared params (a key the\npattern already declares is a type error):\n\n```ts\nconst filtered = Route0.create('/ideas&page[int]').search<{\n  filter?: { price: { min: number; max: number } }\n}>()\n\nfiltered.get({ '?': { page: 2, filter: { price: { min: 10, max: 50 } } } })\n// '/ideas?page=2&filter[price][min]=10&filter[price][max]=50' (brackets encoded)\n```\n\n## Validate params with Standard Schema\n\nEvery route exposes a `.schema` that implements\n[Standard Schema](https://standardschema.dev), so it parses and validates params\nand drops into any pipeline that speaks the spec. A plain param coerces a\n`number` to its string; a typed param validates and keeps its JS type:\n\n```ts\nconst route = Route0.create('/x/:id/:slug?')\n\nroute.schema.safeParse({ id: 1 })\n// { success: true, data: { id: '1', slug: undefined }, error: undefined } — number coerced\nroute.schema.safeParse({ slug: 'x' })\n// { success: false, data: undefined, error: Error } — 'id' is required\nroute.schema.parse({ id: '1' }) // { id: '1', slug: undefined } — throws on invalid input\n```\n\n## OpenAPI\n\nA route describes an OpenAPI operation completely, and two helpers hand it over.\n`toUriTemplate()` is the path in the `{param}` template form OpenAPI's `paths`\nobject speaks — in-segment literals and tail params included, while a value\nconstraint, a param type or a trailing `?` never leak in.\n`toOpenapiParameters()` is the matching `parameters` array: every path param\n(always `required: true` — the spec demands it), then every declared search\nparam as `in: 'query'` with the canonical typed schema, arrays wrapped and\ndefaults attached:\n\n```ts\nconst route = Route0.create('/files/img-:id[int].png&page[int]=0&token!')\n\nroute.toUriTemplate() // '/files/img-{id}.png'\nroute.toOpenapiParameters()\n// [\n//   { name: 'id', in: 'path', required: true, schema: { type: 'integer', minimum: 0 } },\n//   { name: 'page', in: 'query', required: false, schema: { type: 'integer', minimum: 0, default: 0 } },\n//   { name: 'token', in: 'query', required: true, schema: { type: 'string' } },\n// ]\n```\n\nA wildcard has no template variable, so `toUriTemplate()` emits it verbatim\n(`/docs/*`, `/raw/*.{ext}`) and `toOpenapiParameters()` skips it. For request\nand response **bodies** keep using `.schema` / `.searchSchema` — both implement\nStandard JSON Schema, so\n`schema['~standard'].jsonSchema.input({ target: 'openapi-3.0' })` emits the\nobject form directly.\n\n## Infer types from a route\n\nEvery route carries a type-only `Infer` field, so you can pull its types\nstraight off the instance with `typeof` — no generics, no helper imports:\n\n```ts\nconst route = Route0.create('/users/:id/:tab?').search<{ ref?: string }>()\n\ntype ParamsInput = typeof route.Infer.ParamsInput\n// { id: string | number; tab?: string | number | undefined }\n\ntype ParamsOutput = typeof route.Infer.ParamsOutput\n// { id: string; tab: string | undefined }\n\ntype SearchInput = typeof route.Infer.SearchInput\n// { ref?: string }\n```\n\n`Infer` exists only at the type level (its runtime value is `null`), so always\nread it through `typeof`. The members:\n\n| Member                  | What it is                                                              |\n| ----------------------- | ----------------------------------------------------------------------- |\n| `ParamsDefinition`      | Map of param name → its descriptor (see `params` above).                |\n| `ParamsInput`           | What `get()` accepts — required as `string \\| number`, optional opt-in. |\n| `ParamsInputStringOnly` | Same as `ParamsInput`, but every param in its URL-string form.          |\n| `ParamsOutput`          | Parsed params — required `string`, optional `string \\| undefined`.      |\n| `SearchInput`           | Everything `?` accepts: declared params + the `.search<…>()` addition.  |\n| `SearchInputStringOnly` | Same, but every declared param in its URL-string form.                  |\n| `SearchOutput`          | What `searchSchema` yields — coerced, defaults filled, arrays wrapped.  |\n\nFor a param restricted to a set of values, `string` above is that param's\nliteral union instead — and `ParamsInput` drops `number`, since a number could\nnever be one of the listed values. A typed param swaps in its JS type\neverywhere: `:id[int]` is `number` in and out, `:d[date]` is `Date`.\n\nEach member also exists as a standalone type — `ParamsOutput<typeof route>`,\n`ParamsDefinition<'/:locale(ru|en)?'>`, and so on — taking either a route or a\npattern string. A single param reads straight off the descriptor:\n`ParamsDefinition<typeof route>['locale']['required']`.\n\n## Parse any URL\n\n`Route0.getLocation(url)` is the inverse of building — it takes any href, path,\n`URL`, or location-like object and returns a structured, route-agnostic location\n(the search string is parsed with the same nested-aware rules used to build it):\n\n```ts\nconst loc = Route0.getLocation('/search?q=shoes&tag[]=a&tag[]=b#results')\n\nloc.pathname // '/search'\nloc.search // { q: 'shoes', tag: ['a', 'b'] }  — parsed, nested-aware\nloc.searchString // '?q=shoes&tag[]=a&tag[]=b'\nloc.hash // '#results'\nloc.hrefRel // '/search?q=shoes&tag[]=a&tag[]=b#results'  — pathname + search + hash\nloc.abs // false  — input was relative\nloc.route // undefined  — no route was matched against\nloc.params // undefined\n```\n\nFor an absolute input you also get `origin`, `href`, `host`, `hostname`, and\n`port` filled in (otherwise they're `undefined`).\n\n## Match a URL against a route\n\n`getRelation(url)` matches a URL against the route and tells you how **the route\nrelates to that URL**, with typed params pulled out:\n\n- `exact` — the URL _is_ this route.\n- `ancestor` — the route is an ancestor of the URL (the URL is a deeper\n  sub-path).\n- `descendant` — the route is a descendant of the URL (the URL is a shallower\n  prefix).\n- `unmatched` — unrelated.\n\n```ts\nconst route = Route0.create('/users/:id')\n\nroute.getRelation('/users/42')\n// { type: 'exact', params: { id: '42' }, exact: true, ancestor: false, descendant: false, unmatched: false, route: '/users/:id' }\nroute.getRelation('/users/42/posts') // { type: 'ancestor',   params: { id: '42' }, ... }\nroute.getRelation('/users') // { type: 'descendant', params: {},          ... }\nroute.getRelation('/about') // { type: 'unmatched',  params: {},          ... }\n```\n\nWhen you only need a yes/no and not the params, the `is*` checks skip building\nthe relation object — cheaper on hot paths like rendering nav links:\n\n```ts\nroute.isExact('/users/42') // true\nroute.isExactOrAncestor('/users/42/posts') // true  — \"is this nav link active?\"\nroute.isAncestor('/users/42/posts') // true\nroute.isDescendant('/users') // true\n```\n\n## A collection of routes\n\nKeeping every route in its own variable gets noisy. `Routes.create()` gathers\nthem into one typed object — pass plain pattern strings, route instances, or a\nmix. Each route stays individually typed and callable, reachable by its key:\n\n```ts\nimport { Route0, Routes } from '@1gr14/route0'\n\nconst routes = Routes.create({\n  ideaNew: '/idea/new',\n  ideaView: Route0.create('/idea/:id'),\n  ideaEdit: '/idea/:id/edit',\n})\n\nroutes.ideaView({ id: '123' }) // '/idea/123'\nroutes.ideaEdit({ id: '123' }) // '/idea/123/edit'\n```\n\nEverything under `._` is the collection's own toolbox, kept on a separate key so\nit never collides with your route names.\n\n## Match against the whole collection\n\n`routes._.getLocation(url)` matches a URL against every route at once and\nreturns the location of the first (most specific) **exact** match — enriched\nwith the matched `route` and its typed `params`:\n\n```ts\nconst loc = routes._.getLocation('https://example.com/idea/123/edit?ref=feed')\n\nloc.route // '/idea/:id/edit'  — the pattern that matched\nloc.params // { id: '123' }\nloc.search // { ref: 'feed' }\nloc.pathname // '/idea/123/edit'\nloc.hrefRel // '/idea/123/edit?ref=feed'\nloc.href // 'https://example.com/idea/123/edit?ref=feed'\nloc.abs // true\n\nroutes._.getLocation('/nope').route // undefined  — nothing matched\n```\n\n## Deterministic match order\n\nA collection sorts its routes once, from most specific to least, and exposes\nthat order. This is what lets `/idea/new` and `/idea/:id` coexist: the static\nroute is tried first, so it wins the URL `/idea/new` instead of being swallowed\nby the param route.\n\n```ts\nroutes._.pathsOrdering // ['/idea/new', '/idea/:id', '/idea/:id/edit']  — patterns, specific first\nroutes._.keysOrdering // ['ideaNew', 'ideaView', 'ideaEdit']           — same order, by key\nroutes._.ordered[0].definition // '/idea/new'                          — same order, as route objects\n```\n\nThe order is total and deterministic (independent of insertion order), so you\ncan feed `_.ordered` straight into a real router and trust that more specific\npatterns always come first.\n\n## Share a base origin\n\n`routes._.clone(config)` returns a new collection with the config applied to\nevery route — the usual case is stamping an `origin` on the whole set so\n`.abs()` works everywhere:\n\n```ts\nconst absRoutes = routes._.clone({ origin: 'https://1gr14.dev' })\nabsRoutes.ideaView.abs({ id: 123 }) // 'https://1gr14.dev/idea/123'\n```\n\nA single route has the same `route.clone(config)`.\n\n## Compare and order patterns yourself\n\nWhen you're wiring up your own router, you sometimes need to reason about two\npatterns directly. These comparators answer that:\n\n```ts\nconst view = Route0.create('/idea/:id')\nconst fresh = Route0.create('/idea/new')\n\nfresh.isMoreSpecificThan(view) // true  — a static segment beats a param\nview.isOverlap(fresh) // true  — both can match '/idea/new'\nview.isConflict(fresh) // false — ordering resolves it (try the static one first)\n\nRoute0.create('/idea/:id').isConflict('/idea/:slug')\n// true — same shape, equally specific: no ordering can tell them apart\n```\n\n`isOverlap` asks whether two patterns can ever match the same URL; `isConflict`\nnarrows that to overlaps that ordering _can't_ resolve (genuine ambiguity you\nhave to fix); `isMoreSpecificThan` is the total order the collection sorts by.\n\n## Lower-level building blocks\n\nThe pieces a router generator tends to reach for:\n\n```ts\n// Inspect a pattern's structure\nRoute0.create('/users/:id/posts/:slug?').getTokens()\n// [\n//   { kind: 'static', value: 'users' },\n//   { kind: 'param', name: 'id', optional: false },\n//   { kind: 'static', value: 'posts' },\n//   { kind: 'param', name: 'slug', optional: true },\n// ]\nRoute0.create('/org/:org/users/:id').getParamsKeys() // ['org', 'id']\n\n// A restricted param carries its values on the token, and on its descriptor\nRoute0.create('/:locale(ru|en)?/author').getTokens()\n// [\n//   { kind: 'param', name: 'locale', optional: true, values: ['ru', 'en'] },\n//   { kind: 'static', value: 'author' },\n// ]\nRoute0.create('/:locale(ru|en)?/post/:slug').params.locale\n// { required: false, type: 'enum', values: ['ru', 'en'] }\n// an unrestricted param is `{ required, type: 'string' }` — no `values` key, and none on its token\n// tokens and descriptors are frozen: mutating them would widen the schema without widening the matcher\n\n// A typed param carries its type; segment prefix/suffix live on the token only\nRoute0.create('/files/img-:id[int].png').getTokens()\n// [\n//   { kind: 'static', value: 'files' },\n//   { kind: 'param', name: 'id', optional: false, type: 'int', prefix: 'img-', suffix: '.png' },\n// ]\nRoute0.create('/files/img-:id[int].png').params.id // { required: true, type: 'int' }\n\n// A tail param rides on its segment's token; its descriptor is a param like any other\nRoute0.create('/my/:slug.:ext?').getTokens()\n// [\n//   { kind: 'static', value: 'my' },\n//   { kind: 'param', name: 'slug', optional: false, tail: { name: 'ext', optional: true, delimiter: '.' } },\n// ]\n\n// Declared search params have their own descriptor map\nRoute0.create('/ideas&page[int]=0&ids[int][]').searchParams\n// {\n//   page: { required: false, array: false, type: 'int', default: 0 },\n//   ids: { required: false, array: true, type: 'int' },\n// }\n\n// Normalize \"route or string\" inputs — returns the same instance if already a route\nRoute0.from('/users/:id') // a callable route\nRoute0.from(existingRoute) // the same instance, untouched\n\n// One combined regex that matches any route in a set\nconst re = Route0.getRegexGroup([routes.ideaNew, routes.ideaView])\nre.test('/idea/new') // true\n```\n\n## Requirements\n\n- **Bun 1+** or **Node.js 20+** (ESM only)\n- **TypeScript 5+** (optional — works in plain JS too)\n- Peer: `@1gr14/flat`; optional peer: `@standard-schema/spec`\n\n<!-- docs:end -->\n\n## Community\n\nQuestions, bugs, or want to hang with other builders? Join the 1gr14 community —\none hub for all our open-source projects, this one included. Get help, share\nwhat you built, or just say hi:\n[1gr14.dev/#community](https://1gr14.dev/#community)\n\n## Contributing\n\nIssues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and the\n[Code of Conduct](./CODE_OF_CONDUCT.md). Commits follow\n[Conventional Commits](https://www.conventionalcommits.org/). Security reports:\n[SECURITY.md](./SECURITY.md).\n\n## License\n\n[MIT](./LICENSE)\n\n---\n\nMade by [1gr14](https://1gr14.dev), driven by\n[community](https://1gr14.dev/#community)\n","readmeFilename":"README.md"}