weifuwu 0.1.0
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/AGENTS.md +47 -0
- package/README.md +316 -0
- package/compress.ts +69 -0
- package/cookie.ts +58 -0
- package/index.ts +19 -0
- package/middleware.ts +178 -0
- package/package.json +21 -0
- package/rate-limit.ts +68 -0
- package/router.ts +702 -0
- package/serve.ts +126 -0
- package/static.ts +113 -0
- package/test/compress.test.ts +106 -0
- package/test/cookie.test.ts +79 -0
- package/test/middleware.test.ts +407 -0
- package/test/rate-limit.test.ts +94 -0
- package/test/static.test.ts +93 -0
- package/test/unode.test.ts +401 -0
- package/test/upload.test.ts +130 -0
- package/test/validate.test.ts +133 -0
- package/types.ts +23 -0
- package/upload.ts +101 -0
- package/validate.ts +88 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "weifuwu",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test 'test/**/*.test.ts'"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@graphql-tools/schema": "^10",
|
|
11
|
+
"ai": "^6",
|
|
12
|
+
"graphql": "^16",
|
|
13
|
+
"ws": "^8",
|
|
14
|
+
"zod": "^4.4.3"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/ws": "^8.18.1"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/rate-limit.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Middleware } from './types.ts'
|
|
2
|
+
|
|
3
|
+
export interface RateLimitOptions {
|
|
4
|
+
max?: number
|
|
5
|
+
window?: number
|
|
6
|
+
key?: (req: Request) => string
|
|
7
|
+
message?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function rateLimit(options?: RateLimitOptions): Middleware {
|
|
11
|
+
const max = options?.max ?? 100
|
|
12
|
+
const window = options?.window ?? 60_000
|
|
13
|
+
const getKey = options?.key ?? ((req) => {
|
|
14
|
+
const forwarded = req.headers.get('x-forwarded-for')
|
|
15
|
+
if (forwarded) return forwarded.split(',')[0]!.trim()
|
|
16
|
+
return new URL(req.url).hostname
|
|
17
|
+
})
|
|
18
|
+
const message = options?.message ?? 'Too Many Requests'
|
|
19
|
+
|
|
20
|
+
const hits = new Map<string, { count: number; reset: number }>()
|
|
21
|
+
|
|
22
|
+
const interval = setInterval(() => {
|
|
23
|
+
const now = Date.now()
|
|
24
|
+
for (const [key, entry] of hits) {
|
|
25
|
+
if (entry.reset < now) hits.delete(key)
|
|
26
|
+
}
|
|
27
|
+
}, window)
|
|
28
|
+
|
|
29
|
+
if (interval.unref) interval.unref()
|
|
30
|
+
|
|
31
|
+
return async (req, ctx, next) => {
|
|
32
|
+
const key = getKey(req)
|
|
33
|
+
const now = Date.now()
|
|
34
|
+
const entry = hits.get(key)
|
|
35
|
+
|
|
36
|
+
if (!entry || entry.reset < now) {
|
|
37
|
+
hits.set(key, { count: 1, reset: now + window })
|
|
38
|
+
const res = await next(req, ctx)
|
|
39
|
+
const headers = new Headers(res.headers)
|
|
40
|
+
headers.set('X-RateLimit-Limit', String(max))
|
|
41
|
+
headers.set('X-RateLimit-Remaining', String(max - 1))
|
|
42
|
+
headers.set('X-RateLimit-Reset', String(Math.ceil((now + window) / 1000)))
|
|
43
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
entry.count++
|
|
47
|
+
const remaining = Math.max(0, max - entry.count)
|
|
48
|
+
|
|
49
|
+
if (entry.count > max) {
|
|
50
|
+
return new Response(message, {
|
|
51
|
+
status: 429,
|
|
52
|
+
headers: {
|
|
53
|
+
'Retry-After': String(Math.ceil((entry.reset - now) / 1000)),
|
|
54
|
+
'X-RateLimit-Limit': String(max),
|
|
55
|
+
'X-RateLimit-Remaining': '0',
|
|
56
|
+
'X-RateLimit-Reset': String(Math.ceil(entry.reset / 1000)),
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const res = await next(req, ctx)
|
|
62
|
+
const headers = new Headers(res.headers)
|
|
63
|
+
headers.set('X-RateLimit-Limit', String(max))
|
|
64
|
+
headers.set('X-RateLimit-Remaining', String(remaining))
|
|
65
|
+
headers.set('X-RateLimit-Reset', String(Math.ceil(entry.reset / 1000)))
|
|
66
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
67
|
+
}
|
|
68
|
+
}
|