itty-router 3.0.2 → 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
 
@@ -13,17 +13,41 @@
13
13
 
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
+ # Major Announcement: v3.x is Live!
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.
18
+
19
+ ### Increase in bundle size (~250 bytes)
20
+ This was sadly overdue (and hopefully can be golfed down a bit), but as a result addressed the following issues from v3.x:
21
+
22
+ 1 . Routes can now capture complex/unknown paths using the trailing `+` modifier. As a result, this is now possible:
23
+ ```js
24
+ router.handle('/get-file/:path+', ({ params }) => params)
25
+
26
+ // GET /get-file/with/a/long/path.png => { path: "with/a/long/path.png" }
27
+ ```
28
+
29
+ 2. Query params with multiple same-name values now operate as you would expect (previously, they overwrote each other)
30
+ ```js
31
+ router.handle('/foo', ({ query }) => query)
32
+
33
+ // GET /foo?pets=mittens&pets=fluffy&pets=rex&bar=baz => { bar: "baz", pets: ["mittens", "fluffy", "rex"] }
34
+ ```
35
+
36
+ ### Breaking TS changes
37
+ I've been forced to rewrite the TS types. This will need a bit of documentation...
38
+
16
39
  ### Addons & Related Libraries
17
40
  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)!
18
41
  2. [itty-cors](https://www.npmjs.com/package/itty-cors) (early access/alpha) - Easy CORS handling for itty APIs.
19
42
  2. [itty-durable](https://www.npmjs.com/package/itty-durable) - creates a more direct object-like API for interacting with [Cloudflare Durable Objects](https://developers.cloudflare.com/workers/learning/using-durable-objects).
20
43
 
21
44
  ## Features
22
- - [x] Tiny ([~500 bytes](https://bundlephobia.com/package/itty-router) compressed), with zero dependencies.
45
+ - [x] Tiny ([~780 bytes](https://bundlephobia.com/package/itty-router) compressed), with zero dependencies.
23
46
  - [x] [Fully typed/TypeScript support](#typescript)
24
47
  - [x] Supports sync/async handlers/middleware.
25
48
  - [x] Parses route params, with wildcards and optionals (e.g. `/api/:collection/:id?`)
26
- - [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`)
27
51
  - [x] [Middleware support](#middleware). Any number of sync/async handlers may be passed to a route.
28
52
  - [x] [Nestable](#nested-routers-with-404-handling). Supports nesting routers for API branching.
29
53
  - [x] [Base path](#nested-routers-with-404-handling) for prefixing all routes.
@@ -128,14 +152,15 @@ GET /todos/jane
128
152
  query: {}
129
153
  }
130
154
 
131
- GET /todos/jane?limit=2&page=1
155
+ GET /todos/jane?limit=2&page=1&foo=bar&foo=baz
132
156
  {
133
157
  params: {
134
158
  user: 'jane'
135
159
  },
136
160
  query: {
137
161
  limit: '2',
138
- page: '2'
162
+ page: '2',
163
+ foo: ['bar', 'baz],
139
164
  }
140
165
  }
141
166
  */
@@ -292,16 +317,16 @@ export default {
292
317
 
293
318
  // alternative advanced/manual approach for downstream control
294
319
  export default {
295
- fetch: (...args) => router
296
- .handle(...args)
297
- .then(response => {
298
- // can modify response here before final return, e.g. CORS headers
299
-
300
- return response
301
- })
302
- .catch(err => {
303
- // and do something with the errors here, like logging, error status, etc
304
- })
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
+ })
305
330
  }
306
331
  ```
307
332
 
@@ -390,104 +415,49 @@ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a123' })
390
415
 
391
416
  ### Typescript
392
417
 
393
- 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.
394
419
 
395
420
  ```ts
396
- import { Router, Route, Request } from 'itty-router'
397
-
398
- type MethodType = 'GET' | 'POST' | 'PUPPY'
399
-
400
- interface IRequest extends Request {
401
- method: MethodType // method is required to be on the interface
402
- url: string // url is required to be on the interface
403
- 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,
404
433
  }
405
434
 
406
- interface IMethods {
407
- get: Route
408
- post: Route
409
- 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']
410
443
  }
411
444
 
412
- const router = Router<IRequest, IMethods>()
445
+ const router = <CustomRouter>Router({ base: '/' })
413
446
 
414
- router.get('/', (request: IRequest) => {})
415
- router.post('/', (request: IRequest) => {})
416
- 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
+ })
417
455
 
418
456
  addEventListener('fetch', (event: FetchEvent) => {
419
457
  event.respondWith(router.handle(event.request))
420
458
  })
421
459
  ```
422
460
 
423
- Both generics are optional. `TRequest` defaults to `Request` and `TMethods` defaults to `{}`.
424
-
425
- ```ts
426
- import { Router, Route } from 'itty-router'
427
-
428
- type MethodType = 'GET' | 'POST' | 'PUPPY'
429
-
430
- interface IRequest extends Request {
431
- method: MethodType
432
- url: string
433
- optional?: string
434
- }
435
-
436
- interface IMethods {
437
- get: Route
438
- post: Route
439
- puppy: Route
440
- }
441
-
442
- const router = Router() // Valid
443
- const router = Router<IRequest>() // Valid
444
- const router = Router<Request, IMethods>() // Valid
445
- const router = Router<void, IMethods>() // Valid
446
- ```
447
-
448
- The router will also accept any string as a method, not just those provided on the `TMethods` type.
449
-
450
- ```ts
451
- import { Router, Route } from 'itty-router'
452
-
453
- interface IMethods {
454
- get: Route
455
- post: Route
456
- puppy: Route
457
- }
458
-
459
- const router = Router<void, IMethods>()
460
-
461
- router.puppy('/', request => {}) // Valid
462
- router.kitten('/', request => {}) // Also Valid
463
- ```
464
-
465
- The `itty-router` package also exports an interface containing all of the HTTP methods.
466
-
467
- ```ts
468
- import { Router, Route, IHTTPMethods } from 'itty-router'
469
-
470
- const router = Router<void, IHTTPMethods>()
471
-
472
- router.get('/', request => {}) // Exposed via IHTTPMethods
473
- router.puppy('/', request => {}) // Valid but not strongly typed
474
- ```
475
-
476
- You can also extend `IHTTPMethods` with your own custom methods so they will be strongly typed.
477
-
478
- ```ts
479
- import { Router, Route, IHTTPMethods } from 'itty-router'
480
-
481
- interface IMethods extends IHTTPMethods {
482
- puppy: Route
483
- }
484
-
485
- const router = Router<void, IMethods>()
486
-
487
- router.get('/', request => {}) // Exposed via IHTTPMethods
488
- router.puppy('/', request => {}) // Strongly typed
489
- ```
490
-
491
461
  ## Testing and Contributing
492
462
  1. Fork repo
493
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.2",
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",