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.
Files changed (45) hide show
  1. package/README.md +168 -0
  2. package/dist/compress.d.ts +6 -0
  3. package/dist/cookie.d.ts +12 -0
  4. package/dist/index.d.ts +21 -0
  5. package/dist/index.js +1420 -0
  6. package/dist/middleware.d.ts +21 -0
  7. package/dist/rate-limit.d.ts +8 -0
  8. package/dist/router.d.ts +55 -0
  9. package/dist/serve.d.ts +19 -0
  10. package/dist/static.d.ts +7 -0
  11. package/dist/tsx.d.ts +17 -0
  12. package/dist/types.d.ts +9 -0
  13. package/dist/upload.d.ts +14 -0
  14. package/dist/validate.d.ts +9 -0
  15. package/package.json +14 -2
  16. package/AGENTS.md +0 -105
  17. package/compress.ts +0 -69
  18. package/cookie.ts +0 -58
  19. package/index.ts +0 -21
  20. package/middleware.ts +0 -178
  21. package/rate-limit.ts +0 -68
  22. package/router.ts +0 -702
  23. package/serve.ts +0 -126
  24. package/static.ts +0 -113
  25. package/test/compress.test.ts +0 -106
  26. package/test/cookie.test.ts +0 -79
  27. package/test/fixtures/pages/about/page.tsx +0 -3
  28. package/test/fixtures/pages/blog/[slug]/load.ts +0 -3
  29. package/test/fixtures/pages/blog/[slug]/page.tsx +0 -3
  30. package/test/fixtures/pages/blog/[slug]/route.ts +0 -7
  31. package/test/fixtures/pages/blog/layout.tsx +0 -3
  32. package/test/fixtures/pages/layout.tsx +0 -12
  33. package/test/fixtures/pages/page.tsx +0 -3
  34. package/test/middleware.test.ts +0 -407
  35. package/test/rate-limit.test.ts +0 -94
  36. package/test/static.test.ts +0 -93
  37. package/test/tsx.test.ts +0 -285
  38. package/test/unode.test.ts +0 -401
  39. package/test/upload.test.ts +0 -130
  40. package/test/validate.test.ts +0 -133
  41. package/tsconfig.json +0 -13
  42. package/tsx.ts +0 -354
  43. package/types.ts +0 -23
  44. package/upload.ts +0 -101
  45. package/validate.ts +0 -88
@@ -1,130 +0,0 @@
1
- import { describe, it, before, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { readFile, rm, mkdir } from 'node:fs/promises'
4
- import { resolve } from 'node:path'
5
- import { tmpdir } from 'node:os'
6
- import { Router } from '../router.ts'
7
- import { upload } from '../upload.ts'
8
-
9
- function createFormData(fields?: Record<string, string>, files?: Record<string, { name: string; data: string; type?: string }>): [Request, string] {
10
- const boundary = '----boundary123'
11
- const parts: string[] = []
12
-
13
- for (const [key, value] of Object.entries(fields ?? {})) {
14
- parts.push(`--${boundary}`)
15
- parts.push(`Content-Disposition: form-data; name="${key}"`)
16
- parts.push('')
17
- parts.push(value)
18
- }
19
-
20
- for (const [key, file] of Object.entries(files ?? {})) {
21
- parts.push(`--${boundary}`)
22
- parts.push(`Content-Disposition: form-data; name="${key}"; filename="${file.name}"`)
23
- if (file.type) parts.push(`Content-Type: ${file.type}`)
24
- parts.push('')
25
- parts.push(file.data)
26
- }
27
-
28
- parts.push(`--${boundary}--`)
29
- const body = parts.join('\r\n')
30
-
31
- const req = new Request('http://localhost/upload', {
32
- method: 'POST',
33
- headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
34
- body,
35
- })
36
-
37
- return [req, boundary]
38
- }
39
-
40
- describe('upload', () => {
41
- it('parses fields from multipart form', async () => {
42
- const r = new Router()
43
- .post('/upload', upload(), (req, ctx) => Response.json(ctx.parsed?.fields))
44
-
45
- const [req] = createFormData({ title: 'hello', desc: 'world' })
46
- const res = await r.handler()(req, { params: {}, query: {} })
47
- assert.equal(res.status, 200)
48
- const data = await res.json() as Record<string, string>
49
- assert.deepEqual(data, { title: 'hello', desc: 'world' })
50
- })
51
-
52
- it('parses files in memory', async () => {
53
- const r = new Router()
54
- .post('/upload', upload(), (req, ctx) => {
55
- const files = ctx.parsed?.files as Record<string, unknown>
56
- return Response.json(files)
57
- })
58
-
59
- const [req] = createFormData({}, {
60
- avatar: { name: 'photo.png', data: 'fakeimagedata', type: 'image/png' },
61
- })
62
- const res = await r.handler()(req, { params: {}, query: {} })
63
- assert.equal(res.status, 200)
64
- const data = await res.json() as Record<string, any>
65
- const file = data.avatar
66
- assert.equal(file.name, 'photo.png')
67
- assert.equal(file.type, 'image/png')
68
- assert.ok(file.size)
69
- assert.ok(file.buffer)
70
- })
71
-
72
- it('saves files to disk when dir is set', async () => {
73
- const uploadDir = resolve(tmpdir(), 'weifuwu-upload-test')
74
- await mkdir(uploadDir, { recursive: true })
75
-
76
- const r = new Router()
77
- .post('/upload', upload({ dir: uploadDir }), (req, ctx) => {
78
- const files = ctx.parsed?.files as Record<string, any>
79
- return Response.json(files)
80
- })
81
-
82
- const [req] = createFormData({}, {
83
- doc: { name: 'test.txt', data: 'file content' },
84
- })
85
- const res = await r.handler()(req, { params: {}, query: {} })
86
- const data = await res.json() as Record<string, any>
87
- assert.ok(data.doc.path)
88
- const saved = await readFile(data.doc.path, 'utf-8')
89
- assert.equal(saved, 'file content')
90
- await rm(uploadDir, { recursive: true, force: true })
91
- })
92
-
93
- it('rejects oversized files', async () => {
94
- const r = new Router()
95
- .post('/upload', upload({ maxFileSize: 5 }), () => new Response('ok'))
96
-
97
- const [req] = createFormData({}, {
98
- big: { name: 'big.txt', data: 'too large content' },
99
- })
100
- const res = await r.handler()(req, { params: {}, query: {} })
101
- assert.equal(res.status, 413)
102
- })
103
-
104
- it('rejects disallowed file types', async () => {
105
- const r = new Router()
106
- .post('/upload', upload({ allowedTypes: ['image/png'] }), () => new Response('ok'))
107
-
108
- const [req] = createFormData({}, {
109
- bad: { name: 'script.exe', data: 'evil', type: 'application/x-msdownload' },
110
- })
111
- const res = await r.handler()(req, { params: {}, query: {} })
112
- assert.equal(res.status, 415)
113
- })
114
-
115
- it('passes through non-multipart requests', async () => {
116
- let reached = false
117
- const r = new Router()
118
- .post('/upload', upload(), (req, ctx, next) => {
119
- reached = true
120
- return next(req, ctx)
121
- }, () => new Response('ok'))
122
-
123
- const res = await r.handler()(
124
- new Request('http://localhost/upload', { method: 'POST', body: 'plain text' }),
125
- { params: {}, query: {} },
126
- )
127
- assert.equal(res.status, 200)
128
- assert.equal(reached, true)
129
- })
130
- })
@@ -1,133 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { z } from 'zod'
4
- import { Router } from '../router.ts'
5
- import { validate } from '../validate.ts'
6
-
7
- describe('validate', () => {
8
- it('validates body with Zod schema', async () => {
9
- const r = new Router()
10
- .post('/users',
11
- validate({ body: z.object({ name: z.string(), age: z.number() }) }),
12
- async (req, ctx) => Response.json(ctx.parsed?.body),
13
- )
14
-
15
- const res = await r.handler()(
16
- new Request('http://localhost/users', {
17
- method: 'POST',
18
- headers: { 'Content-Type': 'application/json' },
19
- body: JSON.stringify({ name: 'Alice', age: 30 }),
20
- }),
21
- { params: {}, query: {} },
22
- )
23
- assert.equal(res.status, 200)
24
- const data = await res.json() as Record<string, unknown>
25
- assert.deepEqual(data, { name: 'Alice', age: 30 })
26
- })
27
-
28
- it('rejects invalid body with 400', async () => {
29
- const r = new Router()
30
- .post('/users',
31
- validate({ body: z.object({ name: z.string().min(1) }) }),
32
- () => new Response('ok'),
33
- )
34
-
35
- const res = await r.handler()(
36
- new Request('http://localhost/users', {
37
- method: 'POST',
38
- headers: { 'Content-Type': 'application/json' },
39
- body: JSON.stringify({ name: '' }),
40
- }),
41
- { params: {}, query: {} },
42
- )
43
- assert.equal(res.status, 400)
44
- const data = await res.json() as Record<string, unknown>
45
- assert.ok((data as any).issues)
46
- })
47
-
48
- it('validates query params', async () => {
49
- const r = new Router()
50
- .get('/search',
51
- validate({ query: z.object({ q: z.string(), page: z.coerce.number().optional() }) }),
52
- (req, ctx) => Response.json(ctx.parsed?.query),
53
- )
54
-
55
- const res = await r.handler()(
56
- new Request('http://localhost/search?q=hello&page=2'),
57
- { params: {}, query: { q: 'hello', page: '2' } },
58
- )
59
- assert.equal(res.status, 200)
60
- const data = await res.json() as Record<string, unknown>
61
- assert.deepEqual(data, { q: 'hello', page: 2 })
62
- })
63
-
64
- it('validates params', async () => {
65
- const r = new Router()
66
- .get('/:id',
67
- validate({ params: z.object({ id: z.string().length(24) }) }),
68
- (req, ctx) => Response.json(ctx.parsed?.params),
69
- )
70
-
71
- const res = await r.handler()(
72
- new Request('http://localhost/507f1f77bcf86cd799439011'),
73
- { params: { id: '507f1f77bcf86cd799439011' }, query: {} },
74
- )
75
- assert.equal(res.status, 200)
76
- const data = await res.json() as Record<string, unknown>
77
- assert.deepEqual(data, { id: '507f1f77bcf86cd799439011' })
78
- })
79
-
80
- it('rejects invalid params with 400', async () => {
81
- const r = new Router()
82
- .get('/:id',
83
- validate({ params: z.object({ id: z.string().length(24) }) }),
84
- () => new Response('ok'),
85
- )
86
-
87
- const res = await r.handler()(
88
- new Request('http://localhost/bad-id'),
89
- { params: { id: 'bad-id' }, query: {} },
90
- )
91
- assert.equal(res.status, 400)
92
- })
93
-
94
- it('validates both body and query simultaneously', async () => {
95
- const r = new Router()
96
- .post('/data',
97
- validate({
98
- body: z.object({ value: z.number() }),
99
- query: z.object({ token: z.string() }),
100
- }),
101
- (req, ctx) => Response.json(ctx.parsed),
102
- )
103
-
104
- const res = await r.handler()(
105
- new Request('http://localhost/data?token=abc', {
106
- method: 'POST',
107
- headers: { 'Content-Type': 'application/json' },
108
- body: JSON.stringify({ value: 42 }),
109
- }),
110
- { params: {}, query: { token: 'abc' } },
111
- )
112
- assert.equal(res.status, 200)
113
- const data = await res.json() as Record<string, unknown>
114
- assert.deepEqual(data, { body: { value: 42 }, query: { token: 'abc' } })
115
- })
116
-
117
- it('passes through when no schemas provided', async () => {
118
- const r = new Router()
119
- .post('/data',
120
- validate({}),
121
- () => new Response('ok'),
122
- )
123
-
124
- const res = await r.handler()(
125
- new Request('http://localhost/data', {
126
- method: 'POST',
127
- body: JSON.stringify({ x: 1 }),
128
- }),
129
- { params: {}, query: {} },
130
- )
131
- assert.equal(res.status, 200)
132
- })
133
- })
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "noEmit": true,
8
- "jsx": "react-jsx",
9
- "skipLibCheck": true,
10
- "allowImportingTsExtensions": true
11
- },
12
- "include": ["*.ts", "test/**/*.ts"]
13
- }
package/tsx.ts DELETED
@@ -1,354 +0,0 @@
1
- import { createElement } from 'react'
2
- import { renderToReadableStream } from 'react-dom/server'
3
- import * as esbuild from 'esbuild'
4
- import { readdirSync, statSync, existsSync, mkdirSync } from 'node:fs'
5
- import { join, relative, resolve, sep, dirname } from 'node:path'
6
- import { pathToFileURL } from 'node:url'
7
- import { createHash } from 'node:crypto'
8
- import { Router } from './router.ts'
9
- import type { Handler } from './types.ts'
10
-
11
- export interface TsxOptions {
12
- dir: string
13
- }
14
-
15
- type PageEntry = {
16
- route: string
17
- entryPath: string
18
- loadPath?: string
19
- layouts: string[]
20
- routePath?: string
21
- }
22
-
23
- // ── helpers ────────────────────────────────────────────────────────────────
24
-
25
- function id(s: string): string {
26
- return createHash('md5').update(s).digest('hex').slice(0, 8)
27
- }
28
-
29
- function concatUint8(chunks: Uint8Array[]): Uint8Array {
30
- const len = chunks.reduce((a, c) => a + c.length, 0)
31
- const out = new Uint8Array(len)
32
- let off = 0
33
- for (const c of chunks) {
34
- out.set(c, off)
35
- off += c.length
36
- }
37
- return out
38
- }
39
-
40
- async function readStream(stream: ReadableStream): Promise<string> {
41
- const chunks: Uint8Array[] = []
42
- const reader = stream.getReader()
43
- while (true) {
44
- const { done, value } = await reader.read()
45
- if (done) break
46
- chunks.push(value)
47
- }
48
- return new TextDecoder().decode(concatUint8(chunks))
49
- }
50
-
51
- // ── file scanning ──────────────────────────────────────────────────────────
52
-
53
- function scanPages(dir: string): PageEntry[] {
54
- const pages: PageEntry[] = []
55
-
56
- function walk(current: string) {
57
- let entries: string[]
58
- try {
59
- entries = readdirSync(current)
60
- } catch {
61
- return
62
- }
63
-
64
- const dirs: string[] = []
65
- for (const name of entries) {
66
- const full = join(current, name)
67
- const st = statSync(full)
68
- if (st.isDirectory()) {
69
- if (!name.startsWith('.')) dirs.push(full)
70
- }
71
- }
72
-
73
- // Check for page.tsx in this directory
74
- const pagePath = join(current, 'page.tsx')
75
- const tsPagePath = join(current, 'page.ts')
76
- let entryPath = ''
77
- if (existsSync(pagePath)) {
78
- entryPath = pagePath
79
- } else if (existsSync(tsPagePath)) {
80
- entryPath = tsPagePath
81
- }
82
-
83
- if (entryPath) {
84
- let relPath = relative(dir, entryPath).replace(sep, '/')
85
- // Remove page.tsx / page.ts suffix
86
- relPath = relPath.replace(/\/page\.tsx?$/, '')
87
- relPath = relPath.replace(/^page\.tsx?$/, '')
88
-
89
- const route = filePathToRoute(relPath)
90
- const layouts = resolveLayouts(current, dir)
91
- const loadPath = existsSync(join(current, 'load.ts'))
92
- ? join(current, 'load.ts') : undefined
93
- const rPath = existsSync(join(current, 'route.ts'))
94
- ? join(current, 'route.ts') : undefined
95
-
96
- pages.push({
97
- route,
98
- entryPath,
99
- loadPath,
100
- layouts,
101
- routePath: rPath,
102
- })
103
- }
104
-
105
- for (const d of dirs) walk(d)
106
- }
107
-
108
- walk(dir)
109
- return pages
110
- }
111
-
112
- function filePathToRoute(relPath: string): string {
113
- let route = relPath.replace(/\\/g, '/')
114
- // Remove page.tsx suffix => already done in scanPages
115
- // [...rest] → *
116
- route = route.replace(/\[\.\.\.(\w+)\]/g, '*')
117
- // [slug] → :slug
118
- route = route.replace(/\[(\w+)\]/g, ':$1')
119
- return route.startsWith('/') ? route : '/' + route
120
- }
121
-
122
- function resolveLayouts(dir: string, pagesDir: string): string[] {
123
- const layouts: string[] = []
124
- let current = dir
125
-
126
- while (current.startsWith(pagesDir)) {
127
- const p = join(current, 'layout.tsx')
128
- if (existsSync(p)) {
129
- layouts.push(p)
130
- }
131
- const parent = dirname(current)
132
- if (parent === current) break
133
- current = parent
134
- }
135
-
136
- // Return outermost first
137
- return layouts.reverse()
138
- }
139
-
140
- // ── compilation ────────────────────────────────────────────────────────────
141
-
142
- const esbId = '__weifuwu_tsx_build'
143
-
144
- async function compileAll(
145
- files: string[],
146
- outDir: string,
147
- platform: 'node' | 'browser',
148
- ): Promise<void> {
149
- const entryPoints: Record<string, string> = {}
150
- for (const f of files) {
151
- entryPoints[id(f)] = f
152
- }
153
-
154
- await esbuild.build({
155
- entryPoints,
156
- outdir: outDir,
157
- format: 'esm',
158
- platform: platform === 'node' ? 'node' : 'browser',
159
- jsx: 'automatic',
160
- jsxImportSource: 'react',
161
- bundle: platform === 'browser',
162
- write: true,
163
- allowOverwrite: true,
164
- })
165
- }
166
-
167
- function compiledUrl(filePath: string, outDir: string): string {
168
- const hash = id(join(outDir, id(filePath)))
169
- const p = join(outDir, id(filePath) + '.js')
170
- return pathToFileURL(p).href
171
- }
172
-
173
- // ── client bundle (lazy) ───────────────────────────────────────────────────
174
-
175
- const clientBundleCache = new Map<string, Uint8Array>()
176
- const clientRouteLog = new WeakMap<object, Set<string>>()
177
-
178
- async function getOrBuildClientBundle(
179
- entryPath: string,
180
- layoutPaths: string[],
181
- pagesDir: string,
182
- router: Router,
183
- ): Promise<{ url: string } | null> {
184
- const key = id(entryPath)
185
- const url = `/__wfw/client/${key}.js`
186
-
187
- if (!clientRouteLog.get(router)?.has(url)) {
188
- let buf = clientBundleCache.get(key)
189
-
190
- if (!buf) {
191
- try {
192
- const layoutsImport = layoutPaths.map((p, i) =>
193
- `import L${i} from${JSON.stringify(p)};`,
194
- ).join('')
195
- const layoutsWrap = layoutPaths.map((_, i) => {
196
- const idx = layoutPaths.length - 1 - i
197
- return `el=createElement(L${idx},null,el);`
198
- }).join('')
199
-
200
- const code = [
201
- `import{hydrateRoot}from'react-dom/client';`,
202
- `import{createElement}from'react';`,
203
- `import P from${JSON.stringify(entryPath)};`,
204
- layoutsImport,
205
- `const p=window.__WEIFUWU_PROPS;`,
206
- `let el=createElement(P,p);`,
207
- layoutsWrap,
208
- `hydrateRoot(document.getElementById('__weifuwu_root'),el);`,
209
- ].join('')
210
-
211
- const result = await esbuild.build({
212
- stdin: { contents: code, loader: 'tsx', resolveDir: pagesDir },
213
- bundle: true,
214
- format: 'esm',
215
- jsx: 'automatic',
216
- jsxImportSource: 'react',
217
- write: false,
218
- minify: true,
219
- })
220
-
221
- buf = result.outputFiles[0].contents
222
- clientBundleCache.set(key, buf)
223
- } catch (err) {
224
- console.error('hydration bundle failed:', err)
225
- return null
226
- }
227
- }
228
-
229
- router.get(url, () => new Response(buf! as BodyInit, {
230
- headers: { 'content-type': 'application/javascript; charset=utf-8' },
231
- }))
232
-
233
- const set = clientRouteLog.get(router) ?? new Set()
234
- set.add(url)
235
- clientRouteLog.set(router, set)
236
- }
237
-
238
- return { url }
239
- }
240
-
241
- // ── SSR handler ────────────────────────────────────────────────────────────
242
-
243
- function makeSsrHandler(
244
- Component: any,
245
- loadFn: any | undefined,
246
- layouts: any[],
247
- entryPath: string,
248
- layoutPaths: string[],
249
- pagesDir: string,
250
- router: Router,
251
- ): Handler {
252
- return async (req, ctx) => {
253
- const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {}
254
- const allProps = { ...loadProps, params: ctx.params, query: ctx.query }
255
-
256
- let element = createElement(Component, allProps)
257
- for (let i = layouts.length - 1; i >= 0; i--) {
258
- element = createElement(layouts[i], null, element)
259
- }
260
-
261
- const stream = await renderToReadableStream(element)
262
- const body = await readStream(stream)
263
-
264
- const scripts: string[] = []
265
- scripts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(allProps)}</script>`)
266
-
267
- const bundle = await getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router)
268
- if (bundle) {
269
- scripts.push(`<script type="module" src="${bundle.url}"></script>`)
270
- }
271
-
272
- const html = `<!DOCTYPE html>\n${body}\n${scripts.join('\n')}`
273
-
274
- return new Response(html, {
275
- headers: { 'content-type': 'text/html; charset=utf-8' },
276
- })
277
- }
278
- }
279
-
280
- // ── main export ────────────────────────────────────────────────────────────
281
-
282
- export async function tsx(options: TsxOptions): Promise<Router> {
283
- const pagesDir = resolve(options.dir)
284
- const outDir = join(pagesDir, '..', '.weifuwu', 'ssr')
285
- const clientDir = join(pagesDir, '..', '.weifuwu', 'client')
286
-
287
- // 1. Scan
288
- const pages = scanPages(pagesDir)
289
- if (pages.length === 0) return new Router()
290
-
291
- // 2. Collect all files to compile
292
- const allFiles = new Set<string>()
293
- const loadMap = new Map<string, string>()
294
- const layoutMap = new Map<string, string[]>()
295
-
296
- for (const p of pages) {
297
- allFiles.add(p.entryPath)
298
- if (p.loadPath) {
299
- allFiles.add(p.loadPath)
300
- loadMap.set(p.entryPath, p.loadPath)
301
- }
302
- for (const lp of p.layouts) allFiles.add(lp)
303
- layoutMap.set(p.entryPath, [...p.layouts])
304
-
305
- if (p.routePath) allFiles.add(p.routePath)
306
- }
307
-
308
- // 3. Compile for SSR
309
- mkdirSync(outDir, { recursive: true })
310
- await compileAll([...allFiles], outDir, 'node')
311
-
312
- // 4. Import and register routes
313
- const router = new Router()
314
-
315
- for (const p of pages) {
316
- const url = compiledUrl(p.entryPath, outDir)
317
- const mod = await import(url)
318
- const Component = mod.default
319
-
320
- let loadFn: any
321
- if (p.loadPath) {
322
- const loadUrl = compiledUrl(p.loadPath, outDir)
323
- const modLoad = await import(loadUrl)
324
- loadFn = modLoad.default
325
- }
326
-
327
- const layoutComponents: any[] = []
328
- for (const lp of p.layouts) {
329
- const lUrl = compiledUrl(lp, outDir)
330
- const modL = await import(lUrl)
331
- layoutComponents.push(modL.default)
332
- }
333
-
334
- const handler = makeSsrHandler(
335
- Component, loadFn, layoutComponents,
336
- p.entryPath, p.layouts, pagesDir, router,
337
- )
338
- router.get(p.route, handler)
339
-
340
- // route.ts — skip GET (handled by page.tsx SSR)
341
- if (p.routePath) {
342
- const rUrl = compiledUrl(p.routePath, outDir)
343
- const modR = await import(rUrl)
344
- const methods = (['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as const)
345
- for (const method of methods) {
346
- if (modR[method]) {
347
- router.route(method, p.route, modR[method])
348
- }
349
- }
350
- }
351
- }
352
-
353
- return router
354
- }
package/types.ts DELETED
@@ -1,23 +0,0 @@
1
- export interface Context {
2
- params: Record<string, string>
3
- query: Record<string, string>
4
- user?: unknown
5
- parsed?: Record<string, unknown>
6
- }
7
-
8
- export type Handler = (
9
- req: Request,
10
- ctx: Context,
11
- ) => Response | Promise<Response>
12
-
13
- export type Middleware = (
14
- req: Request,
15
- ctx: Context,
16
- next: Handler,
17
- ) => Response | Promise<Response>
18
-
19
- export type ErrorHandler = (
20
- error: Error,
21
- req: Request,
22
- ctx: Context,
23
- ) => Response