itty-router 3.0.3 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  [![Version](https://img.shields.io/npm/v/itty-router.svg?style=flat-square)](https://npmjs.com/package/itty-router)
4
4
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/itty-router?style=flat-square)](https://bundlephobia.com/result?p=itty-router)
5
- [![Build Status](https://img.shields.io/github/actions/workflow/status/kwhitley/itty-router/verify.yml?branch=v2.x&style=flat-square)](https://github.com/kwhitley/itty-router/actions/workflows/verify.yml)
6
- [![Coverage Status](https://img.shields.io/coveralls/github/kwhitley/itty-router/v2.x?style=flat-square)](https://coveralls.io/github/kwhitley/itty-router?branch=v2.x)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/kwhitley/itty-router/verify.yml?branch=v3.x&style=flat-square)](https://github.com/kwhitley/itty-router/actions/workflows/verify.yml)
6
+ [![Coverage Status](https://img.shields.io/coveralls/github/kwhitley/itty-router/v3.x?style=flat-square)](https://coveralls.io/github/kwhitley/itty-router?branch=v3.x)
7
7
  [![NPM Weekly Downloads](https://img.shields.io/npm/dw/itty-router?style=flat-square)](https://npmjs.com/package/itty-router)
8
8
  [![Open Issues](https://img.shields.io/github/issues/kwhitley/itty-router?style=flat-square)](https://github.com/kwhitley/itty-router/issues)
9
9
 
@@ -14,12 +14,10 @@
14
14
  Tiny, zero-dependency router with route param and query parsing - built for [Cloudflare Workers](https://developers.cloudflare.com/workers/), but works everywhere!
15
15
 
16
16
  # Major Announcement: v3.x is Live!
17
- Due to an NPM hiccup, `3.0.0` went live early (instead of staying on `next`). The immediate NPM unpublish (normally fine) was rejected, so rather than deprecate the version (super dirty), it's going to stay live on the main v3 path, and we'll smoke test the issues to address them rapidly. Please [join the discussion on Discord](https://discord.gg/53vyrZAu9u) to assist in this rollout! In the meantime, thanks everyone for your patience!
18
-
19
- This comes with a couple major changes, and is now a TS-first lib.
17
+ Version 3 introduces itty as a TypeScript-first library. This version should break no existing JS users, but TS users may have to update their types, as shown below. Please [join the discussion on Discord](https://discord.gg/53vyrZAu9u) to assist in this rollout! In the meantime, thanks everyone for your patience! Here are the major changes in version 3, with `itty-router-extras` (certainly) and likely `itty-cors` to be added into core as upcoming minor releases.
20
18
 
21
19
  ### Increase in bundle size (~250 bytes)
22
- This was sadly overdue (and hopefully can be golfed down a bit), but as a result addressed the following issues from v2.x:
20
+ This was sadly overdue (and hopefully can be golfed down a bit), but as a result addressed the following issues from v3.x:
23
21
 
24
22
  1 . Routes can now capture complex/unknown paths using the trailing `+` modifier. As a result, this is now possible:
25
23
  ```js
@@ -48,7 +46,8 @@ I've been forced to rewrite the TS types. This will need a bit of documentation
48
46
  - [x] [Fully typed/TypeScript support](#typescript)
49
47
  - [x] Supports sync/async handlers/middleware.
50
48
  - [x] Parses route params, with wildcards and optionals (e.g. `/api/:collection/:id?`)
51
- - [x] Query parsing (e.g. `?page=3&foo=bar`)
49
+ - [x] ["Greedy" route captures](#greedy) (e.g. `/api/:path+`)
50
+ - [x] Query parsing (e.g. `?page=3&foo=bar&foo=baz`)
52
51
  - [x] [Middleware support](#middleware). Any number of sync/async handlers may be passed to a route.
53
52
  - [x] [Nestable](#nested-routers-with-404-handling). Supports nesting routers for API branching.
54
53
  - [x] [Base path](#nested-routers-with-404-handling) for prefixing all routes.
@@ -153,14 +152,15 @@ GET /todos/jane
153
152
  query: {}
154
153
  }
155
154
 
156
- GET /todos/jane?limit=2&page=1
155
+ GET /todos/jane?limit=2&page=1&foo=bar&foo=baz
157
156
  {
158
157
  params: {
159
158
  user: 'jane'
160
159
  },
161
160
  query: {
162
161
  limit: '2',
163
- page: '2'
162
+ page: '2',
163
+ foo: ['bar', 'baz],
164
164
  }
165
165
  }
166
166
  */
@@ -317,16 +317,16 @@ export default {
317
317
 
318
318
  // alternative advanced/manual approach for downstream control
319
319
  export default {
320
- fetch: (...args) => router
321
- .handle(...args)
322
- .then(response => {
323
- // can modify response here before final return, e.g. CORS headers
324
-
325
- return response
326
- })
327
- .catch(err => {
328
- // and do something with the errors here, like logging, error status, etc
329
- })
320
+ fetch: (request, env, context) => router
321
+ .handle(request, env, context)
322
+ .then(response => {
323
+ // can modify response here before final return, e.g. CORS headers
324
+
325
+ return response
326
+ })
327
+ .catch(err => {
328
+ // and do something with the errors here, like logging, error status, etc
329
+ })
330
330
  }
331
331
  ```
332
332
 
@@ -415,104 +415,49 @@ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a123' })
415
415
 
416
416
  ### Typescript
417
417
 
418
- For Typescript projects, the Router can be adorned with two generics: A custom request interface and a custom methods interface.
418
+ As of version `3.x`, itty-router is TypeScript-first, meaning it has full hinting out of the box.
419
419
 
420
420
  ```ts
421
- import { Router, Route, Request } from 'itty-router'
422
-
423
- type MethodType = 'GET' | 'POST' | 'PUPPY'
424
-
425
- interface IRequest extends Request {
426
- method: MethodType // method is required to be on the interface
427
- url: string // url is required to be on the interface
428
- optional?: string
421
+ import {
422
+ Router, // the router itself
423
+ IRequest, // lightweight/generic Request type
424
+ RouterType, // generic Router type
425
+ Route, // generic Route type
426
+ } from './itty-router'
427
+
428
+ // declare a custom Router type with used methods
429
+ interface CustomRouter extends RouterType {
430
+ all: Route,
431
+ get: Route,
432
+ puppy: Route,
429
433
  }
430
434
 
431
- interface IMethods {
432
- get: Route
433
- post: Route
434
- puppy: Route
435
+ // declare a custom Request type to allow request injection from middleware
436
+ type RequestWithAuthors = {
437
+ authors?: string[]
438
+ } & IRequest
439
+
440
+ // middleware that modifies the request
441
+ const withAuthors = (request: IRequest) => {
442
+ request.authors = ['foo', 'bar']
435
443
  }
436
444
 
437
- const router = Router<IRequest, IMethods>()
445
+ const router = <CustomRouter>Router({ base: '/' })
438
446
 
439
- router.get('/', (request: IRequest) => {})
440
- router.post('/', (request: IRequest) => {})
441
- router.puppy('/', (request: IRequest) => {})
447
+ router
448
+ .all<CustomRouter>('*', () => {})
449
+ .get<CustomRouter>('/authors', withAuthors, (request: RequestWithAuthors) => {
450
+ return request.authors?.[0]
451
+ })
452
+ .puppy('*', (request) => {
453
+ const foo = request.query.foo
454
+ })
442
455
 
443
456
  addEventListener('fetch', (event: FetchEvent) => {
444
457
  event.respondWith(router.handle(event.request))
445
458
  })
446
459
  ```
447
460
 
448
- Both generics are optional. `TRequest` defaults to `Request` and `TMethods` defaults to `{}`.
449
-
450
- ```ts
451
- import { Router, Route } from 'itty-router'
452
-
453
- type MethodType = 'GET' | 'POST' | 'PUPPY'
454
-
455
- interface IRequest extends Request {
456
- method: MethodType
457
- url: string
458
- optional?: string
459
- }
460
-
461
- interface IMethods {
462
- get: Route
463
- post: Route
464
- puppy: Route
465
- }
466
-
467
- const router = Router() // Valid
468
- const router = Router<IRequest>() // Valid
469
- const router = Router<Request, IMethods>() // Valid
470
- const router = Router<void, IMethods>() // Valid
471
- ```
472
-
473
- The router will also accept any string as a method, not just those provided on the `TMethods` type.
474
-
475
- ```ts
476
- import { Router, Route } from 'itty-router'
477
-
478
- interface IMethods {
479
- get: Route
480
- post: Route
481
- puppy: Route
482
- }
483
-
484
- const router = Router<void, IMethods>()
485
-
486
- router.puppy('/', request => {}) // Valid
487
- router.kitten('/', request => {}) // Also Valid
488
- ```
489
-
490
- The `itty-router` package also exports an interface containing all of the HTTP methods.
491
-
492
- ```ts
493
- import { Router, Route, IHTTPMethods } from 'itty-router'
494
-
495
- const router = Router<void, IHTTPMethods>()
496
-
497
- router.get('/', request => {}) // Exposed via IHTTPMethods
498
- router.puppy('/', request => {}) // Valid but not strongly typed
499
- ```
500
-
501
- You can also extend `IHTTPMethods` with your own custom methods so they will be strongly typed.
502
-
503
- ```ts
504
- import { Router, Route, IHTTPMethods } from 'itty-router'
505
-
506
- interface IMethods extends IHTTPMethods {
507
- puppy: Route
508
- }
509
-
510
- const router = Router<void, IMethods>()
511
-
512
- router.get('/', request => {}) // Exposed via IHTTPMethods
513
- router.puppy('/', request => {}) // Strongly typed
514
- ```
515
-
516
461
  ## Testing and Contributing
517
462
  1. Fork repo
518
463
  1. Install dev dependencies via `yarn`
@@ -1,20 +1,22 @@
1
- declare type RequestTraps = {
1
+ declare type GenericTraps = {
2
2
  [key: string]: any;
3
3
  };
4
- declare type RequestLike = {
4
+ declare type IRequest = {
5
5
  method: string;
6
6
  url: string;
7
- } & RequestTraps;
7
+ params: GenericTraps;
8
+ query: GenericTraps;
9
+ } & GenericTraps;
8
10
  interface RouterOptions {
9
11
  base?: string;
10
12
  routes?: RouteEntry[];
11
13
  }
12
14
  interface RouteHandler {
13
- (request: RequestLike, ...args: any): any;
15
+ (request: IRequest, ...args: any): any;
14
16
  }
15
17
  declare type RouteEntry = [string, RegExp, RouteHandler[]];
16
- declare type Route = (path: string, ...handlers: RouteHandler[]) => RouterType;
17
- declare type RouterTraps = {
18
+ declare type Route = <T extends RouterType>(path: string, ...handlers: RouteHandler[]) => T;
19
+ declare type RouterHints = {
18
20
  all?: Route;
19
21
  delete?: Route;
20
22
  get?: Route;
@@ -26,8 +28,8 @@ declare type RouterTraps = {
26
28
  declare type RouterType = {
27
29
  __proto__: RouterType;
28
30
  routes: RouteEntry[];
29
- handle: (request: RequestLike, ...extra: any) => Promise<any>;
30
- } & RouterTraps;
31
+ handle: (request: IRequest, ...extra: any) => Promise<any>;
32
+ } & RouterHints;
31
33
  declare const Router: ({ base, routes }?: RouterOptions) => RouterType;
32
34
 
33
- export { RequestLike, RequestTraps, Route, RouteEntry, RouteHandler, Router, RouterOptions, RouterTraps, RouterType };
35
+ export { GenericTraps, IRequest, Route, RouteEntry, RouteHandler, Router, RouterHints, RouterOptions, RouterType };
@@ -1 +1 @@
1
- var u=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var h=(r,e)=>{for(var t in e)u(r,t,{get:e[t],enumerable:!0})},x=(r,e,t,p)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of d(e))!g.call(r,o)&&o!==t&&u(r,o,{get:()=>e[o],enumerable:!(p=i(e,o))||p.enumerable});return r};var f=r=>x(u({},"__esModule",{value:!0}),r);var T={};h(T,{Router:()=>m});module.exports=f(T);var $=r=>[...r.entries()].reduce((e,[t,p])=>(e[t]===void 0?e[t]=p:e[t]=[e[t],p].flat())&&e||e,{}),m=({base:r="",routes:e=[]}={})=>({__proto__:new Proxy({},{get:(t,p,o)=>(a,...n)=>e.push([p.toUpperCase(),RegExp(`^${(r+a).replace(/(\/?)\*/g,"($1.*)?").replace(/(\/$)|((?<=\/)\/)/,"").replace(/(:(\w+)\+)/,"(?<$2>.*)").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.").replace(/\)\.\?\(([^\[]+)\[\^/g,"?)\\.?($1(?<=\\.)[^\\.")}/*$`),n])&&o}),routes:e,async handle(t,...p){let o,a,n=new URL(t.url);t.query=$(n.searchParams);for(let[s,R,l]of e)if((s===t.method||s==="ALL")&&(a=n.pathname.match(R))){t.params=a.groups;for(let y of l)if((o=await y(t.proxy||t,...p))!==void 0)return o}}});0&&(module.exports={Router});
1
+ "use strict";var u=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var h=(r,e)=>{for(var t in e)u(r,t,{get:e[t],enumerable:!0})},x=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of d(e))!g.call(r,o)&&o!==t&&u(r,o,{get:()=>e[o],enumerable:!(n=l(e,o))||n.enumerable});return r};var f=r=>x(u({},"__esModule",{value:!0}),r);var m={};h(m,{Router:()=>c});module.exports=f(m);var T=r=>[...r.entries()].reduce((e,[t,n])=>(e[t]===void 0?e[t]=n:e[t]=[e[t],n].flat())&&e||e,{}),c=({base:r="",routes:e=[]}={})=>({__proto__:new Proxy({},{get:(t,n,o)=>(p,...a)=>e.push([n.toUpperCase(),RegExp(`^${(r+p).replace(/(\/?)\*/g,"($1.*)?").replace(/(\/$)|((?<=\/)\/)/,"").replace(/(:(\w+)\+)/,"(?<$2>.*)").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.").replace(/\)\.\?\(([^\[]+)\[\^/g,"?)\\.?($1(?<=\\.)[^\\.")}/*$`),a])&&o}),routes:e,async handle(t,...n){let o,p,a=new URL(t.url);t.query=T(a.searchParams);for(let[s,R,y]of e)if((s===t.method||s==="ALL")&&(p=a.pathname.match(R))){t.params=p.groups||{};for(let i of y)if((o=await i(t.proxy||t,...n))!==void 0)return o}}});0&&(module.exports={Router});
@@ -1 +1 @@
1
- var y=n=>[...n.entries()].reduce((t,[e,r])=>(t[e]===void 0?t[e]=r:t[e]=[t[e],r].flat())&&t||t,{}),i=({base:n="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(e,r,o)=>(p,...a)=>t.push([r.toUpperCase(),RegExp(`^${(n+p).replace(/(\/?)\*/g,"($1.*)?").replace(/(\/$)|((?<=\/)\/)/,"").replace(/(:(\w+)\+)/,"(?<$2>.*)").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.").replace(/\)\.\?\(([^\[]+)\[\^/g,"?)\\.?($1(?<=\\.)[^\\.")}/*$`),a])&&o}),routes:t,async handle(e,...r){let o,p,a=new URL(e.url);e.query=y(a.searchParams);for(let[u,s,R]of t)if((u===e.method||u==="ALL")&&(p=a.pathname.match(s))){e.params=p.groups;for(let l of R)if((o=await l(e.proxy||e,...r))!==void 0)return o}}});export{i as Router};
1
+ var i=a=>[...a.entries()].reduce((t,[e,r])=>(t[e]===void 0?t[e]=r:t[e]=[t[e],r].flat())&&t||t,{}),l=({base:a="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(e,r,o)=>(n,...p)=>t.push([r.toUpperCase(),RegExp(`^${(a+n).replace(/(\/?)\*/g,"($1.*)?").replace(/(\/$)|((?<=\/)\/)/,"").replace(/(:(\w+)\+)/,"(?<$2>.*)").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.").replace(/\)\.\?\(([^\[]+)\[\^/g,"?)\\.?($1(?<=\\.)[^\\.")}/*$`),p])&&o}),routes:t,async handle(e,...r){let o,n,p=new URL(e.url);e.query=i(p.searchParams);for(let[u,s,R]of t)if((u===e.method||u==="ALL")&&(n=p.pathname.match(s))){e.params=n.groups||{};for(let y of R)if((o=await y(e.proxy||e,...r))!==void 0)return o}}});export{l as Router};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itty-router",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Tiny, zero-dependency API router - built for Cloudflare Workers, but works everywhere!",
5
5
  "sourceType": "module",
6
6
  "main": "./dist/itty-router.js",