weifuwu 0.2.1 → 0.2.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/README.md +168 -0
- package/dist/compress.d.ts +6 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1420 -0
- package/dist/middleware.d.ts +21 -0
- package/dist/rate-limit.d.ts +8 -0
- package/dist/router.d.ts +55 -0
- package/dist/serve.d.ts +19 -0
- package/dist/static.d.ts +7 -0
- package/dist/tsx.d.ts +17 -0
- package/dist/types.d.ts +9 -0
- package/dist/upload.d.ts +14 -0
- package/dist/validate.d.ts +9 -0
- package/package.json +14 -2
- package/AGENTS.md +0 -105
- package/compress.ts +0 -69
- package/cookie.ts +0 -58
- package/index.ts +0 -21
- package/middleware.ts +0 -178
- package/rate-limit.ts +0 -68
- package/router.ts +0 -702
- package/serve.ts +0 -126
- package/static.ts +0 -113
- package/test/compress.test.ts +0 -106
- package/test/cookie.test.ts +0 -79
- package/test/fixtures/pages/about/page.tsx +0 -3
- package/test/fixtures/pages/blog/[slug]/load.ts +0 -3
- package/test/fixtures/pages/blog/[slug]/page.tsx +0 -3
- package/test/fixtures/pages/blog/[slug]/route.ts +0 -7
- package/test/fixtures/pages/blog/layout.tsx +0 -3
- package/test/fixtures/pages/layout.tsx +0 -12
- package/test/fixtures/pages/page.tsx +0 -3
- package/test/middleware.test.ts +0 -407
- package/test/rate-limit.test.ts +0 -94
- package/test/static.test.ts +0 -93
- package/test/tsx.test.ts +0 -285
- package/test/unode.test.ts +0 -401
- package/test/upload.test.ts +0 -130
- package/test/validate.test.ts +0 -133
- package/tsconfig.json +0 -13
- package/tsx.ts +0 -354
- package/types.ts +0 -23
- package/upload.ts +0 -101
- package/validate.ts +0 -88
package/upload.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { writeFile, mkdir } from 'node:fs/promises'
|
|
2
|
-
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import type { Middleware } from './types.ts'
|
|
5
|
-
|
|
6
|
-
export interface UploadedFile {
|
|
7
|
-
name: string
|
|
8
|
-
type: string
|
|
9
|
-
size: number
|
|
10
|
-
path?: string
|
|
11
|
-
buffer?: Buffer
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface UploadOptions {
|
|
15
|
-
dir?: string
|
|
16
|
-
maxFileSize?: number
|
|
17
|
-
allowedTypes?: string[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function upload(options?: UploadOptions): Middleware {
|
|
21
|
-
const saveDir = options?.dir
|
|
22
|
-
|
|
23
|
-
return async (req, ctx, next) => {
|
|
24
|
-
const ct = req.headers.get('content-type') ?? ''
|
|
25
|
-
if (!ct.includes('multipart/form-data')) {
|
|
26
|
-
return next(req, ctx)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const match = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i)
|
|
30
|
-
if (!match) {
|
|
31
|
-
return Response.json({ error: 'Missing boundary' }, { status: 400 })
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const boundary = match[1] ?? match[2]!
|
|
35
|
-
const body = await req.text()
|
|
36
|
-
const rawParts = body.split(`--${boundary}`).filter((p) => p && !p.startsWith('--') && !p.startsWith('\r\n--'))
|
|
37
|
-
|
|
38
|
-
const files: Record<string, UploadedFile | UploadedFile[]> = {}
|
|
39
|
-
const fields: Record<string, string> = {}
|
|
40
|
-
|
|
41
|
-
for (const raw of rawParts) {
|
|
42
|
-
const trimmed = raw.replace(/^\r?\n/, '')
|
|
43
|
-
const lines = trimmed.split(/\r?\n/)
|
|
44
|
-
let i = 0
|
|
45
|
-
const headers: Record<string, string> = {}
|
|
46
|
-
while (i < lines.length && lines[i]!.length > 0) {
|
|
47
|
-
const sep = lines[i]!.indexOf(': ')
|
|
48
|
-
if (sep !== -1) headers[lines[i]!.slice(0, sep).toLowerCase()] = lines[i]!.slice(sep + 2)
|
|
49
|
-
i++
|
|
50
|
-
}
|
|
51
|
-
i++
|
|
52
|
-
const bodyValue = lines.slice(i).join('\r\n')
|
|
53
|
-
|
|
54
|
-
const disposition = headers['content-disposition'] ?? ''
|
|
55
|
-
const nameMatch = disposition.match(/name="([^"]*)"/)
|
|
56
|
-
if (!nameMatch) continue
|
|
57
|
-
const name = nameMatch[1]!
|
|
58
|
-
const filenameMatch = disposition.match(/filename="([^"]*)"/)
|
|
59
|
-
const filename = filenameMatch?.[1]
|
|
60
|
-
|
|
61
|
-
if (filename) {
|
|
62
|
-
const buf = Buffer.from(bodyValue.replace(/\r?\n$/, ''), 'binary')
|
|
63
|
-
if (options?.allowedTypes) {
|
|
64
|
-
const mime = headers['content-type'] ?? 'application/octet-stream'
|
|
65
|
-
if (!options.allowedTypes.includes(mime)) {
|
|
66
|
-
return Response.json({ error: `File type not allowed: ${mime}` }, { status: 415 })
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (options?.maxFileSize && buf.byteLength > options.maxFileSize) {
|
|
70
|
-
return Response.json({ error: `File too large: ${filename}` }, { status: 413 })
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const uf: UploadedFile = {
|
|
74
|
-
name: filename,
|
|
75
|
-
type: headers['content-type'] ?? 'application/octet-stream',
|
|
76
|
-
size: buf.byteLength,
|
|
77
|
-
buffer: saveDir ? undefined : buf,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (saveDir) {
|
|
81
|
-
const filePath = join(saveDir, `${randomUUID()}-${filename}`)
|
|
82
|
-
await mkdir(saveDir, { recursive: true })
|
|
83
|
-
await writeFile(filePath, buf)
|
|
84
|
-
uf.path = filePath
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (files[name]) {
|
|
88
|
-
const existing = files[name]
|
|
89
|
-
files[name] = Array.isArray(existing) ? [...existing, uf] : [existing, uf]
|
|
90
|
-
} else {
|
|
91
|
-
files[name] = uf
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
fields[name] = bodyValue.replace(/\r?\n$/, '')
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
ctx.parsed = { ...ctx.parsed, files, fields }
|
|
99
|
-
return next(req, ctx)
|
|
100
|
-
}
|
|
101
|
-
}
|
package/validate.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { ZodSchema } from 'zod'
|
|
2
|
-
import type { Middleware } from './types.ts'
|
|
3
|
-
|
|
4
|
-
export interface ValidationSchemas {
|
|
5
|
-
body?: ZodSchema
|
|
6
|
-
query?: ZodSchema
|
|
7
|
-
params?: ZodSchema
|
|
8
|
-
headers?: ZodSchema
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function validate(schemas: ValidationSchemas): Middleware {
|
|
12
|
-
return async (req, ctx, next) => {
|
|
13
|
-
const parsed: Record<string, unknown> = {}
|
|
14
|
-
const issues: { path: string[]; message: string }[] = []
|
|
15
|
-
|
|
16
|
-
if (schemas.params) {
|
|
17
|
-
const result = schemas.params.safeParse(ctx.params)
|
|
18
|
-
if (result.success) {
|
|
19
|
-
parsed.params = result.data
|
|
20
|
-
} else {
|
|
21
|
-
issues.push(...result.error.issues.map((i) => ({
|
|
22
|
-
path: ['params', ...i.path.map(String)],
|
|
23
|
-
message: i.message,
|
|
24
|
-
})))
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (schemas.query) {
|
|
29
|
-
const result = schemas.query.safeParse(ctx.query)
|
|
30
|
-
if (result.success) {
|
|
31
|
-
parsed.query = result.data
|
|
32
|
-
} else {
|
|
33
|
-
issues.push(...result.error.issues.map((i) => ({
|
|
34
|
-
path: ['query', ...i.path.map(String)],
|
|
35
|
-
message: i.message,
|
|
36
|
-
})))
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (schemas.headers) {
|
|
41
|
-
const rawHeaders: Record<string, string> = {}
|
|
42
|
-
req.headers.forEach((v, k) => { rawHeaders[k] = v })
|
|
43
|
-
const result = schemas.headers.safeParse(rawHeaders)
|
|
44
|
-
if (result.success) {
|
|
45
|
-
parsed.headers = result.data
|
|
46
|
-
} else {
|
|
47
|
-
issues.push(...result.error.issues.map((i) => ({
|
|
48
|
-
path: ['headers', ...i.path.map(String)],
|
|
49
|
-
message: i.message,
|
|
50
|
-
})))
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (schemas.body) {
|
|
55
|
-
if (req.body === null) {
|
|
56
|
-
issues.push({ path: ['body'], message: 'Request body is required' })
|
|
57
|
-
} else {
|
|
58
|
-
const bodyText = await req.text()
|
|
59
|
-
if (!bodyText && req.method !== 'GET' && req.method !== 'HEAD') {
|
|
60
|
-
issues.push({ path: ['body'], message: 'Request body is required' })
|
|
61
|
-
} else {
|
|
62
|
-
let bodyValue: unknown
|
|
63
|
-
try {
|
|
64
|
-
bodyValue = JSON.parse(bodyText)
|
|
65
|
-
} catch {
|
|
66
|
-
bodyValue = bodyText
|
|
67
|
-
}
|
|
68
|
-
const result = schemas.body.safeParse(bodyValue)
|
|
69
|
-
if (result.success) {
|
|
70
|
-
parsed.body = result.data
|
|
71
|
-
} else {
|
|
72
|
-
issues.push(...result.error.issues.map((i) => ({
|
|
73
|
-
path: ['body', ...i.path.map(String)],
|
|
74
|
-
message: i.message,
|
|
75
|
-
})))
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (issues.length > 0) {
|
|
82
|
-
return Response.json({ error: 'Validation failed', issues }, { status: 400 })
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
ctx.parsed = parsed
|
|
86
|
-
return next(req, ctx)
|
|
87
|
-
}
|
|
88
|
-
}
|