itty-router 4.0.0-next.49 → 4.0.0-next.52

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
@@ -1,31 +1,69 @@
1
- # [![Itty Router](https://user-images.githubusercontent.com/865416/146679767-16be95b4-5dd7-4bcf-aed7-b8aa8c828f48.png)](https://itty-router.dev)
2
1
 
3
- [![Version](https://img.shields.io/npm/v/itty-router.svg?style=flat-square)](https://npmjs.com/package/itty-router)
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=v4.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/v4.x?style=flat-square)](https://coveralls.io/github/kwhitley/itty-router?branch=v4.x)
7
- [![NPM Weekly Downloads](https://img.shields.io/npm/dw/itty-router?style=flat-square)](https://npmjs.com/package/itty-router)
8
- [![Open Issues](https://img.shields.io/github/issues/kwhitley/itty-router?style=flat-square)](https://github.com/kwhitley/itty-router/issues)
9
2
 
10
- [![Discord](https://img.shields.io/discord/832353585802903572?style=flat-square)](https://discord.com/channels/832353585802903572)
11
- [![GitHub Repo stars](https://img.shields.io/github/stars/kwhitley/itty-router?style=social)](https://github.com/kwhitley/itty-router)
12
- [![Twitter](https://img.shields.io/twitter/follow/kevinrwhitley.svg?style=social&label=Follow)](https://www.twitter.com/kevinrwhitley)
13
-
14
- Itty aims to be the world's smallest (430 bytes), feature-rich JavaScript router, enabling low-calorie code with a low-calorie bundlesize. Designed originally for Cloudflare Workers, itty can be used in browsers, Service Workers, edge functions, or standalone runtimes like Node!
3
+ <p align="center">
4
+ <a href="https://itty.dev/itty-router">
5
+ <img src="https://user-images.githubusercontent.com/865416/146679767-16be95b4-5dd7-4bcf-aed7-b8aa8c828f48.png" alt="Itty Router" />
6
+ </a>
7
+ <p>
8
+
9
+ ---
10
+ <h3 align="center"><a href="https://itty.dev/itty-router">4.x Documentation @ itty.dev</a></h3>
11
+
12
+ <p align="center">
13
+ <a href="https://npmjs.com/package/itty-router" target="_blank">
14
+ <img src="https://img.shields.io/npm/v/itty-router.svg?style=flat-square" alt="npm version" />
15
+ </a>
16
+ <a href="https://bundlephobia.com/result?p=itty-router" target="_blank">
17
+ <img src="https://img.shields.io/bundlephobia/minzip/itty-router?style=flat-square" alt="bundle size" />
18
+ </a>
19
+ <a href="https://github.com/kwhitley/itty-router/actions/workflows/verify.yml" target="_blank">
20
+ <img src="https://img.shields.io/github/actions/workflow/status/kwhitley/itty-router/verify.yml?branch=v4.x&style=flat-square" alt="build status" />
21
+ </a>
22
+ <a href="https://coveralls.io/github/kwhitley/itty-router?branch=v4.x" target="_blank">
23
+ <img src="https://img.shields.io/coveralls/github/kwhitley/itty-router/v4.x?style=flat-square" alt="code coverage" />
24
+ </a>
25
+ <a href="https://npmjs.com/package/itty-router" target="_blank">
26
+ <img src="https://img.shields.io/npm/dw/itty-router?style=flat-square" alt="weekly downloads" />
27
+ </a>
28
+ <a href="https://github.com/kwhitley/itty-router/issues" target="_blank">
29
+ <img src="https://img.shields.io/github/issues/kwhitley/itty-router?style=flat-square" alt="open issues" />
30
+ </a>
31
+ <a href="" target="_blank">
32
+ <img src="" alt="" />
33
+ </a>
34
+ </p>
35
+
36
+ <p align="center">
37
+ <a href="https://discord.com/channels/832353585802903572" target="_blank">
38
+ <img src="https://img.shields.io/discord/832353585802903572?style=flat-square" alt="join us on discord" />
39
+ </a>
40
+ <a href="https://github.com/kwhitley/itty-router" target="_blank">
41
+ <img src="https://img.shields.io/github/stars/kwhitley/itty-router?style=social" alt="repo stars" />
42
+ </a>
43
+ <a href="https://www.twitter.com/kevinrwhitley" target="_blank">
44
+ <img src="https://img.shields.io/twitter/follow/kevinrwhitley.svg?style=social&label=Follow" alt="follow the author" />
45
+ </a>
46
+ <a href="" target="_blank">
47
+ <img src="" alt="" />
48
+ </a>
49
+ </p>
50
+
51
+ ---
52
+
53
+ Itty aims to be the world's smallest (~440 bytes), feature-rich JavaScript router, enabling beautiful API code with a near-zero bundlesize. Designed originally for [Cloudflare Workers](https://itty.dev/itty-router/runtimes#Cloudflare%20Workers), itty can be used in browsers, Service Workers, edge functions, or standalone runtimes like [Node](https://itty.dev/itty-router/runtimes#Node), [Bun](https://itty.dev/itty-router/runtimes#Bun), etc.!
15
54
 
16
55
  ## Features:
56
+ - [fully typed/TypeScript support](https://itty.dev/itty-router#typescript)
17
57
  - [route params](https://itty.dev/itty-router/route-patterns#params) (including [optional params](https://itty.dev/itty-router/route-patterns#optional))
18
58
  - [wildcards](https://itty.dev/itty-router/route-patterns#wildcards) and [greedy params](https://itty.dev/itty-router/route-patterns#greedy)
19
59
  - [query parsing](https://itty.dev/itty-router/route-patterns#query)
20
60
  - [middleware](https://itty.dev/itty-router/middleware)
21
61
  - [router nesting](https://itty.dev/itty-router/nesting)
22
- - any real/fake HTTP method
23
62
  - lightweight, easy-to-understand route code
24
- - works in *any* runtime/environment (including the browser)
25
- - fully typed/TS
63
+ - works in virtually [*any* runtime/environment](https://itty.dev/itty-router/runtimes) (including the browser)
26
64
 
27
65
  ## [Full Documentation](https://itty.dev/itty-router)
28
- Complete docs/API available on [itty.dev](https://itty.dev/itty-router), or join our [Discord](https://discord.com/channels/832353585802903572) channel to chat.
66
+ Complete docs/API are available at [itty.dev](https://itty.dev/itty-router), or join our [Discord](https://discord.com/channels/832353585802903572) channel to chat!
29
67
 
30
68
  ## Installation
31
69
  ```
@@ -35,20 +73,17 @@ npm install itty-router@next
35
73
  ## Example
36
74
  ```js
37
75
  import {
38
- error, // create an error response
39
- respondWithError, // downstream handler for thrown errors
40
- respondWithJSON, // downstream handler to send data as JSON
41
- Router, // the ~470 byte router itself
42
- withParams, // middleware to auto-embed route params
43
- notFound, // create a 404
76
+ error, // creates error responses
77
+ json, // creates json responses
78
+ Router, // the ~440 byte router itself
79
+ withParams, // middleware: puts params directly on the Request
44
80
  } from 'itty-router'
45
81
 
46
82
  const router = Router() // create a new Router
47
83
  const todos = [] // and some fake todos
48
84
 
49
85
  router
50
- // add some global middleware
51
- // withParams auto-parses route params into the request
86
+ // middleware: withParams auto-parses route params into the request
52
87
  .all('*', withParams)
53
88
 
54
89
  // GET list of todos
@@ -57,18 +92,18 @@ router
57
92
  // GET single todo, by ID
58
93
  .get('/todos/:id',
59
94
  ({ id }) => todos.find(todo => todo.id === id)
60
- || notFound('That todo was not found')
95
+ || error(404, 'That todo was not found')
61
96
  )
62
97
 
63
98
  // 404 for everything else
64
- .all('*', notFound)
99
+ .all('*', () => error(404))
65
100
 
66
- // Example: Cloudflare ESM Worker syntax
101
+ // Example: Cloudflare Worker module syntax
67
102
  export default {
68
- fetch: (request, env, context) => router
69
- .handle(request, env, context)
70
- .then(respondWithJSON) // automatically send as JSON
71
- .catch(respondWithError) // and send error Responses for thrown errors
103
+ fetch: (request, ...args) => router
104
+ .handle(request, ...args)
105
+ .then(json) // automatically send as JSON
106
+ .catch(error) // and send error Responses for thrown errors
72
107
  }
73
108
  ```
74
109
 
package/Router.d.ts CHANGED
@@ -5,9 +5,10 @@ export declare type RequestLike = {
5
5
  method: string;
6
6
  url: string;
7
7
  } & GenericTraps;
8
- export declare type IRequest = {
8
+ export declare type IRequestStrict = {
9
9
  method: string;
10
10
  url: string;
11
+ route: string;
11
12
  params: {
12
13
  [key: string]: string;
13
14
  };
@@ -15,28 +16,34 @@ export declare type IRequest = {
15
16
  [key: string]: string | string[] | undefined;
16
17
  };
17
18
  proxy?: any;
18
- } & GenericTraps;
19
- export interface RouterOptions {
19
+ } & Request;
20
+ export declare type IRequest = IRequestStrict & GenericTraps;
21
+ export declare type RouterOptions = {
20
22
  base?: string;
21
23
  routes?: RouteEntry[];
22
- }
23
- export interface RouteHandler {
24
- (request: IRequest, ...args: any): any;
25
- }
24
+ };
25
+ export declare type RouteHandler<I = IRequest, A extends any[] = any[]> = {
26
+ (request: I, ...args: A): any;
27
+ };
26
28
  export declare type RouteEntry = [string, RegExp, RouteHandler[], string];
27
- export declare type Route = <T extends RouterType>(path: string, ...handlers: RouteHandler[]) => T;
28
- export declare type RouterHints = {
29
- all: Route;
30
- delete: Route;
31
- get: Route;
32
- options: Route;
33
- patch: Route;
34
- post: Route;
35
- put: Route;
29
+ export declare type Route = <RequestType = IRequest, Args extends any[] = any[], RT = RouterType>(path: string, ...handlers: RouteHandler<RequestType, Args>[]) => RT;
30
+ export declare type UniversalRoute<RequestType = IRequest, Args extends any[] = any[]> = (path: string, ...handlers: RouteHandler<RequestType, Args>[]) => RouterType<UniversalRoute<RequestType, Args>, Args>;
31
+ declare type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
32
+ export declare type CustomRoutes<R = Route> = {
33
+ [key: string]: R;
36
34
  };
37
- export declare type RouterType = {
38
- __proto__: RouterType;
35
+ export declare type RouterType<R = Route, Args extends any[] = any[]> = {
36
+ __proto__: RouterType<R>;
39
37
  routes: RouteEntry[];
40
- handle: (request: RequestLike, ...extra: any) => Promise<any>;
41
- } & RouterHints;
42
- export declare const Router: ({ base, routes }?: RouterOptions) => RouterType;
38
+ handle: <A extends any[] = Args>(request: RequestLike, ...extra: Equal<R, Args> extends true ? A : Args) => Promise<any>;
39
+ all: R;
40
+ delete: R;
41
+ get: R;
42
+ head: R;
43
+ options: R;
44
+ patch: R;
45
+ post: R;
46
+ put: R;
47
+ } & CustomRoutes<R>;
48
+ export declare const Router: <RequestType = IRequest, Args extends any[] = any[], RouteType = Equal<RequestType, IRequest> extends true ? Route : UniversalRoute<RequestType, Args>>({ base, routes }?: RouterOptions) => RouterType<RouteType, Args>;
49
+ export {};
package/Router.js CHANGED
@@ -1 +1 @@
1
- const e=({base:e="",routes:r=[]}={})=>({__proto__:new Proxy({},{get:(a,o,t,p)=>(a,...l)=>r.push([o.toUpperCase(),RegExp(`^${(p=e+"/"+a).replace(/\/+(\/|$)/g,"$1").replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),l,p])&&t}),routes:r,async handle(e,...a){let o,t,p=new URL(e.url),l=e.query={};for(let[e,r]of p.searchParams)l[e]=void 0===l[e]?r:[l[e],r].flat();for(let[l,s,c,$]of r)if((l===e.method||"ALL"===l)&&(t=p.pathname.match(s))){e.params=t.groups||{},e.route=$?.replace("//","/");for(let r of c)if(void 0!==(o=await r(e.proxy||e,...a)))return o}}});export{e as Router};
1
+ const e=({base:e="",routes:r=[]}={})=>({__proto__:new Proxy({},{get:(a,o,t,p)=>(a,...l)=>r.push([o.toUpperCase(),RegExp(`^${(p=(e+"/"+a).replace(/\/+(\/|$)/g,"$1")).replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),l,p])&&t}),routes:r,async handle(e,...a){let o,t,p=new URL(e.url),l=e.query={};for(let[e,r]of p.searchParams)l[e]=void 0===l[e]?r:[l[e],r].flat();for(let[l,s,$,c]of r)if((l===e.method||"ALL"===l)&&(t=p.pathname.match(s))){e.params=t.groups||{},e.route=c;for(let r of $)if(void 0!==(o=await r(e.proxy||e,...a)))return o}}});export{e as Router};
package/cjs/Router.d.ts CHANGED
@@ -5,9 +5,10 @@ export declare type RequestLike = {
5
5
  method: string;
6
6
  url: string;
7
7
  } & GenericTraps;
8
- export declare type IRequest = {
8
+ export declare type IRequestStrict = {
9
9
  method: string;
10
10
  url: string;
11
+ route: string;
11
12
  params: {
12
13
  [key: string]: string;
13
14
  };
@@ -15,28 +16,34 @@ export declare type IRequest = {
15
16
  [key: string]: string | string[] | undefined;
16
17
  };
17
18
  proxy?: any;
18
- } & GenericTraps;
19
- export interface RouterOptions {
19
+ } & Request;
20
+ export declare type IRequest = IRequestStrict & GenericTraps;
21
+ export declare type RouterOptions = {
20
22
  base?: string;
21
23
  routes?: RouteEntry[];
22
- }
23
- export interface RouteHandler {
24
- (request: IRequest, ...args: any): any;
25
- }
24
+ };
25
+ export declare type RouteHandler<I = IRequest, A extends any[] = any[]> = {
26
+ (request: I, ...args: A): any;
27
+ };
26
28
  export declare type RouteEntry = [string, RegExp, RouteHandler[], string];
27
- export declare type Route = <T extends RouterType>(path: string, ...handlers: RouteHandler[]) => T;
28
- export declare type RouterHints = {
29
- all: Route;
30
- delete: Route;
31
- get: Route;
32
- options: Route;
33
- patch: Route;
34
- post: Route;
35
- put: Route;
29
+ export declare type Route = <RequestType = IRequest, Args extends any[] = any[], RT = RouterType>(path: string, ...handlers: RouteHandler<RequestType, Args>[]) => RT;
30
+ export declare type UniversalRoute<RequestType = IRequest, Args extends any[] = any[]> = (path: string, ...handlers: RouteHandler<RequestType, Args>[]) => RouterType<UniversalRoute<RequestType, Args>, Args>;
31
+ declare type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
32
+ export declare type CustomRoutes<R = Route> = {
33
+ [key: string]: R;
36
34
  };
37
- export declare type RouterType = {
38
- __proto__: RouterType;
35
+ export declare type RouterType<R = Route, Args extends any[] = any[]> = {
36
+ __proto__: RouterType<R>;
39
37
  routes: RouteEntry[];
40
- handle: (request: RequestLike, ...extra: any) => Promise<any>;
41
- } & RouterHints;
42
- export declare const Router: ({ base, routes }?: RouterOptions) => RouterType;
38
+ handle: <A extends any[] = Args>(request: RequestLike, ...extra: Equal<R, Args> extends true ? A : Args) => Promise<any>;
39
+ all: R;
40
+ delete: R;
41
+ get: R;
42
+ head: R;
43
+ options: R;
44
+ patch: R;
45
+ post: R;
46
+ put: R;
47
+ } & CustomRoutes<R>;
48
+ export declare const Router: <RequestType = IRequest, Args extends any[] = any[], RouteType = Equal<RequestType, IRequest> extends true ? Route : UniversalRoute<RequestType, Args>>({ base, routes }?: RouterOptions) => RouterType<RouteType, Args>;
49
+ export {};
package/cjs/Router.js CHANGED
@@ -1 +1 @@
1
- "use strict";exports.Router=({base:e="",routes:r=[]}={})=>({__proto__:new Proxy({},{get:(a,o,t,p)=>(a,...l)=>r.push([o.toUpperCase(),RegExp(`^${(p=e+"/"+a).replace(/\/+(\/|$)/g,"$1").replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),l,p])&&t}),routes:r,async handle(e,...a){let o,t,p=new URL(e.url),l=e.query={};for(let[e,r]of p.searchParams)l[e]=void 0===l[e]?r:[l[e],r].flat();for(let[l,s,c,u]of r)if((l===e.method||"ALL"===l)&&(t=p.pathname.match(s))){e.params=t.groups||{},e.route=u?.replace("//","/");for(let r of c)if(void 0!==(o=await r(e.proxy||e,...a)))return o}}});
1
+ "use strict";exports.Router=({base:e="",routes:r=[]}={})=>({__proto__:new Proxy({},{get:(a,o,t,p)=>(a,...s)=>r.push([o.toUpperCase(),RegExp(`^${(p=(e+"/"+a).replace(/\/+(\/|$)/g,"$1")).replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),s,p])&&t}),routes:r,async handle(e,...a){let o,t,p=new URL(e.url),s=e.query={};for(let[e,r]of p.searchParams)s[e]=void 0===s[e]?r:[s[e],r].flat();for(let[s,l,u,$]of r)if((s===e.method||"ALL"===s)&&(t=p.pathname.match(l))){e.params=t.groups||{},e.route=$;for(let r of u)if(void 0!==(o=await r(e.proxy||e,...a)))return o}}});
package/cjs/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class e extends Error{status;constructor(e=500,t){"object"==typeof t?(super(t.error),Object.assign(this,t)):super(t),this.status=e}}const t=(e="text/plain; charset=utf-8",t)=>(s,r={})=>{const{headers:o={},...n}=r;return"Response"===s?.constructor.name?s:new Response(t?t(s):s,{headers:{"content-type":e,...o},...n})},s=t("application/json; charset=utf-8",JSON.stringify),r=e=>({400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",500:"Internal Server Error"}[e]||"Unknown Error"),o=t("text/html"),n=t("image/jpeg"),a=t("image/png"),c=t("image/webp");exports.Router=({base:e="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(s,r,o,n)=>(s,...a)=>t.push([r.toUpperCase(),RegExp(`^${(n=e+"/"+s).replace(/\/+(\/|$)/g,"$1").replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),a,n])&&o}),routes:t,async handle(e,...s){let r,o,n=new URL(e.url),a=e.query={};for(let[e,t]of n.searchParams)a[e]=void 0===a[e]?t:[a[e],t].flat();for(let[a,c,p,l]of t)if((a===e.method||"ALL"===a)&&(o=n.pathname.match(c))){e.params=o.groups||{},e.route=l?.replace("//","/");for(let t of p)if(void 0!==(r=await t(e.proxy||e,...s)))return r}}}),exports.StatusError=e,exports.createCors=(e={})=>{const{origins:t=["*"],maxAge:s,methods:r=["GET"],headers:o={}}=e;let n;const a={"content-type":"application/json","Access-Control-Allow-Methods":r.join(", "),...o};s&&(a["Access-Control-Max-Age"]=s);return{corsify:e=>{if(!e)throw new Error("No fetch handler responded and no upstream to proxy to specified.");const{headers:t,status:s,body:r}=e;if([101,301,302,308].includes(s))return e;const o=Object.fromEntries(t);return o["access-control-allow-origin"]?e:new Response(r,{status:s,headers:{...o,...a,...n,"content-type":t.get("content-type")}})},preflight:e=>{const s=[...new Set(["OPTIONS",...r])],o=e.headers.get("origin")||"";if(n=(t.includes(o)||t.includes("*"))&&{"Access-Control-Allow-Origin":o},"OPTIONS"===e.method){if(null!==e.headers.get("Origin")&&null!==e.headers.get("Access-Control-Request-Method")&&null!==e.headers.get("Access-Control-Request-Headers")){const t={...a,"Access-Control-Allow-Methods":s.join(", "),"Access-Control-Allow-Headers":e.headers.get("Access-Control-Request-Headers"),...n};return new Response(null,{headers:t})}return new Response(null,{headers:{Allow:s.join(", ")}})}}}},exports.createResponse=t,exports.error=(e=500,t)=>{if(e instanceof Error){const{message:s,...o}=e;e=e.status||500,t={error:s||r(e),...o}}return t={status:e,..."object"==typeof t?t:{error:t||r(e)}},s(t,{status:e})},exports.html=o,exports.jpeg=n,exports.json=s,exports.png=a,exports.status=e=>new Response(null,{status:e}),exports.text=(e,t)=>new Response(e,t),exports.webp=c,exports.withContent=async e=>{e.headers.get("content-type")?.includes("json")&&(e.content=await e.json())},exports.withCookies=e=>{e.cookies=(e.headers.get("Cookie")||"").split(/;\s*/).map((e=>e.split(/=(.+)/))).reduce(((e,[t,s])=>(e[t]=s,e)),{})},exports.withParams=e=>{e.proxy=new Proxy(e.proxy||e,{get:(t,s)=>{let r;return void 0!==(r=t[s])?r.bind?.(e)||r:t?.params?.[s]}})};
1
+ "use strict";class e extends Error{status;constructor(e=500,t){"object"==typeof t?(super(t.error),Object.assign(this,t)):super(t),this.status=e}}const t=(e="text/plain; charset=utf-8",t)=>(s,r={})=>{const{headers:o={},...n}=r;return"Response"===s?.constructor.name?s:new Response(t?t(s):s,{headers:{"content-type":e,...o},...n})},s=t("application/json; charset=utf-8",JSON.stringify),r=e=>({400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",500:"Internal Server Error"}[e]||"Unknown Error"),o=t("text/html"),n=t("image/jpeg"),a=t("image/png"),c=t("image/webp");exports.Router=({base:e="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(s,r,o,n)=>(s,...a)=>t.push([r.toUpperCase(),RegExp(`^${(n=(e+"/"+s).replace(/\/+(\/|$)/g,"$1")).replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),a,n])&&o}),routes:t,async handle(e,...s){let r,o,n=new URL(e.url),a=e.query={};for(let[e,t]of n.searchParams)a[e]=void 0===a[e]?t:[a[e],t].flat();for(let[a,c,p,l]of t)if((a===e.method||"ALL"===a)&&(o=n.pathname.match(c))){e.params=o.groups||{},e.route=l;for(let t of p)if(void 0!==(r=await t(e.proxy||e,...s)))return r}}}),exports.StatusError=e,exports.createCors=(e={})=>{const{origins:t=["*"],maxAge:s,methods:r=["GET"],headers:o={}}=e;let n;const a={"content-type":"application/json","Access-Control-Allow-Methods":r.join(", "),...o};s&&(a["Access-Control-Max-Age"]=s);return{corsify:e=>{if(!e)throw new Error("No fetch handler responded and no upstream to proxy to specified.");const{headers:t,status:s,body:r}=e;if([101,301,302,308].includes(s))return e;const o=Object.fromEntries(t);return o["access-control-allow-origin"]?e:new Response(r,{status:s,headers:{...o,...a,...n,"content-type":t.get("content-type")}})},preflight:e=>{const s=[...new Set(["OPTIONS",...r])],o=e.headers.get("origin")||"";if(n=(t.includes(o)||t.includes("*"))&&{"Access-Control-Allow-Origin":o},"OPTIONS"===e.method){if(null!==e.headers.get("Origin")&&null!==e.headers.get("Access-Control-Request-Method")&&null!==e.headers.get("Access-Control-Request-Headers")){const t={...a,"Access-Control-Allow-Methods":s.join(", "),"Access-Control-Allow-Headers":e.headers.get("Access-Control-Request-Headers"),...n};return new Response(null,{headers:t})}return new Response(null,{headers:{Allow:s.join(", ")}})}}}},exports.createResponse=t,exports.error=(e=500,t)=>{if(e instanceof Error){const{message:s,...o}=e;e=e.status||500,t={error:s||r(e),...o}}return t={status:e,..."object"==typeof t?t:{error:t||r(e)}},s(t,{status:e})},exports.html=o,exports.jpeg=n,exports.json=s,exports.png=a,exports.status=e=>new Response(null,{status:e}),exports.text=(e,t)=>new Response(e,t),exports.webp=c,exports.withContent=async e=>{e.headers.get("content-type")?.includes("json")&&(e.content=await e.json())},exports.withCookies=e=>{e.cookies=(e.headers.get("Cookie")||"").split(/;\s*/).map((e=>e.split(/=(.+)/))).reduce(((e,[t,s])=>(e[t]=s,e)),{})},exports.withParams=e=>{e.proxy=new Proxy(e.proxy||e,{get:(t,s)=>{let r;return void 0!==(r=t[s])?r.bind?.(e)||r:t?.params?.[s]}})};
package/index.js CHANGED
@@ -1 +1 @@
1
- const e=({base:e="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(s,r,o,n)=>(s,...a)=>t.push([r.toUpperCase(),RegExp(`^${(n=e+"/"+s).replace(/\/+(\/|$)/g,"$1").replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),a,n])&&o}),routes:t,async handle(e,...s){let r,o,n=new URL(e.url),a=e.query={};for(let[e,t]of n.searchParams)a[e]=void 0===a[e]?t:[a[e],t].flat();for(let[a,c,l,i]of t)if((a===e.method||"ALL"===a)&&(o=n.pathname.match(c))){e.params=o.groups||{},e.route=i?.replace("//","/");for(let t of l)if(void 0!==(r=await t(e.proxy||e,...s)))return r}}});class t extends Error{status;constructor(e=500,t){"object"==typeof t?(super(t.error),Object.assign(this,t)):super(t),this.status=e}}const s=(e="text/plain; charset=utf-8",t)=>(s,r={})=>{const{headers:o={},...n}=r;return"Response"===s?.constructor.name?s:new Response(t?t(s):s,{headers:{"content-type":e,...o},...n})},r=s("application/json; charset=utf-8",JSON.stringify),o=e=>({400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",500:"Internal Server Error"}[e]||"Unknown Error"),n=(e=500,t)=>{if(e instanceof Error){const{message:s,...r}=e;e=e.status||500,t={error:s||o(e),...r}}return t={status:e,..."object"==typeof t?t:{error:t||o(e)}},r(t,{status:e})},a=e=>new Response(null,{status:e}),c=(e,t)=>new Response(e,t),l=s("text/html"),i=s("image/jpeg"),p=s("image/png"),u=s("image/webp"),d=async e=>{e.headers.get("content-type")?.includes("json")&&(e.content=await e.json())},h=e=>{e.cookies=(e.headers.get("Cookie")||"").split(/;\s*/).map((e=>e.split(/=(.+)/))).reduce(((e,[t,s])=>(e[t]=s,e)),{})},g=e=>{e.proxy=new Proxy(e.proxy||e,{get:(t,s)=>{let r;return void 0!==(r=t[s])?r.bind?.(e)||r:t?.params?.[s]}})},f=(e={})=>{const{origins:t=["*"],maxAge:s,methods:r=["GET"],headers:o={}}=e;let n;const a={"content-type":"application/json","Access-Control-Allow-Methods":r.join(", "),...o};s&&(a["Access-Control-Max-Age"]=s);return{corsify:e=>{if(!e)throw new Error("No fetch handler responded and no upstream to proxy to specified.");const{headers:t,status:s,body:r}=e;if([101,301,302,308].includes(s))return e;const o=Object.fromEntries(t);return o["access-control-allow-origin"]?e:new Response(r,{status:s,headers:{...o,...a,...n,"content-type":t.get("content-type")}})},preflight:e=>{const s=[...new Set(["OPTIONS",...r])],o=e.headers.get("origin")||"";if(n=(t.includes(o)||t.includes("*"))&&{"Access-Control-Allow-Origin":o},"OPTIONS"===e.method){if(null!==e.headers.get("Origin")&&null!==e.headers.get("Access-Control-Request-Method")&&null!==e.headers.get("Access-Control-Request-Headers")){const t={...a,"Access-Control-Allow-Methods":s.join(", "),"Access-Control-Allow-Headers":e.headers.get("Access-Control-Request-Headers"),...n};return new Response(null,{headers:t})}return new Response(null,{headers:{Allow:s.join(", ")}})}}}};export{e as Router,t as StatusError,f as createCors,s as createResponse,n as error,l as html,i as jpeg,r as json,p as png,a as status,c as text,u as webp,d as withContent,h as withCookies,g as withParams};
1
+ const e=({base:e="",routes:t=[]}={})=>({__proto__:new Proxy({},{get:(s,o,r,n)=>(s,...a)=>t.push([o.toUpperCase(),RegExp(`^${(n=(e+"/"+s).replace(/\/+(\/|$)/g,"$1")).replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`),a,n])&&r}),routes:t,async handle(e,...s){let o,r,n=new URL(e.url),a=e.query={};for(let[e,t]of n.searchParams)a[e]=void 0===a[e]?t:[a[e],t].flat();for(let[a,c,l,i]of t)if((a===e.method||"ALL"===a)&&(r=n.pathname.match(c))){e.params=r.groups||{},e.route=i;for(let t of l)if(void 0!==(o=await t(e.proxy||e,...s)))return o}}});class t extends Error{status;constructor(e=500,t){"object"==typeof t?(super(t.error),Object.assign(this,t)):super(t),this.status=e}}const s=(e="text/plain; charset=utf-8",t)=>(s,o={})=>{const{headers:r={},...n}=o;return"Response"===s?.constructor.name?s:new Response(t?t(s):s,{headers:{"content-type":e,...r},...n})},o=s("application/json; charset=utf-8",JSON.stringify),r=e=>({400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",500:"Internal Server Error"}[e]||"Unknown Error"),n=(e=500,t)=>{if(e instanceof Error){const{message:s,...o}=e;e=e.status||500,t={error:s||r(e),...o}}return t={status:e,..."object"==typeof t?t:{error:t||r(e)}},o(t,{status:e})},a=e=>new Response(null,{status:e}),c=(e,t)=>new Response(e,t),l=s("text/html"),i=s("image/jpeg"),p=s("image/png"),u=s("image/webp"),d=async e=>{e.headers.get("content-type")?.includes("json")&&(e.content=await e.json())},h=e=>{e.cookies=(e.headers.get("Cookie")||"").split(/;\s*/).map((e=>e.split(/=(.+)/))).reduce(((e,[t,s])=>(e[t]=s,e)),{})},g=e=>{e.proxy=new Proxy(e.proxy||e,{get:(t,s)=>{let o;return void 0!==(o=t[s])?o.bind?.(e)||o:t?.params?.[s]}})},f=(e={})=>{const{origins:t=["*"],maxAge:s,methods:o=["GET"],headers:r={}}=e;let n;const a={"content-type":"application/json","Access-Control-Allow-Methods":o.join(", "),...r};s&&(a["Access-Control-Max-Age"]=s);return{corsify:e=>{if(!e)throw new Error("No fetch handler responded and no upstream to proxy to specified.");const{headers:t,status:s,body:o}=e;if([101,301,302,308].includes(s))return e;const r=Object.fromEntries(t);return r["access-control-allow-origin"]?e:new Response(o,{status:s,headers:{...r,...a,...n,"content-type":t.get("content-type")}})},preflight:e=>{const s=[...new Set(["OPTIONS",...o])],r=e.headers.get("origin")||"";if(n=(t.includes(r)||t.includes("*"))&&{"Access-Control-Allow-Origin":r},"OPTIONS"===e.method){if(null!==e.headers.get("Origin")&&null!==e.headers.get("Access-Control-Request-Method")&&null!==e.headers.get("Access-Control-Request-Headers")){const t={...a,"Access-Control-Allow-Methods":s.join(", "),"Access-Control-Allow-Headers":e.headers.get("Access-Control-Request-Headers"),...n};return new Response(null,{headers:t})}return new Response(null,{headers:{Allow:s.join(", ")}})}}}};export{e as Router,t as StatusError,f as createCors,s as createResponse,n as error,l as html,i as jpeg,o as json,p as png,a as status,c as text,u as webp,d as withContent,h as withCookies,g as withParams};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itty-router",
3
- "version": "4.0.0-next.49",
3
+ "version": "4.0.0-next.52",
4
4
  "description": "Tiny, zero-dependency API router - built for Cloudflare Workers, but works everywhere!",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -16,9 +16,6 @@
16
16
  "nested"
17
17
  ],
18
18
  "scripts": {
19
- "docs:dev": "cp README.md ./docs/pages/README.md && vite",
20
- "docs:build": "cp README.md ./docs/pages/README.md && vite build",
21
- "docs:serve": "vite preview",
22
19
  "lint": "npx eslint src",
23
20
  "test": "vitest --coverage --reporter verbose",
24
21
  "test:once": "vitest run",
@@ -31,7 +28,9 @@
31
28
  "build:core": "rollup -c",
32
29
  "build:extras": "rollup --config rollup.config.extras.mjs",
33
30
  "build": "rollup -c",
34
- "release": "release --tag --push --type=next --src=dist"
31
+ "release": "release --tag --push --type=next --src=dist",
32
+ "runtime:bun": "bun example/bun.ts",
33
+ "runtime:node": "node example/node.js"
35
34
  },
36
35
  "repository": {
37
36
  "type": "git",
@@ -49,8 +48,10 @@
49
48
  "@rollup/plugin-terser": "^0.2.1",
50
49
  "@rollup/plugin-typescript": "^10.0.1",
51
50
  "@skypack/package-check": "^0.2.2",
51
+ "@types/node": "^20.2.1",
52
52
  "@vitejs/plugin-vue": "^2.2.4",
53
53
  "@vitest/coverage-c8": "^0.24.3",
54
+ "@whatwg-node/server": "^0.7.6",
54
55
  "coveralls": "^3.1.1",
55
56
  "eslint": "^8.11.0",
56
57
  "eslint-plugin-jest": "^26.1.2",
@@ -58,17 +59,19 @@
58
59
  "fs-extra": "^10.0.1",
59
60
  "globby": "^13.1.3",
60
61
  "gzip-size": "^6.0.0",
62
+ "http": "^0.0.1-security",
61
63
  "isomorphic-fetch": "^3.0.0",
64
+ "itty-router": "^4.0.0-next.50",
62
65
  "jsdom": "^20.0.1",
63
66
  "npm-run-all": "^4.1.5",
64
67
  "rimraf": "^3.0.2",
65
68
  "rollup": "^3.8.1",
66
69
  "rollup-plugin-bundle-size": "^1.0.3",
67
70
  "rollup-plugin-multi-input": "^1.3.3",
71
+ "ts-node": "^10.9.1",
68
72
  "typescript": "^4.8.4",
69
73
  "vite": "^2.8.6",
70
74
  "vitest": "^0.24.3",
71
- "vue": "^3.2.31",
72
75
  "yarn": "^1.22.18",
73
76
  "yarn-release": "^1.10.3"
74
77
  }