weifuwu 0.2.2 → 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 +56 -5
  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 -701
  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 -374
  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,374 +0,0 @@
1
- import { createElement, createContext, useContext } 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 { Context, Handler } from './types.ts'
10
-
11
- export interface TsxOptions {
12
- dir: string
13
- }
14
-
15
- export const TsxContext = createContext<{
16
- params: Record<string, string>
17
- query: Record<string, string>
18
- user?: unknown
19
- parsed?: Record<string, unknown>
20
- }>({ params: {}, query: {} })
21
-
22
- export function useTsx() {
23
- return useContext(TsxContext)
24
- }
25
-
26
- type PageEntry = {
27
- route: string
28
- entryPath: string
29
- loadPath?: string
30
- layouts: string[]
31
- routePath?: string
32
- }
33
-
34
- // ── helpers ────────────────────────────────────────────────────────────────
35
-
36
- function id(s: string): string {
37
- return createHash('md5').update(s).digest('hex').slice(0, 8)
38
- }
39
-
40
- function concatUint8(chunks: Uint8Array[]): Uint8Array {
41
- const len = chunks.reduce((a, c) => a + c.length, 0)
42
- const out = new Uint8Array(len)
43
- let off = 0
44
- for (const c of chunks) {
45
- out.set(c, off)
46
- off += c.length
47
- }
48
- return out
49
- }
50
-
51
- async function readStream(stream: ReadableStream): Promise<string> {
52
- const chunks: Uint8Array[] = []
53
- const reader = stream.getReader()
54
- while (true) {
55
- const { done, value } = await reader.read()
56
- if (done) break
57
- chunks.push(value)
58
- }
59
- return new TextDecoder().decode(concatUint8(chunks))
60
- }
61
-
62
- // ── file scanning ──────────────────────────────────────────────────────────
63
-
64
- function scanPages(dir: string): PageEntry[] {
65
- const pages: PageEntry[] = []
66
-
67
- function walk(current: string) {
68
- let entries: string[]
69
- try {
70
- entries = readdirSync(current)
71
- } catch {
72
- return
73
- }
74
-
75
- const dirs: string[] = []
76
- for (const name of entries) {
77
- const full = join(current, name)
78
- const st = statSync(full)
79
- if (st.isDirectory()) {
80
- if (!name.startsWith('.')) dirs.push(full)
81
- }
82
- }
83
-
84
- // Check for page.tsx in this directory
85
- const pagePath = join(current, 'page.tsx')
86
- const tsPagePath = join(current, 'page.ts')
87
- let entryPath = ''
88
- if (existsSync(pagePath)) {
89
- entryPath = pagePath
90
- } else if (existsSync(tsPagePath)) {
91
- entryPath = tsPagePath
92
- }
93
-
94
- if (entryPath) {
95
- let relPath = relative(dir, entryPath).replace(sep, '/')
96
- // Remove page.tsx / page.ts suffix
97
- relPath = relPath.replace(/\/page\.tsx?$/, '')
98
- relPath = relPath.replace(/^page\.tsx?$/, '')
99
-
100
- const route = filePathToRoute(relPath)
101
- const layouts = resolveLayouts(current, dir)
102
- const loadPath = existsSync(join(current, 'load.ts'))
103
- ? join(current, 'load.ts') : undefined
104
- const rPath = existsSync(join(current, 'route.ts'))
105
- ? join(current, 'route.ts') : undefined
106
-
107
- pages.push({
108
- route,
109
- entryPath,
110
- loadPath,
111
- layouts,
112
- routePath: rPath,
113
- })
114
- }
115
-
116
- for (const d of dirs) walk(d)
117
- }
118
-
119
- walk(dir)
120
- return pages
121
- }
122
-
123
- function filePathToRoute(relPath: string): string {
124
- let route = relPath.replace(/\\/g, '/')
125
- // Remove page.tsx suffix => already done in scanPages
126
- // [...rest] → *
127
- route = route.replace(/\[\.\.\.(\w+)\]/g, '*')
128
- // [slug] → :slug
129
- route = route.replace(/\[(\w+)\]/g, ':$1')
130
- return route.startsWith('/') ? route : '/' + route
131
- }
132
-
133
- function resolveLayouts(dir: string, pagesDir: string): string[] {
134
- const layouts: string[] = []
135
- let current = dir
136
-
137
- while (current.startsWith(pagesDir)) {
138
- const p = join(current, 'layout.tsx')
139
- if (existsSync(p)) {
140
- layouts.push(p)
141
- }
142
- const parent = dirname(current)
143
- if (parent === current) break
144
- current = parent
145
- }
146
-
147
- // Return outermost first
148
- return layouts.reverse()
149
- }
150
-
151
- // ── compilation ────────────────────────────────────────────────────────────
152
-
153
- const esbId = '__weifuwu_tsx_build'
154
-
155
- async function compileAll(
156
- files: string[],
157
- outDir: string,
158
- platform: 'node' | 'browser',
159
- ): Promise<void> {
160
- const entryPoints: Record<string, string> = {}
161
- for (const f of files) {
162
- entryPoints[id(f)] = f
163
- }
164
-
165
- await esbuild.build({
166
- entryPoints,
167
- outdir: outDir,
168
- format: 'esm',
169
- platform: platform === 'node' ? 'node' : 'browser',
170
- jsx: 'automatic',
171
- jsxImportSource: 'react',
172
- bundle: platform === 'browser',
173
- write: true,
174
- allowOverwrite: true,
175
- })
176
- }
177
-
178
- function compiledUrl(filePath: string, outDir: string): string {
179
- const hash = id(join(outDir, id(filePath)))
180
- const p = join(outDir, id(filePath) + '.js')
181
- return pathToFileURL(p).href
182
- }
183
-
184
- // ── client bundle (lazy) ───────────────────────────────────────────────────
185
-
186
- const clientBundleCache = new Map<string, Uint8Array>()
187
- const clientRouteLog = new WeakMap<object, Set<string>>()
188
-
189
- async function getOrBuildClientBundle(
190
- entryPath: string,
191
- layoutPaths: string[],
192
- pagesDir: string,
193
- router: Router,
194
- ): Promise<{ url: string } | null> {
195
- const key = id(entryPath)
196
- const url = `/__wfw/client/${key}.js`
197
-
198
- if (!clientRouteLog.get(router)?.has(url)) {
199
- let buf = clientBundleCache.get(key)
200
-
201
- if (!buf) {
202
- try {
203
- const nested = layoutPaths.slice(1)
204
- const layoutsImport = nested.map((p, i) =>
205
- `import L${i} from${JSON.stringify(p)};`,
206
- ).join('')
207
- const layoutsWrap = nested.map((_, i) => {
208
- const idx = nested.length - 1 - i
209
- return `el=createElement(L${idx},null,el);`
210
- }).join('')
211
-
212
- const code = [
213
- `import{hydrateRoot}from'react-dom/client';`,
214
- `import{createElement}from'react';`,
215
- `import P from${JSON.stringify(entryPath)};`,
216
- layoutsImport,
217
- `const p=window.__WEIFUWU_PROPS;`,
218
- `let el=createElement(P,p);`,
219
- layoutsWrap,
220
- `hydrateRoot(document.getElementById('__weifuwu_root'),el);`,
221
- ].join('')
222
-
223
- const result = await esbuild.build({
224
- stdin: { contents: code, loader: 'tsx', resolveDir: pagesDir },
225
- bundle: true,
226
- format: 'esm',
227
- jsx: 'automatic',
228
- jsxImportSource: 'react',
229
- write: false,
230
- minify: true,
231
- })
232
-
233
- buf = result.outputFiles[0].contents
234
- clientBundleCache.set(key, buf)
235
- } catch (err) {
236
- console.error('hydration bundle failed:', err)
237
- return null
238
- }
239
- }
240
-
241
- router.get(url, () => new Response(buf! as BodyInit, {
242
- headers: { 'content-type': 'application/javascript; charset=utf-8' },
243
- }))
244
-
245
- const set = clientRouteLog.get(router) ?? new Set()
246
- set.add(url)
247
- clientRouteLog.set(router, set)
248
- }
249
-
250
- return { url }
251
- }
252
-
253
- // ── SSR handler ────────────────────────────────────────────────────────────
254
-
255
- function makeSsrHandler(
256
- Component: any,
257
- loadFn: any | undefined,
258
- layouts: any[],
259
- entryPath: string,
260
- layoutPaths: string[],
261
- pagesDir: string,
262
- router: Router,
263
- ): Handler {
264
- return async (req, ctx) => {
265
- const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {}
266
- const allProps = { ...loadProps, params: ctx.params, query: ctx.query }
267
-
268
- let element = createElement(Component, allProps)
269
- for (let i = layouts.length - 1; i >= 0; i--) {
270
- const isRoot = i === 0
271
- element = createElement(
272
- layouts[i],
273
- isRoot ? { children: element, req, ctx } : { children: element },
274
- )
275
- }
276
-
277
- element = createElement(TsxContext.Provider, {
278
- value: { params: ctx.params, query: ctx.query, user: ctx.user, parsed: ctx.parsed },
279
- }, element)
280
-
281
- const stream = await renderToReadableStream(element)
282
- const body = await readStream(stream)
283
-
284
- const scripts: string[] = []
285
- scripts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(allProps)}</script>`)
286
-
287
- const bundle = await getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router)
288
- if (bundle) {
289
- scripts.push(`<script type="module" src="${bundle.url}"></script>`)
290
- }
291
-
292
- const html = `<!DOCTYPE html>\n${body}\n${scripts.join('\n')}`
293
-
294
- return new Response(html, {
295
- headers: { 'content-type': 'text/html; charset=utf-8' },
296
- })
297
- }
298
- }
299
-
300
- // ── main export ────────────────────────────────────────────────────────────
301
-
302
- export async function tsx(options: TsxOptions): Promise<Router> {
303
- const pagesDir = resolve(options.dir)
304
- const outDir = join(pagesDir, '..', '.weifuwu', 'ssr')
305
- const clientDir = join(pagesDir, '..', '.weifuwu', 'client')
306
-
307
- // 1. Scan
308
- const pages = scanPages(pagesDir)
309
- if (pages.length === 0) return new Router()
310
-
311
- // 2. Collect all files to compile
312
- const allFiles = new Set<string>()
313
- const loadMap = new Map<string, string>()
314
- const layoutMap = new Map<string, string[]>()
315
-
316
- for (const p of pages) {
317
- allFiles.add(p.entryPath)
318
- if (p.loadPath) {
319
- allFiles.add(p.loadPath)
320
- loadMap.set(p.entryPath, p.loadPath)
321
- }
322
- for (const lp of p.layouts) allFiles.add(lp)
323
- layoutMap.set(p.entryPath, [...p.layouts])
324
-
325
- if (p.routePath) allFiles.add(p.routePath)
326
- }
327
-
328
- // 3. Compile for SSR
329
- mkdirSync(outDir, { recursive: true })
330
- await compileAll([...allFiles], outDir, 'node')
331
-
332
- // 4. Import and register routes
333
- const router = new Router()
334
-
335
- for (const p of pages) {
336
- const url = compiledUrl(p.entryPath, outDir)
337
- const mod = await import(url)
338
- const Component = mod.default
339
-
340
- let loadFn: any
341
- if (p.loadPath) {
342
- const loadUrl = compiledUrl(p.loadPath, outDir)
343
- const modLoad = await import(loadUrl)
344
- loadFn = modLoad.default
345
- }
346
-
347
- const layoutComponents: any[] = []
348
- for (const lp of p.layouts) {
349
- const lUrl = compiledUrl(lp, outDir)
350
- const modL = await import(lUrl)
351
- layoutComponents.push(modL.default)
352
- }
353
-
354
- const handler = makeSsrHandler(
355
- Component, loadFn, layoutComponents,
356
- p.entryPath, p.layouts, pagesDir, router,
357
- )
358
- router.get(p.route, handler)
359
-
360
- // route.ts — skip GET (handled by page.tsx SSR)
361
- if (p.routePath) {
362
- const rUrl = compiledUrl(p.routePath, outDir)
363
- const modR = await import(rUrl)
364
- const methods = (['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as const)
365
- for (const method of methods) {
366
- if (modR[method]) {
367
- router.route(method, p.route, modR[method])
368
- }
369
- }
370
- }
371
- }
372
-
373
- return router
374
- }
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