itty-router 3.0.3 → 3.0.5

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,13 +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!
17
+ Version 3 introduces itty as a TypeScript-first library. This version should break no existing JS users, but TS users will likely need to [update their types](#typescript). Please [join the discussion on Discord](https://discord.gg/53vyrZAu9u) to assist in this rollout! In the meantime, thanks everyone for your patience!
18
18
 
19
- This comes with a couple major changes, and is now a TS-first lib.
19
+ 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
20
 
21
- ### 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:
23
-
24
21
  1 . Routes can now capture complex/unknown paths using the trailing `+` modifier. As a result, this is now possible:
25
22
  ```js
26
23
  router.handle('/get-file/:path+', ({ params }) => params)
@@ -35,9 +32,6 @@ This was sadly overdue (and hopefully can be golfed down a bit), but as a result
35
32
  // GET /foo?pets=mittens&pets=fluffy&pets=rex&bar=baz => { bar: "baz", pets: ["mittens", "fluffy", "rex"] }
36
33
  ```
37
34
 
38
- ### Breaking TS changes
39
- I've been forced to rewrite the TS types. This will need a bit of documentation...
40
-
41
35
  ### Addons & Related Libraries
42
36
  1. [itty-router-extras](https://www.npmjs.com/package/itty-router-extras) - adds quality-of-life improvements and utility functions for simplifying request/response code (e.g. middleware, cookies, body parsing, json handling, errors, and an itty version with automatic exception handling)!
43
37
  2. [itty-cors](https://www.npmjs.com/package/itty-cors) (early access/alpha) - Easy CORS handling for itty APIs.
@@ -48,7 +42,8 @@ I've been forced to rewrite the TS types. This will need a bit of documentation
48
42
  - [x] [Fully typed/TypeScript support](#typescript)
49
43
  - [x] Supports sync/async handlers/middleware.
50
44
  - [x] Parses route params, with wildcards and optionals (e.g. `/api/:collection/:id?`)
51
- - [x] Query parsing (e.g. `?page=3&foo=bar`)
45
+ - [x] ["Greedy" route captures](#greedy) (e.g. `/api/:path+`)
46
+ - [x] Query parsing (e.g. `?page=3&foo=bar&foo=baz`)
52
47
  - [x] [Middleware support](#middleware). Any number of sync/async handlers may be passed to a route.
53
48
  - [x] [Nestable](#nested-routers-with-404-handling). Supports nesting routers for API branching.
54
49
  - [x] [Base path](#nested-routers-with-404-handling) for prefixing all routes.
@@ -153,14 +148,15 @@ GET /todos/jane
153
148
  query: {}
154
149
  }
155
150
 
156
- GET /todos/jane?limit=2&page=1
151
+ GET /todos/jane?limit=2&page=1&foo=bar&foo=baz
157
152
  {
158
153
  params: {
159
154
  user: 'jane'
160
155
  },
161
156
  query: {
162
157
  limit: '2',
163
- page: '2'
158
+ page: '2',
159
+ foo: ['bar', 'baz],
164
160
  }
165
161
  }
166
162
  */
@@ -317,16 +313,16 @@ export default {
317
313
 
318
314
  // alternative advanced/manual approach for downstream control
319
315
  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
- })
316
+ fetch: (request, env, context) => router
317
+ .handle(request, env, context)
318
+ .then(response => {
319
+ // can modify response here before final return, e.g. CORS headers
320
+
321
+ return response
322
+ })
323
+ .catch(err => {
324
+ // and do something with the errors here, like logging, error status, etc
325
+ })
330
326
  }
331
327
  ```
332
328
 
@@ -415,104 +411,49 @@ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a123' })
415
411
 
416
412
  ### Typescript
417
413
 
418
- For Typescript projects, the Router can be adorned with two generics: A custom request interface and a custom methods interface.
414
+ As of version `3.x`, itty-router is TypeScript-first, meaning it has full hinting out of the box.
419
415
 
420
416
  ```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
417
+ import {
418
+ Router, // the router itself
419
+ IRequest, // lightweight/generic Request type
420
+ RouterType, // generic Router type
421
+ Route, // generic Route type
422
+ } from './itty-router'
423
+
424
+ // declare a custom Router type with used methods
425
+ interface CustomRouter extends RouterType {
426
+ all: Route,
427
+ get: Route,
428
+ puppy: Route,
429
429
  }
430
430
 
431
- interface IMethods {
432
- get: Route
433
- post: Route
434
- puppy: Route
431
+ // declare a custom Request type to allow request injection from middleware
432
+ type RequestWithAuthors = {
433
+ authors?: string[]
434
+ } & IRequest
435
+
436
+ // middleware that modifies the request
437
+ const withAuthors = (request: IRequest) => {
438
+ request.authors = ['foo', 'bar']
435
439
  }
436
440
 
437
- const router = Router<IRequest, IMethods>()
441
+ const router = <CustomRouter>Router({ base: '/' })
438
442
 
439
- router.get('/', (request: IRequest) => {})
440
- router.post('/', (request: IRequest) => {})
441
- router.puppy('/', (request: IRequest) => {})
443
+ router
444
+ .all<CustomRouter>('*', () => {})
445
+ .get<CustomRouter>('/authors', withAuthors, (request: RequestWithAuthors) => {
446
+ return request.authors?.[0]
447
+ })
448
+ .puppy('*', (request) => {
449
+ const foo = request.query.foo
450
+ })
442
451
 
443
452
  addEventListener('fetch', (event: FetchEvent) => {
444
453
  event.respondWith(router.handle(event.request))
445
454
  })
446
455
  ```
447
456
 
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
457
  ## Testing and Contributing
517
458
  1. Fork repo
518
459
  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.5",
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",