{"_id":"@anilanar/typesafe-routes","name":"@anilanar/typesafe-routes","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@anilanar/typesafe-routes","version":"0.0.1","main":"build/index.js","types":"build/index.d.ts","repository":{"type":"git","url":"git+ssh://git@github.com/kruschid/typesafe-routes.git"},"author":{"name":"Anil Anar","email":"anilanar@hotmail.com"},"contributors":[{"name":"Denis Kruschinski","email":"denis.kruschinski@gmail.com"}],"license":"MIT","scripts":{"build":"yarn test && yarn tsc","test":"yarn tsd && yarn tape -r ts-node/register src/test.ts","express":"ts-node src/demo/express.ts","react":"parcel src/demo/index.html"},"devDependencies":{"@types/express":"^4.17.13","@types/node":"^17.0.14","@types/react":"^17.0.39","@types/react-dom":"^17.0.11","@types/tape":"^4.13.2","express":"^4.17.2","parcel":"^2.2.1","react":"^17.0.2","react-dom":"^17.0.2","react-router-dom":"^6.2.1","tape":"^5.5.0","ts-node":"^10.4.0","ts-toolbelt":"^8.0.6","tsd":"^0.19.1","typescript":"^4.5.5"},"peerDependencies":{"typescript":"^4.1.0"},"peerDependenciesMeta":{"react-router-dom":{"optional":true}},"dependencies":{"@types/qs":"^6.9.7","qs":"^6.10.3"},"gitHead":"973f389b5ba762513704c1c99675747e1d1b28a6","description":"<img title=\"logo\" src=\"logo.png\" />","bugs":{"url":"https://github.com/kruschid/typesafe-routes/issues"},"homepage":"https://github.com/kruschid/typesafe-routes#readme","_id":"@anilanar/typesafe-routes@0.0.1","_nodeVersion":"18.14.0","_npmVersion":"9.3.1","dist":{"integrity":"sha512-CbVytmQAHSwA1XiOmVtzfu7udlQ/TyWzDsjDuMb4Ql+iLYIU0TcQs4G/ck750fdQZSuvslCWPxvwR13N9Coysw==","shasum":"c5ebb2e0d3cf62e19a05dd01860fd50576ce7fd1","tarball":"https://registry.npmjs.org/@anilanar/typesafe-routes/-/typesafe-routes-0.0.1.tgz","fileCount":17,"unpackedSize":37129,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDCwsMY4lMMUKvvPTFU1su29zKh2uarFUV5Rq8MIU1LowIhAJiyfzbDPcR7ojgW22xn1xJ5qxNhRQDSkhPTDZKAYDJj"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkA1h9ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoVXBAAoVMvdDyzBcrHfNnj2MVp7myF1r+XlGq59tOYkmFMGWThejP3\r\noZ6fdJbBkU9MUcvlQUTadOPaAw/ibx5oXQWinbDtyWuYpPrixswpM9FcCs+a\r\nOCN2U7HPKYHM5Lsnijgq6ctrS91ABXCLHNzpmzmHpCpNr+6co2a7ft+d40oa\r\nMdeUJ6MD1+AQ6FeIPc7ZT8TBLeaOoVIwDn4M4AOSIL5pHQkdCZRk8uR4BQXj\r\ncRpZEbFSnB0bKMGYBwbVLeb+/FHwpL61b9DjBad9/y4qkKFTXX1wPAWnpME4\r\nShyGioE77ERnhzDwHwqCBhQAUkqu49hR9FZf+WIzy9E+c0ull+n82F4knbEW\r\nEcJhI4/R58BWLbtYsgM2iBh3TKeo3mVcx9X0AqTGJ07NgpcbD9e1No4833wG\r\nBVDeaTTNIKPQG8NCsU8HJVZ4JyRkRWvm6SqDicGnGCEJyStEBVJJwFyOnpUd\r\nabDWyA53bhQD0JGvyMB08CW4pVL1uaKmOrkHysdgZhE6E28mhgZpsnFk1fi0\r\nbtzcxTxnnOho8DDzsYuReH3N/HKWYUxmwhtFUsDkuPr1cCk7A344m2MZm2co\r\nvGtZLnNX992O4y7ZYYpZfnteGLNWG1Cjel/ayCI1XjEE07n9CSAuUlZPP7lI\r\nsUuP54dP71+Cd9aKfq0Q8S9TxTPOYrw/WA8=\r\n=lhMB\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"anilanar","email":"anilanar@hotmail.com"},"directories":{},"maintainers":[{"name":"anilanar","email":"anilanar@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/typesafe-routes_0.0.1_1677940861374_0.14920019335332957"},"_hasShrinkwrap":false}},"time":{"created":"2023-03-04T14:41:01.316Z","0.0.1":"2023-03-04T14:41:01.543Z","modified":"2023-03-04T14:41:01.696Z"},"maintainers":[{"name":"anilanar","email":"anilanar@hotmail.com"}],"description":"<img title=\"logo\" src=\"logo.png\" />","homepage":"https://github.com/kruschid/typesafe-routes#readme","repository":{"type":"git","url":"git+ssh://git@github.com/kruschid/typesafe-routes.git"},"contributors":[{"name":"Denis Kruschinski","email":"denis.kruschinski@gmail.com"}],"author":{"name":"Anil Anar","email":"anilanar@hotmail.com"},"bugs":{"url":"https://github.com/kruschid/typesafe-routes/issues"},"license":"MIT","readme":"<img title=\"logo\" src=\"logo.png\" />\n\n# WIP\n\nThis is a hard-fork from [typesafe-routes](https://github.com/kruschid/typesafe-routes). Will update documentation as time allows.\n\n\n# Typesafe Routes\n\nSpices up your favorite routing library by adding type-safety to plain string-based route definitions. Let typescript handle the detection of broken links in compilation time while you create maintainable software products.\n\nYou can use this utility with your favorite framework that follows [path-to-regex](https://github.com/pillarjs/path-to-regexp) syntax (although we only support a subset of it). You can find some demo applications with [react-router](https://reacttraining.com/react-router/) or [express](https://expressjs.com/) in `src/demo`.\n\n**Typesafe Routes utilizes [Template Literal Types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#template-literal-types) and [Recursive Conditional Types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#recursive-conditional-types). These features are only available in [typescript version 4.1](https://github.com/microsoft/TypeScript/issues/40124) and above.**\n\n## Installation (npm/yarn examples)\n\n``` sh\nnpm i typesafe-routes\n\n# or\n\nyarn add typesafe-routes\n```\n\n## Usage\n\n![example](assets/usage.gif)\n\n### `route(path: string, parserMap: Record<string, Parser>, children: Record<string, ChildRoute>)`\n\n* `path` the path following the `path-to-regex` syntax.\n* `parserMap` contains parameter-specific `Parser` identified by parameter name\n* `children` assigns route children for nested routes\n\n## Examples\n\n<details>\n  <summary>Basic Example</summary>\n\n  ``` ts\n  import { route, stringParser } from \"typesafe-routes\";\n\n  const accountRoute = route(\"/account/:accountId\", {\n    accountId: stringParser, // parser implicitly defines the type (string) of 'accountId'\n  }, {});\n\n  // serialisation:\n  accountRoute({ accountId: \"5c9f1e79e96c\" }).$\n  // => \"/account/5c9f1e79e96c\"\n\n  // parsing:\n  accountRoute.parseParams({ accountId: \"123\"}).$\n  // => { accountId: \"123\" }\n  ```\n\n  While `stringParser` is probably the most common parser/serializer there are also `intParser`, `floatParser`, `dateParser`, and `booleanParser` shipped with the module. But you are not limited to these. If you wish to implement your custom parserserializer just imlement the interface `Parser<T>`. You can find more details on that topic further down the page.\n</details>\n\n<details>\n  <summary>Nested Routes</summary>\n  \n  ``` ts\n  import { route } from \"typesafe-routes\";\n\n  const detailsRoute = route(\"details\", {}, {})\n  const settingsRoute = route(\"settings\", {}, { detailsRoute });\n  const accountRoute = route(\"/account\", {}, { settingsRoute });\n\n  accountRoute({}).settingsRoute({}).detailsRoute({}).$\n  // => \"/account/settings/details\"\n  ```\n</details>\n\n<details>\n  <summary>Absolute & Relative Routes</summary>\n\n  ``` ts\n  import { route } from \"typesafe-routes\";\n\n  const invoice = route(\":invoiceId\", { invoiceId: intParser }, {});\n\n  const invoices = route(\"invoices\", {}, { invoice });\n\n  const sales = route(\"sales\", {}, { invoices });\n\n  const home = route(\"/\", {}, { sales }); // root route prefixed with a \"/\"\n\n  // absolute routes:\n  home({}).sales({}).invoices({}).invoice({invoiceId: 1234}).$ // => \"/sales/invoices/1234\"\n  home({}).sales({}).invoices({}).$ // => \"/sales/invoices\"\n  home({}).sales({}).$ // => \"/sales\"\n  home({}).$ // => \"/\"\n\n  // relative routes\n  sales({}).invoices({}).invoice({invoiceId: 5678}).$ // => \"sales/invoices/5678\"\n  invoices({}).invoice({invoiceId: 8765}).$ // => \"invoices/8765\"\n  invoice({invoiceId: 4321}).$ // => \"4321\"\n\n  ```\n</details>\n\n<details>\n  <summary>Optional Parameters</summary>\n\n  Parameters can be suffixed with a question mark (?) to make a parameter optional.\n\n  ``` ts\n  import { route, intParser } from \"typesafe-routes\";\n\n  const userRoute = route(\"/user/:userId/:groupId?\", {\n    userId: intParser,\n    groupId: intParser // parser is required also required for optional parameters\n  }, {});\n\n  userRoute({ userId: 342 }).$ // groupId is optional\n  // => \"/user/342\"\n  userRoute({ userId: 5453, groupId: 5464 }).$\n  // => \"/user/5453/5464\"\n  userRoute({ groupId: 464 }).$\n  // => error because userId is missing\n\n  // parsing:\n  userRoute.parseParams({ userId: \"65\", groupId: \"212\" });\n  // returns { userId: 6, groupId: 12 }\n  ```\n\n</details>\n\n<details>\n  <summary>Query Parameters</summary>\n\n  Parameters can be prefixed with `&` to make the parameter a query parameter.\n\n  ``` ts\n  import { route, intParser } from \"typesafe-routes\";\n\n  const usersRoute = route(\"/users&:start&:limit\", {\n    start: intParser,\n    limit: intParser,\n  }, {});\n\n  usersRoute({ start: 10, limit: 20 }).$\n  // returns \"/users?start=10&limit=20\"\n  ```\n\n  When serialising nested routes the query params of a parent route are always being appended to the end of the locator string.\n\n  ``` ts\n  import { route, intParser } from \"typesafe-routes\";\n\n  const settingsRoute = route(\"/settings&:expertMode\", {\n    expertMode: booleanParser,\n  }, {});\n\n  const usersRoute = route(\"/users&:start&:limit\", {\n    start: intParser,\n    limit: intParser,\n  }, {\n    settingsRoute\n  });\n\n  usersRoute({ start: 10, limit: 20 }).settingsRoute({ expertMode: true })$\n  // returns \"/users/settings?expertMode=true&start=10&limit=20\"\n\n  userRoute.parseParams({ start: \"10\", limit: \"20\", expertMode: \"false\" });\n  // returns { start: 10, limit: 20, expertMode: false }\n  ```\n\n</details>\n\n<details>\n  <summary>Parsers &amp; Serializers</summary>\n\n  If you need to parse/serialize other datatypes than primitive types or dates or the build-in parsers don't meet your requirements for some reason you can create your own parsers with a few lines of code. The `Parser<T>` interface that helps yo to achieve that is defined as followed:\n\n  ``` ts\n  interface Parser<T> {\n    parse: (s: string) => T;\n    serialize: (x: T) => string;\n  }\n  ```\n\n  The next example shows the implementation and usage of a typesafe `Vector2D` parser/serializer.\n\n  ``` ts\n  import { Parser, route } from \"typesafe-routes\";\n\n  interface Vector2D {\n    x: number;\n    y: number;\n  };\n\n  const vectorParser: Parser<Vector2D> = {\n    serialize: (v) => btoa(JSON.stringify(v)),\n    parse: (s) => JSON.parse(atob(s)),\n  };\n\n  const mapRoute = route(\"/map&:pos\", { pos: vectorParser }, {});\n\n  mapRoute({ pos: { x: 1, y: 0 }}).$;\n  // returns \"/map?pos=eyJ4IjoxLCJ5IjowfQ%3D%3D\"\n\n  vectorParser.parseParams({pos: \"eyJ4IjoxLCJ5IjowfQ==\"})\n  // returns { pos: { x: 1, y: 0 }}\n  ```\n\n</details>\n\n<details>\n  <summary>React Router Utilities</summary>\n\n  #### `useRouteParams(route: RouteNode)`\n\n  Internally `useRouteParams` depends on `useParams` that will be imported from the optional dependency `react-router-dom`. However unlike `useParams` the `useRouteParams` function is able to parse query strings by utilising [`qs`](github.com/ljharb/qs).\n\n  ``` ts\n  import { route, useRouteParams } from \"typesafe-routes\";\n\n  const topicRoute = route(\"/:topicId&:limit?\", {\n    topicId: stringParser,\n    limit: floatParser,\n  }, {});\n\n  const Component = () => {\n    const { topicId, limit } = useRouteParams(topicRoute);\n\n    return <>{...}</>;\n  }\n  ```\n\n  #### `<Link>` and `<NavLink>`\n\n  Same as the original `<Link>` and `<NavLink>` from `react-router-dom` but require the `to` property to be a route:\n\n  ``` ts\n  import { route, Link, NavLink } from \"typesafe-routes\";\n\n  const topicRoute = route(\"/topic\", {}, {});\n\n  <Link to={topicRoute({})}>Topic</Link>\n  <NavLink to={topicRoute({})}>Topic</NavLink>\n\n  <Link to=\"/topic\">Topic</Link> // error \"to\" prop can't be string \n  <NavLink to=\"/topic\">Topic</NavLink> // error \"to\" prop can't be string \n  ```\n\n  #### `template`\n\n  `typesafe-routes` implements a subset of template syntax of `react-router` and thus is compatible with it. But since specifying additional query params would break the compatibility (`react-router` doesn't understand the `&` prefix) the `.template` property doesn't contain any of such parameters and can be used to define router in your `react-router` app:\n\n  ``` ts\n  import { route } from \"typesafe-routes\";\n\n  const topicRoute = route(\"/:topicId&:limit?\", {\n    topicId: stringParser,\n    limit: floatParser,\n  }, {});\n\n  <Route path={topicRoute.template}> // template only contains the \"/:topicId\" path\n    <Topic />\n  </Route>\n  ```\n\n</details>\n\n---\n\n## Developer Fuel\n\nYou can have some impact and improve the quality of this project not only by opening issues and opening PRs but also by buying me a cup of fresh coffee as a small reward for my effort. ¡Gracias!\n\n<a href=\"https://www.buymeacoffee.com/kruschid\" target=\"_blank\"><img width=\"200px\" src=\"https://cdn.buymeacoffee.com/buttons/v2/default-orange.png\" alt=\"Buy Me A Coffee\" ></a>\n\n## Roadmap\n\nSo far I consider this library feature-complete that's why I will be mainly concerned about fixing bugs and improving the API. However, if some high demand for additional functionality or PRs shows up I might be considering expanding the scope.\n","readmeFilename":"README.md"}