itty-router 2.4.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ ## Changelog
2
+ Until this library makes it to a production release of v1.x, **minor versions may contain breaking changes to the API**. After v1.x, semantic versioning will be honored, and breaking changes will only occur under the umbrella of a major version bump.
3
+
4
+ - **v2.4.1** - fixed type errors introduced with 2.4.0
5
+ - **v2.4.0** - HUGE internal code-golfing refactor thanks to [@taralx](https://github.com/taralx)! Super cool work on this!!!
6
+ - **v2.3.10** - fix: dots now properly escaped (e.g. /image.jpg should not match /imageXjpg)
7
+ - **v2.3.9** - dev fixes: [@taralx](https://github.com/taralx) improved QOL issues for test writers and dev installers
8
+ - **v2.3.7** - fix: :id.:format not resolving (only conditional format would match)
9
+ - **v2.3.0** - feature: request handler will be passed request.proxy (if found) or request (if not) - allowing for middleware magic downstream...
10
+ - **v2.2.0** - feature: base path (option) is now included in the route param parsing...
11
+ - **v2.1.1** - fix: should now be strict-mode compatible
12
+ - **v2.1.0** - now handles the problematic . character within a param (e.g. /:url/action with /google.com/action)
13
+ - **v2.0.7** - shaved a few more characters in the regex
14
+ - **v2.0.0** - API break: `Router({ else: missingHandler })` has been replaced with `router.all('*', missingHandler)`, and now "all" channel respects order of entry
15
+ - **v1.6.0** - added { else: missingHandler } to options for 404 catch-alls (thanks to the discussion with [@martinffx](https://github.com/martinffx))
16
+ - **v1.5.0** - added '.all(route, handler)' handling for passthrough middleware
17
+ - **v1.4.3** - fixed nested routers using simple "/" routes
18
+ - **v1.4.1** - fixed typings for extra args (thanks [@rodrigoaddor](https://github.com/rodrigoaddor))
19
+ - **v1.4.0** - adds support for optional format params (e.g. "/:id.:format?" --> { params: { id: '13', format: 'jpg' }})
20
+ - **v1.3.0** - adds support for multiple args to handle(request, ...args) method (@hunterloftis)
21
+ - **v1.2.2** - fix: require verify/build pass before publishing and fixed README badges (should point to v1.x branch)
22
+ - **v1.2.0** - feature: chainable route declarations (with test)... that didn't take long...
23
+ - **v1.1.1** - updated README to reflect that chaining actually never was working... (not a breaking change, as no code could have been using it)
24
+ - **v1.1.0** - feature: added single option `{ base: '/some/path' }` to `Router` for route prefixing, fix: trailing wildcard issue (e.g. `/foo/*` should match `/foo`)
25
+ - **v1.0.0** - production release, stamped into gold from x0.9.7
26
+ - **v0.9.0** - added support for multiple handlers (middleware)
27
+ - **v0.8.0** - deep minification pass and build steps for final module
28
+ - **v0.7.0** - removed { path } from request handler context, travis build fixed, added coveralls, improved README docs
29
+ - **v0.6.0** - added types to project for vscode intellisense (thanks [@mvasigh](https://github.com/mvasigh))
30
+ - **v0.5.4** - fix: wildcard routes properly supported
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Kevin R. Whitley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,458 @@
1
+ # [![Itty Router][logo-image]][itty-homepage]
2
+
3
+ [![npm package][npm-image]][npm-url]
4
+ [![minified + gzipped size][gzip-image]][gzip-url]
5
+ [![Build Status][travis-image]][travis-url]
6
+ [![Coverage Status][coveralls-image]][coveralls-url]
7
+ [![Open Issues][issues-image]][issues-url]
8
+ <a href="https://npmjs.com/package/itty-router" target="\_parent">
9
+ <img alt="" src="https://img.shields.io/npm/dm/itty-router.svg" />
10
+ </a>
11
+ [![Discord][discord-image]][discord-url]
12
+ <a href="https://github.com/kwhitley/itty-router" target="\_parent">
13
+ <img alt="" src="https://img.shields.io/github/stars/kwhitley/itty-router.svg?style=social&label=Star" />
14
+ </a>
15
+ <a href="https://twitter.com/kevinrwhitley" target="\_parent">
16
+ <img alt="" src="https://img.shields.io/twitter/follow/kevinrwhitley.svg?style=social&label=Follow" />
17
+ </a>
18
+ <!--<a href="https://github.com/kwhitley/itty-router/discussions">
19
+ <img alt="Join the discussion on Github" src="https://img.shields.io/badge/Github%20Discussions%20%26%20Support-Chat%20now!-blue" />
20
+ </a>-->
21
+
22
+ It's an itty bitty router, designed for express-like routing within [Cloudflare Workers](https://developers.cloudflare.com/workers/) (or anywhere else). Like... it's super tiny ([~4xx bytes](https://bundlephobia.com/package/itty-router)), with zero dependencies. For reals.
23
+
24
+ For quality-of-life improvements (e.g. middleware, cookies, body parsing, json handling, errors, and an itty version with automatic exception handling), to further shorten your routing code, be sure to check out [itty-router-extras](https://www.npmjs.com/package/itty-router-extras) - also specifically written for API development on [Cloudflare Workers](https://developers.cloudflare.com/workers/)!
25
+
26
+ ## Features
27
+ - [x] Tiny ([~4xx bytes](https://bundlephobia.com/package/itty-router) compressed) with zero dependencies.
28
+ - [x] Full sync/async support. Use it when you need it!
29
+ - [x] Route params, with wildcards and optionals (e.g. `/api/:collection/:id?`)
30
+ - [x] Query parsing (e.g. `?page=3&foo=bar`)
31
+ - [x] [Middleware support](#middleware). Any number of sync/async handlers may be passed to a route.
32
+ - [x] [Nestable](#nested-routers-with-404-handling). Supports nesting routers for API branching.
33
+ - [x] [Base path](#nested-routers-with-404-handling) for prefixing all routes.
34
+ - [x] [Multi-method support](#nested-routers-with-404-handling) using the `.all()` channel.
35
+ - [x] Supports **any** method type (e.g. `.get() --> 'GET'` or `.puppy() --> 'PUPPY'`).
36
+ - [x] Supports [Cloudflare ES Module syntax](#cf-es6-module-syntax)! :)
37
+ - [x] [Preload or manually inject custom regex for routes](#manual-routes) (advanced usage)
38
+ - [x] [Extendable](#extending-itty-router). Use itty as the internal base for more feature-rich/elaborate routers.
39
+ - [x] Chainable route declarations (why not?)
40
+ - [ ] Readable internal code (yeah right...)
41
+
42
+ ## Installation
43
+
44
+ ```
45
+ npm install itty-router
46
+ ```
47
+
48
+ ## Example
49
+ ```js
50
+ import { Router } from 'itty-router'
51
+
52
+ // now let's create a router (note the lack of "new")
53
+ const router = Router()
54
+
55
+ // GET collection index
56
+ router.get('/todos', () => new Response('Todos Index!'))
57
+
58
+ // GET item
59
+ router.get('/todos/:id', ({ params }) => new Response(`Todo #${params.id}`))
60
+
61
+ // POST to the collection (we'll use async here)
62
+ router.post('/todos', async request => {
63
+ const content = await request.json()
64
+
65
+ return new Response('Creating Todo: ' + JSON.stringify(content))
66
+ })
67
+
68
+ // 404 for everything else
69
+ router.all('*', () => new Response('Not Found.', { status: 404 }))
70
+
71
+ // attach the router "handle" to the event handler
72
+ addEventListener('fetch', event =>
73
+ event.respondWith(router.handle(event.request))
74
+ )
75
+ ```
76
+
77
+ ## Options API
78
+ #### `Router(options = {})`
79
+
80
+ | Name | Type(s) | Description | Examples |
81
+ | --- | --- | --- | --- |
82
+ | `base` | `string` | prefixes all routes with this string | `Router({ base: '/api' })`
83
+ | `routes` | `array of routes` | array of manual routes for preloading | [see documentation](#manual-routes)
84
+
85
+ ## Usage
86
+ ### 1. Create a Router
87
+ ```js
88
+ import { Router } from 'itty-router'
89
+
90
+ const router = Router() // no "new", as this is not a real class
91
+ ```
92
+
93
+ ### 2. Register Route(s)
94
+ ##### `router.{method}(route: string, handler1: function, handler2: function, ...)`
95
+ ```js
96
+ // register a route on the "GET" method
97
+ router.get('/todos/:user/:item?', (req) => {
98
+ const { params, query } = req
99
+
100
+ console.log({ params, query })
101
+ })
102
+ ```
103
+
104
+ ### 3. Handle Incoming Request(s)
105
+ ##### `async router.handle(request.proxy: Proxy || request: Request, ...anything else)`
106
+ Requests (doesn't have to be a real Request class) should have both a **method** and full **url**.
107
+ The `handle` method will then return the first matching route handler that returns something (or nothing at all if no match).
108
+
109
+ ```js
110
+ router.handle({
111
+ method: 'GET', // required
112
+ url: 'https://example.com/todos/jane/13', // required
113
+ })
114
+
115
+ /*
116
+ Example outputs (using route handler from step #2 above):
117
+
118
+ GET /todos/jane/13
119
+ {
120
+ params: {
121
+ user: 'jane',
122
+ item: '13'
123
+ },
124
+ query: {}
125
+ }
126
+
127
+ GET /todos/jane
128
+ {
129
+ params: {
130
+ user: 'jane'
131
+ },
132
+ query: {}
133
+ }
134
+
135
+ GET /todos/jane?limit=2&page=1
136
+ {
137
+ params: {
138
+ user: 'jane'
139
+ },
140
+ query: {
141
+ limit: '2',
142
+ page: '2'
143
+ }
144
+ }
145
+ */
146
+ ```
147
+
148
+ #### A few notes about this:
149
+ - **Error Handling:** By default, there is no error handling built in to itty. However, the handle function is async, allowing you to add a `.catch(error)` like this:
150
+
151
+ ```js
152
+ import { Router } from 'itty-router'
153
+
154
+ // a generic error handler
155
+ const errorHandler = error =>
156
+ new Response(error.message || 'Server Error', { status: error.status || 500 })
157
+
158
+ // add some routes (will both safely trigger errorHandler)
159
+ router
160
+ .get('/accidental', request => request.that.will.throw)
161
+ .get('/intentional', () => {
162
+ throw new Error('Bad Request')
163
+ })
164
+
165
+ // attach the router "handle" to the event handler
166
+ addEventListener('fetch', event =>
167
+ event.respondWith(
168
+ router
169
+ .handle(event.request)
170
+ .catch(errorHandler)
171
+ )
172
+ )
173
+ ```
174
+ - **Extra Variables:** The router handle expects only the request itself, but passes along any additional params to the handlers/middleware. For example, to access the `event` itself within a handler (e.g. for `event.waitUntil()`), you could simply do this:
175
+
176
+ ```js
177
+ const router = Router()
178
+
179
+ router.get('/long-task', (request, event) => {
180
+ event.waitUntil(longAsyncTaskPromise)
181
+
182
+ return new Response('Task is still running in the background!')
183
+ })
184
+
185
+ addEventListener('fetch', event =>
186
+ event.respondWith(router.handle(event.request, event))
187
+ )
188
+ ```
189
+ - **Proxies:** To allow for some pretty incredible middleware hijacks, we pass `request.proxy` (if it exists) or `request` (if not) to the handler. This allows middleware to set `request.proxy = new Proxy(request.proxy || request, {})` and effectively take control of reads/writes to the request object itself. As an example, the `withParams` middleware in `itty-router-extras` uses this to control future reads from the request. It intercepts "get" on the Proxy, first checking the requested attribute within the `request.params` then falling back to the `request` itself.
190
+
191
+ ## Examples
192
+
193
+ ### Nested Routers with 404 handling
194
+ ```js
195
+ // lets save a missing handler
196
+ const missingHandler = new Response('Not found.', { status: 404 })
197
+
198
+ // create a parent router
199
+ const parentRouter = Router({ base: '/api' )
200
+
201
+ // and a child router (with FULL base path defined, from root)
202
+ const todosRouter = Router({ base: '/api/todos' })
203
+
204
+ // with some routes on it (these will be relative to the base)...
205
+ todosRouter
206
+ .get('/', () => new Response('Todos Index'))
207
+ .get('/:id', ({ params }) => new Response(`Todo #${params.id}`))
208
+
209
+ // then divert ALL requests to /todos/* into the child router
210
+ parentRouter
211
+ .all('/todos/*', todosRouter.handle) // attach child router
212
+ .all('*', missingHandler) // catch any missed routes
213
+
214
+ // GET /todos --> Todos Index
215
+ // GET /todos/13 --> Todo #13
216
+ // POST /todos --> missingHandler (caught eventually by parentRouter)
217
+ // GET /foo --> missingHandler
218
+ ```
219
+
220
+ A few quick caveats about nesting... each handler/router is fired in complete isolation, unaware of upstream routers. Because of this, base paths do **not** chain from parent routers - meaning each child branch/router will need to define its **full** path.
221
+
222
+ However, as a bonus (from v2.2+), route params will use the base path as well (e.g. `Router({ path: '/api/:collection' })`).
223
+
224
+ ### Middleware
225
+ Any handler that does not **return** will effectively be considered "middleware", continuing to execute future functions/routes until one returns, closing the response.
226
+
227
+ ```js
228
+ // withUser modifies original request, but returns nothing
229
+ const withUser = request => {
230
+ request.user = { name: 'Mittens', age: 3 }
231
+ }
232
+
233
+ // requireUser optionally returns (early) if user not found on request
234
+ const requireUser = request => {
235
+ if (!request.user) {
236
+ return new Response('Not Authenticated', { status: 401 })
237
+ }
238
+ }
239
+
240
+ // showUser returns a response with the user (assumed to exist)
241
+ const showUser = request => new Response(JSON.stringify(request.user))
242
+
243
+ // now let's add some routes
244
+ router
245
+ .get('/pass/user', withUser, requireUser, showUser)
246
+ .get('/fail/user', requireUser, showUser)
247
+
248
+ router.handle({ method: 'GET', url: 'https://example.com/pass/user' })
249
+ // withUser injects user, allowing requireUser to not return/continue
250
+ // STATUS 200: { name: 'Mittens', age: 3 }
251
+
252
+ router.handle({ method: 'GET', url: 'https://example.com/fail/user' })
253
+ // requireUser returns early because req.user doesn't exist
254
+ // STATUS 401: Not Authenticated
255
+ ```
256
+
257
+ ### Multi-route (Upstream) Middleware
258
+ ```js
259
+ // middleware that injects a user, but doesn't return
260
+ const withUser = request => {
261
+ request.user = { name: 'Mittens', age: 3 }
262
+ }
263
+
264
+ router
265
+ .get('*', withUser) // embeds user before all other matching routes
266
+ .get('/user', request => new Response(`Hello, ${user.name}!`))
267
+
268
+ router.handle({ method: 'GET', url: 'https://example.com/user' })
269
+ // STATUS 200: Hello, Mittens!
270
+ ```
271
+
272
+ ### File format support
273
+ ```js
274
+ // GET item with optional format/extension
275
+ router.get('/todos/:id.:format?', request => {
276
+ const { id, format = 'csv' } = request.params
277
+
278
+ return new Response(`Getting todo #${id} in ${format} format.`)
279
+ })
280
+ ```
281
+
282
+ ### Cloudflare ES6 Module Syntax (required for Durable Objects) <a id="cf-es6-module-syntax"></a>
283
+ ```js
284
+ const router = Router()
285
+
286
+ router.get('/', (request, env) => {
287
+ // now have access to the env (where CF bindings like durables, KV, etc now are)
288
+ })
289
+
290
+ export default {
291
+ fetch: router.handle // yep, it's this easy.
292
+ }
293
+ ```
294
+
295
+ ### Extending itty router
296
+ Extending itty is as easy as wrapping Router in another Proxy layer to control the handle (or the route registering). For example, here's the code to
297
+ ThrowableRouter in itty-router-extras... a version of itty with built-in error-catching for convenience.
298
+ ```js
299
+ import { Router } from 'itty-router'
300
+
301
+ // a generic error handler
302
+ const errorHandler = error =>
303
+ new Response(error.message || 'Server Error', { status: error.status || 500 })
304
+
305
+ // and the new-and-improved itty
306
+ const ThrowableRouter = (options = {}) =>
307
+ new Proxy(Router(options), {
308
+ get: (obj, prop) => (...args) =>
309
+ prop === 'handle'
310
+ ? obj[prop](...args).catch(errorHandler)
311
+ : obj[prop](...args)
312
+ })
313
+
314
+ // 100% drop-in replacement for Router
315
+ const router = ThrowableRouter()
316
+
317
+ // add some routes
318
+ router
319
+ .get('/accidental', request => request.that.will.throw) // will safely trigger error (above)
320
+ .get('/intentional', () => {
321
+ throw new Error('Bad Request') // will also trigger error handler
322
+ })
323
+ ```
324
+
325
+ ### Manual Routes
326
+ Thanks to a pretty sick refactor by [@taralx](https://github.com/taralx), we've added the ability to fully preload or push manual routes with hand-writted regex.
327
+
328
+ Why is this useful?
329
+
330
+ Out of the box, only a tiny subset of regex "accidentally" works with itty, given that... you know... it's the thing writing regex for you in the first place. If you have a problem route that needs custom regex though, you can now manually give itty the exact regex it will match against, through the far-less-user-friendly-but-totally-possible manual injection method (below).
331
+
332
+ Individual routes are defined as an array of: `[ method: string, match: RegExp, handlers: Array<function> ]`
333
+
334
+ ###### EXAMPLE 1: Manually push a custom route
335
+ ```js
336
+ import { Router } from 'itty-router'
337
+
338
+ const router = Router()
339
+
340
+ // let's define a simple request handler
341
+ const handler = request => request.params
342
+
343
+ // and manually push a route onto the internal routes collection
344
+ router.routes.push(
345
+ [
346
+ 'GET', // method: GET
347
+ /^\/custom-(?<id>\w\d{3})$/, // regex match with named groups (e.g. "id")
348
+ [handler], // array of request handlers
349
+ ]
350
+ )
351
+
352
+ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a123' }) // { id: "a123" }
353
+ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a12456' }) // won't catch
354
+ ```
355
+
356
+
357
+ ###### EXAMPLE 2: Preloading routes via Router options
358
+ ```js
359
+ import { Router } from 'itty-router'
360
+
361
+ // let's define a simple request handler
362
+ const handler = request => request.params
363
+
364
+ // and load the route while creating the router
365
+ const router = Router({
366
+ routes: [
367
+ [ 'GET', /^\/custom-(?<id>\w\d{3})$/, [handler] ], // same example as above, but shortened
368
+ ]
369
+ })
370
+
371
+ // add routes normally...
372
+ router.get('/test', () => new Response('You can still define routes normally as well...'))
373
+
374
+ // router will catch on custom route, as expected
375
+ await router.handle({ method: 'GET', url: 'https:nowhere.com/custom-a123' }) // { id: "a123" }
376
+ ```
377
+
378
+ ## Testing and Contributing
379
+ 1. Fork repo
380
+ 1. Install dev dependencies via `yarn`
381
+ 1. Start test runner/dev mode `yarn dev`
382
+ 1. Add your code and tests if needed - do NOT remove/alter existing tests
383
+ 1. Verify that tests pass once minified `yarn verify`
384
+ 1. Commit files
385
+ 1. Submit PR with a detailed description of what you're doing
386
+ 1. I'll add you to the credits! :)
387
+
388
+ ### The Entire Code (for more legibility, [see src on GitHub](https://github.com/kwhitley/itty-router/blob/v2.x/src/itty-router.js))
389
+ ```js
390
+ const Router = ({ base = '', routes = [] } = {}) => ({
391
+ __proto__: new Proxy({}, {
392
+ get: (t, k, c) => (p, ...H) =>
393
+ routes.push([
394
+ k.toUpperCase(),
395
+ RegExp(`^${(base + p)
396
+ .replace(/(\/?)\*/g, '($1.*)?')
397
+ .replace(/\/$/, '')
398
+ .replace(/:(\w+)(\?)?(\.)?/g, '$2(?<$1>[^/]+)$2$3')
399
+ .replace(/\.(?=[\w(])/, '\\.')
400
+ }/*$`),
401
+ H,
402
+ ]) && c
403
+ }),
404
+ routes,
405
+ async handle (q, ...a) {
406
+ let s, m,
407
+ u = new URL(q.url)
408
+ q.query = Object.fromEntries(u.searchParams)
409
+ for (let [M, p, H] of routes) {
410
+ if ((M === q.method || M === 'ALL') && (m = u.pathname.match(p))) {
411
+ q.params = m.groups
412
+ for (let h of H) {
413
+ if ((s = await h(q.proxy || q, ...a)) !== undefined) return s
414
+ }
415
+ }
416
+ }
417
+ }
418
+ })
419
+ ```
420
+
421
+ ## Special Thanks
422
+ This repo goes out to my past and present colleagues at Arundo - who have brought me such inspiration, fun,
423
+ and drive over the last couple years. In particular, the absurd brevity of this code is thanks to a
424
+ clever [abuse] of `Proxy`, courtesy of the brilliant [@mvasigh](https://github.com/mvasigh).
425
+ This trick allows methods (e.g. "get", "post") to by defined dynamically by the router as they are requested,
426
+ **drastically** reducing boilerplate.
427
+
428
+ [twitter-image]:https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fitty-router
429
+ [logo-image]:https://user-images.githubusercontent.com/865416/79531114-fa0d8200-8036-11ea-824d-70d84164b00a.png
430
+ [gzip-image]:https://img.shields.io/bundlephobia/minzip/itty-router
431
+ [gzip-url]:https://bundlephobia.com/result?p=itty-router
432
+ [issues-image]:https://img.shields.io/github/issues/kwhitley/itty-router
433
+ [issues-url]:https://github.com/kwhitley/itty-router/issues
434
+ [npm-image]:https://img.shields.io/npm/v/itty-router.svg
435
+ [npm-url]:http://npmjs.org/package/itty-router
436
+ [travis-image]:https://travis-ci.org/kwhitley/itty-router.svg?branch=v1.x
437
+ [travis-url]:https://travis-ci.org/kwhitley/itty-router
438
+ [david-image]:https://david-dm.org/kwhitley/itty-router/status.svg
439
+ [david-url]:https://david-dm.org/kwhitley/itty-router
440
+ [coveralls-image]:https://coveralls.io/repos/github/kwhitley/itty-router/badge.svg?branch=v1.x
441
+ [coveralls-url]:https://coveralls.io/github/kwhitley/itty-router?branch=v1.x
442
+ [itty-homepage]:https://itty-router.dev
443
+ [discord-image]:https://img.shields.io/discord/832353585802903572
444
+ [discord-url]:https://discord.com/channels/832353585802903572
445
+
446
+ ## Contributors
447
+ These folks are the real heroes, making open source the powerhouse that it is! Help out and get your name added to this list! <3
448
+
449
+ #### Core, Concepts, and Codebase
450
+ - [@taralx](https://github.com/taralx) - router internal code-golfing refactor for performance and character savings
451
+ - [@hunterloftis](https://github.com/hunterloftis) - router.handle() method now accepts extra arguments and passed them to route functions
452
+ - [@mvasigh](https://github.com/mvasigh) - proxy hacks courtesy of this chap
453
+ #### Fixes
454
+ - [@taralx](https://github.com/taralx) - QOL fixes for contributing (dev dep fix and test file consistency) <3
455
+ - [@technoyes](https://github.com/technoyes) - three kind-of-a-big-deal errors fixed. Imagine the look on my face... thanks man!! :)
456
+ - [@roojay520](https://github.com/roojay520) - TS interface fixes
457
+ - [@arunsathiya](https://github.com/arunsathiya) - documentation
458
+ - [@poacher2k](https://github.com/poacher2k) - documentation
@@ -0,0 +1,44 @@
1
+ export type Obj = {
2
+ [propName: string]: string
3
+ }
4
+
5
+ export interface RouteHandler<TRequest> {
6
+ (request: TRequest & Request, ...args: any): any
7
+ }
8
+
9
+ export interface Route {
10
+ <TRequest>(path: string, ...handlers: RouteHandler<TRequest & Request>[]): Router<TRequest>
11
+ }
12
+
13
+ export interface RouteEntry<TRequest> {
14
+ 0: string
15
+ 1: RegExp
16
+ 2: RouteHandler<TRequest>[]
17
+ }
18
+
19
+ export interface Request {
20
+ method: string
21
+ params?: Obj
22
+ query?: Obj
23
+ url: string
24
+
25
+ arrayBuffer?(): Promise<any>
26
+ blob?(): Promise<any>
27
+ formData?(): Promise<any>
28
+ json?(): Promise<any>
29
+ text?(): Promise<any>
30
+ }
31
+
32
+ export type Router<TRequest> = {
33
+ handle: (request: Request, ...extra: any) => any
34
+ routes: RouteEntry<TRequest>[]
35
+ } & {
36
+ [any:string]: Route
37
+ }
38
+
39
+ export interface RouterOptions<TRequest> {
40
+ base?: string
41
+ routes?: RouteEntry<TRequest>[]
42
+ }
43
+
44
+ export function Router<TRequest>(options?:RouterOptions<TRequest>): Router<TRequest>
@@ -0,0 +1 @@
1
+ module.exports={Router:({base:t="",routes:l=[]}={})=>({__proto__:new Proxy({},{get:(e,a,o)=>(e,...r)=>l.push([a.toUpperCase(),RegExp(`^${(t+e).replace(/(\/?)\*/g,"($1.*)?").replace(/\/$/,"").replace(/:(\w+)(\?)?(\.)?/g,"$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/,"\\.")}/*$`),r])&&o}),routes:l,async handle(e,...r){let a,o,t=new URL(e.url);e.query=Object.fromEntries(t.searchParams);for(var[p,s,u]of l)if((p===e.method||"ALL"===p)&&(o=t.pathname.match(s))){e.params=o.groups;for(var c of u)if(void 0!==(a=await c(e.proxy||e,...r)))return a}}})};
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "itty-router",
3
+ "version": "2.4.3",
4
+ "description": "Tiny, zero-dependency router with route param and query parsing - build for Cloudflare Workers, but works everywhere!",
5
+ "main": "./dist/itty-router.min.js",
6
+ "types": "./dist/itty-router.d.ts",
7
+ "files": [
8
+ "dist/itty-router.min.js",
9
+ "dist/itty-router.d.ts"
10
+ ],
11
+ "keywords": [
12
+ "router",
13
+ "cloudflare",
14
+ "worker",
15
+ "workers",
16
+ "serverless",
17
+ "api",
18
+ "express",
19
+ "regex",
20
+ "routing",
21
+ "handler",
22
+ "params",
23
+ "request",
24
+ "response",
25
+ "service worker",
26
+ "sw",
27
+ "cf",
28
+ "optional",
29
+ "middleware",
30
+ "nested",
31
+ "rest",
32
+ "crud",
33
+ "query",
34
+ "parameters",
35
+ "durable",
36
+ "objects"
37
+ ],
38
+ "scripts": {
39
+ "docs:dev": "cp README.md ./docs/pages/README.md && vite",
40
+ "docs:build": "cp README.md ./docs/pages/README.md && vite build",
41
+ "docs:serve": "vite preview",
42
+ "lint": "npx eslint src",
43
+ "test:dist": "jest --verbose --coverage dist",
44
+ "test": "yarn lint && jest --verbose --coverage src",
45
+ "verify": "echo 'verifying module...' && yarn build && yarn test && yarn test:dist",
46
+ "dev": "yarn lint && jest --verbose --watch src",
47
+ "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls",
48
+ "prerelease": "yarn verify",
49
+ "prebuild": "rimraf dist && mkdir dist && node prebuild.js && cp src/itty-router.d.ts dist",
50
+ "build": "uglifyjs dist/itty-router.js -c -m --toplevel > dist/itty-router.min.js",
51
+ "postbuild": "node check-size.js",
52
+ "release": "release --tag --push"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/kwhitley/itty-router.git"
57
+ },
58
+ "author": "Kevin R. Whitley <krwhitley@gmail.com>",
59
+ "license": "MIT",
60
+ "bugs": {
61
+ "url": "https://github.com/kwhitley/itty-router/issues"
62
+ },
63
+ "homepage": "https://itty-router.dev",
64
+ "devDependencies": {
65
+ "@vitejs/plugin-vue": "^1.4.0",
66
+ "@vue/compiler-sfc": "^3.2.2",
67
+ "chalk": "^4.1.2",
68
+ "coveralls": "^3.1.1",
69
+ "eslint": "^7.32.0",
70
+ "eslint-plugin-jest": "^24.4.0",
71
+ "fs-extra": "^10.0.0",
72
+ "gzip-size": "^6.0.0",
73
+ "isomorphic-fetch": "^3.0.0",
74
+ "jest": "^27.0.6",
75
+ "rimraf": "^3.0.2",
76
+ "uglify-js": "^3.14.1",
77
+ "vite": "^2.4.4",
78
+ "yarn": "^1.22.11",
79
+ "yarn-release": "^1.10.3"
80
+ }
81
+ }