# Typed Route Config
Type safe configuration for routes.

# Installation
```bash
npm install typed-route-config
```

# The Problem

If you use react-router, you might want to centralise your routes in some kind of configuration object, which can then be looked up in all the places you use them.

For example, you may have:

```tsx
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'

export const App: React.FC = () => (
    <BrowserRouter>
        <Switch>
            <Route path={'/dashboard/me'} render={() => <div>the dashboard profile component here...</div>}/>
            <Route path={'/dashboard/offer/:offerId/documents'} render={() => <div>the documents component here...</div>}/>
        </Switch>
    </BrowserRouter>
)

type LinkProps = {
    offerId: string
}

export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => (
    <Link to={`/dashboard/offer/${offerId}/documents`} />
)
```

So, suppose you to factor these routes out into a configuration that looked like this:

```ts
// myRoutes.ts
export const mySimpleRouteConfig = {
    'dashboard':                        '/dashboard',
    'dashboard.me':                     '/dashboard/me',

    'dashboard.offer':                  '/dashboard/offer/:offerId',
    'dashboard.offer.collaborators':    '/dashboard/offer/:offerId/collaborators',
    'dashboard.offer.documents':        '/dashboard/offer/:offerId/documents',
}
```

Which would then look something like this:

```tsx
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
import { mySimpleRouteConfig } from './myRoutes'

export const App: React.FC = () => (
    <BrowserRouter>
        <Switch>
            <Route path={mySimpleRouteConfig['dashboard.me']} render={() => <div>the dashboard profile component here...</div>}/>
            <Route path={mySimpleRouteConfig['dashboard.offer.documents']} render={() => <div>the documents component here...</div>}/>
        </Switch>
    </BrowserRouter>
)

type LinkProps = {
    offerId: string
}

export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => {
    const documentsPath = mySimpleRouteConfig['dashboard.offer.documents'] // '/dashboard/offer/:offerId/documents'
    
    // interpolate the offerId into documentsPath...
    const documentsUrlForThisOfferId = interpolate(documentsPath, { offerId })
    return (
        <Link to={documentsUrlForThisOfferId} />
    )
}
```

The issue here is you have no typesafety with how you've implented the `interpolate` function!

# The Solution

Using `typed-route-config` could re-write your config as:

```ts
// myRoutes.ts
import { createRoutes } from 'typed-route-config';

export const { route, path } = createRoutes(root => ({
    'dashboard':                        root.path('dashboard'),
    'dashboard.me':                     root.path('dashboard/me'),

    'dashboard.offer':                  root.path('dashboard/offer').param('offerId'),
    'dashboard.offer.collaborators':    root.path('dashboard/offer').param('offerId').path('collaborators'),
    'dashboard.offer.documents':        root.path('dashboard/offer').param('offerId').path('documents')
}))
```

And then continuing with our example, we would then get

```tsx
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
import { route, path } from './myRoutes'

export const App: React.FC = () => {

    // const fakePath = path('fake.route')
    // Argument of type '"fake.route"' is not assignable to parameter of type '"dashboard" | "dashboard.me" | ... | "dashboard.offer.documents"

    // path() will only let you use keys from the config! It returns the un-interpolated version of that route
    // e.g. path('dashboard.offer.documents') -> 'dashboard/offer/:offerId/documents'
    return (
        <BrowserRouter>
            <Switch>
                <Route path={path('dashboard.me')} render={() => <div>the dashboard profile component here...</div>}/>
                <Route path={path('dashboard.offer.documents')} render={() => <div>the documents component here...</div>}/>
            </Switch>
        </BrowserRouter>
    )
}

type LinkProps = {
    offerId: string
}

export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => {
    // route() interpolates the offerId into path for that route, but most importantly in a typesafe way
    const documentsUrlForThisOfferId = route('dashboard.offer.documents', { offerId })

    // const error = route('dashboard.offer.documents', { monkeyId: 'abc' })
    // Argument of type '{ monkeyId: string; }' is not assignable to parameter of type '{ offerId: string; }'

    return (
        <Link to={documentsUrlForThisOfferId} />
    )
}

```

We can go a step further and neaten up our route config to remove the repetition.
You can use the `.group(...)` to nest routes. The config from before could be re-written as:

```ts
import { createRoutes } from 'typed-route-config';

export const { routes, route, path } = createRoutes(root => ({
    'dashboard': root.path('dashboard').group(dashboard => ({
        // '' within a group gives us a "root" route with the name of the group i.e. 'dashboard'
        '': dashboard,

        // joins the group name to this route name i.e. 'dashboard.me'
        'me': dashboard.path('me'),

        // and you can nest groups
        'offer': dashboard.path('offer').param('offerId').group(offer => ({
            '':                 offer,                       // 'dashboard.offer'
            'collaborators':    offer.path('collaborators'), // 'dashboard.offer.collaborators'
            'documents':        offer.path('documents')      // 'dashboard.offer.documents'
        }))
    })),
}))
```

# Usage

Define your routes somewhere in your application using the `createRoutes` function:
```ts
// routes.ts
import { createRoutes, MakeRouteParams } from 'typed-route-config'

const { routes, route, path } = createRoutes(root => ({
    // ... routes go here
}))

// only export the functions you need, there are more explained later not shown in this basic example
// you probably don't want to export 'routes' as it is just used to generate the helper types below
export { route, path }

type Routes = typeof routes

// union type of every routeName
export type RouteNames = keyof Routes

// gets the route params object for the given routeName
export type RouteParams<N extends RouteNames> = MakeRouteParams<Routes, N>
```

TODO explain the rest of the API

# Building from source
To build, just run `npm run build`

# To make a change
Make your change, (don't forget to `npm run build`), then increment the version number in the package.json before pushing.

In the projects which depend on this package, run `npm update typed-route-config`. 

npm detects changes using the version number in the package.json
