spiceflow 1.1.8 → 1.1.10
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 +177 -92
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +9 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/utils.js.map +1 -1
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +1 -3
- package/dist/client/ws.js.map +1 -1
- package/dist/client.test.js +1 -1
- package/dist/client.test.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js.map +1 -1
- package/dist/cors.test.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/middleware.test.js.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +1 -1
- package/dist/openapi.js.map +1 -1
- package/dist/openapi.test.js.map +1 -1
- package/dist/simple.benchmark.d.ts +2 -0
- package/dist/simple.benchmark.d.ts.map +1 -0
- package/dist/{benchmark.benchmark.js → simple.benchmark.js} +1 -1
- package/dist/simple.benchmark.js.map +1 -0
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +14 -2
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/static-node.d.ts +4 -0
- package/dist/static-node.d.ts.map +1 -0
- package/dist/static-node.js +35 -0
- package/dist/static-node.js.map +1 -0
- package/dist/static.benchmark.d.ts +2 -0
- package/dist/static.benchmark.d.ts.map +1 -0
- package/dist/static.benchmark.js +19 -0
- package/dist/static.benchmark.js.map +1 -0
- package/dist/static.d.ts +28 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +181 -0
- package/dist/static.js.map +1 -0
- package/dist/stream.test.js +1 -1
- package/dist/stream.test.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/types.test.js +3 -7
- package/dist/types.test.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/zod.test.js.map +1 -1
- package/package.json +1 -1
- package/src/client/errors.ts +17 -17
- package/src/client/index.ts +437 -469
- package/src/client/types.ts +168 -191
- package/src/client/utils.ts +5 -5
- package/src/client/ws.ts +87 -89
- package/src/client.test.ts +176 -183
- package/src/context.ts +82 -82
- package/src/cors.test.ts +38 -38
- package/src/cors.ts +87 -92
- package/src/error.ts +13 -13
- package/src/middleware.test.ts +201 -201
- package/src/openapi.test.ts +97 -97
- package/src/openapi.ts +365 -365
- package/src/simple.benchmark.ts +16 -0
- package/src/spiceflow.test.ts +461 -467
- package/src/spiceflow.ts +1132 -1161
- package/src/static-node.ts +38 -0
- package/src/static.benchmark.ts +22 -0
- package/src/static.ts +240 -0
- package/src/stream.test.ts +310 -310
- package/src/types.test.ts +46 -50
- package/src/types.ts +698 -701
- package/src/utils.ts +79 -79
- package/src/zod.test.ts +64 -64
- package/dist/benchmark.benchmark.d.ts +0 -2
- package/dist/benchmark.benchmark.d.ts.map +0 -1
- package/dist/benchmark.benchmark.js.map +0 -1
- package/src/benchmark.benchmark.ts +0 -16
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { stat } from 'fs/promises'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { ServeStaticOptions, serveStatic as baseServeStatic } from './static.js'
|
|
4
|
+
import { MiddlewareHandler } from './types.js'
|
|
5
|
+
|
|
6
|
+
export const serveStatic = (options: ServeStaticOptions): MiddlewareHandler => {
|
|
7
|
+
const getContent = (path: string) => {
|
|
8
|
+
path = `./${path}`
|
|
9
|
+
try {
|
|
10
|
+
return fs.readFileSync(path)
|
|
11
|
+
} catch (err: any) {
|
|
12
|
+
if (err.code !== 'ENOENT') {
|
|
13
|
+
throw err
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
const pathResolve = (path: string) => {
|
|
19
|
+
return `./${path}`
|
|
20
|
+
}
|
|
21
|
+
const isDir = (path: string) => {
|
|
22
|
+
let isDir
|
|
23
|
+
try {
|
|
24
|
+
const stats = fs.statSync(path)
|
|
25
|
+
isDir = stats.isDirectory()
|
|
26
|
+
} catch {}
|
|
27
|
+
return isDir
|
|
28
|
+
}
|
|
29
|
+
const m = baseServeStatic({
|
|
30
|
+
...options,
|
|
31
|
+
getContent,
|
|
32
|
+
pathResolve,
|
|
33
|
+
isDir,
|
|
34
|
+
})
|
|
35
|
+
return function serveStatic(c, next) {
|
|
36
|
+
return m(c, next)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { bench } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Spiceflow } from './spiceflow.js'
|
|
4
|
+
import { serveStatic } from './static-node.js'
|
|
5
|
+
|
|
6
|
+
bench('Spiceflow static', async () => {
|
|
7
|
+
const app = new Spiceflow()
|
|
8
|
+
|
|
9
|
+
app
|
|
10
|
+
.use(serveStatic({ root: '.' })) //
|
|
11
|
+
.get('/hello', () => {
|
|
12
|
+
return { message: 'Hello, World!' }
|
|
13
|
+
})
|
|
14
|
+
.get('/hellox', () => {
|
|
15
|
+
return { message: 'Hello, World!' }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const request = new Request('http://localhost/src/cors.ts')
|
|
19
|
+
for (let i = 0; i < 10000; i++) {
|
|
20
|
+
await app.handle(request)
|
|
21
|
+
}
|
|
22
|
+
})
|
package/src/static.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { MiddlewareHandler } from './types.js'
|
|
2
|
+
|
|
3
|
+
type Env = {}
|
|
4
|
+
type Context<E extends Env = Env> = {}
|
|
5
|
+
type Data = any
|
|
6
|
+
|
|
7
|
+
export type ServeStaticOptions<E extends Env = Env> = {
|
|
8
|
+
root?: string
|
|
9
|
+
|
|
10
|
+
// path?: string
|
|
11
|
+
mimes?: Record<string, string>
|
|
12
|
+
// rewriteRequestPath?: (path: string) => string
|
|
13
|
+
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_DOCUMENT = 'index.html'
|
|
17
|
+
const defaultPathResolve = (path: string) => path
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This middleware is not directly used by the user. Create a wrapper specifying `getContent()` by the environment such as Deno or Bun.
|
|
21
|
+
*/
|
|
22
|
+
export const serveStatic = <E extends Env = Env>(
|
|
23
|
+
options: ServeStaticOptions<E> & {
|
|
24
|
+
getContent: (
|
|
25
|
+
path: string,
|
|
26
|
+
c: Context<E>,
|
|
27
|
+
) => Data | Response | null | Promise<Data | Response | null>
|
|
28
|
+
pathResolve?: (path: string) => string
|
|
29
|
+
isDir?: (path: string) => boolean | undefined | Promise<boolean | undefined>
|
|
30
|
+
},
|
|
31
|
+
): MiddlewareHandler => {
|
|
32
|
+
return async (c, next) => {
|
|
33
|
+
let filename = decodeURI(new URL(c.request.url).pathname)
|
|
34
|
+
// filename = options.rewriteRequestPath
|
|
35
|
+
// ? options.rewriteRequestPath(filename)
|
|
36
|
+
// : filename
|
|
37
|
+
const root = options.root
|
|
38
|
+
|
|
39
|
+
// If it was Directory, force `/` on the end.
|
|
40
|
+
if (!filename.endsWith('/') && options.isDir) {
|
|
41
|
+
const path = getFilePathWithoutDefaultDocument({
|
|
42
|
+
filename,
|
|
43
|
+
root,
|
|
44
|
+
})
|
|
45
|
+
if (path && (await options.isDir(path))) {
|
|
46
|
+
filename = filename + '/'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let path = getFilePath({
|
|
51
|
+
filename,
|
|
52
|
+
root,
|
|
53
|
+
defaultDocument: DEFAULT_DOCUMENT,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if (!path) {
|
|
57
|
+
return await next()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const getContent = options.getContent
|
|
61
|
+
const pathResolve = options.pathResolve ?? defaultPathResolve
|
|
62
|
+
|
|
63
|
+
path = pathResolve(path)
|
|
64
|
+
let content = await getContent(path, c)
|
|
65
|
+
|
|
66
|
+
if (!content) {
|
|
67
|
+
let pathWithOutDefaultDocument = getFilePathWithoutDefaultDocument({
|
|
68
|
+
filename,
|
|
69
|
+
root,
|
|
70
|
+
})
|
|
71
|
+
if (!pathWithOutDefaultDocument) {
|
|
72
|
+
return await next()
|
|
73
|
+
}
|
|
74
|
+
pathWithOutDefaultDocument = pathResolve(pathWithOutDefaultDocument)
|
|
75
|
+
|
|
76
|
+
if (pathWithOutDefaultDocument !== path) {
|
|
77
|
+
content = await getContent(pathWithOutDefaultDocument, c)
|
|
78
|
+
if (content) {
|
|
79
|
+
path = pathWithOutDefaultDocument
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (content instanceof Response) {
|
|
85
|
+
return content
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (content) {
|
|
89
|
+
let mimeType: string | undefined
|
|
90
|
+
mimeType = getMimeType(path, options.mimes)
|
|
91
|
+
let response = new Response(content)
|
|
92
|
+
if (mimeType) {
|
|
93
|
+
response.headers.set('Content-Type', mimeType)
|
|
94
|
+
}
|
|
95
|
+
return response
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await options.onNotFound?.(path, c)
|
|
99
|
+
await next()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const baseMimes: Record<string, string> = {
|
|
105
|
+
aac: 'audio/aac',
|
|
106
|
+
avi: 'video/x-msvideo',
|
|
107
|
+
avif: 'image/avif',
|
|
108
|
+
av1: 'video/av1',
|
|
109
|
+
bin: 'application/octet-stream',
|
|
110
|
+
bmp: 'image/bmp',
|
|
111
|
+
css: 'text/css',
|
|
112
|
+
csv: 'text/csv',
|
|
113
|
+
eot: 'application/vnd.ms-fontobject',
|
|
114
|
+
epub: 'application/epub+zip',
|
|
115
|
+
gif: 'image/gif',
|
|
116
|
+
gz: 'application/gzip',
|
|
117
|
+
htm: 'text/html',
|
|
118
|
+
html: 'text/html',
|
|
119
|
+
ico: 'image/x-icon',
|
|
120
|
+
ics: 'text/calendar',
|
|
121
|
+
jpeg: 'image/jpeg',
|
|
122
|
+
jpg: 'image/jpeg',
|
|
123
|
+
js: 'text/javascript',
|
|
124
|
+
json: 'application/json',
|
|
125
|
+
jsonld: 'application/ld+json',
|
|
126
|
+
map: 'application/json',
|
|
127
|
+
mid: 'audio/x-midi',
|
|
128
|
+
midi: 'audio/x-midi',
|
|
129
|
+
mjs: 'text/javascript',
|
|
130
|
+
mp3: 'audio/mpeg',
|
|
131
|
+
mp4: 'video/mp4',
|
|
132
|
+
mpeg: 'video/mpeg',
|
|
133
|
+
oga: 'audio/ogg',
|
|
134
|
+
ogv: 'video/ogg',
|
|
135
|
+
ogx: 'application/ogg',
|
|
136
|
+
opus: 'audio/opus',
|
|
137
|
+
otf: 'font/otf',
|
|
138
|
+
pdf: 'application/pdf',
|
|
139
|
+
png: 'image/png',
|
|
140
|
+
rtf: 'application/rtf',
|
|
141
|
+
svg: 'image/svg+xml',
|
|
142
|
+
tif: 'image/tiff',
|
|
143
|
+
tiff: 'image/tiff',
|
|
144
|
+
ts: 'video/mp2t',
|
|
145
|
+
ttf: 'font/ttf',
|
|
146
|
+
txt: 'text/plain',
|
|
147
|
+
wasm: 'application/wasm',
|
|
148
|
+
webm: 'video/webm',
|
|
149
|
+
weba: 'audio/webm',
|
|
150
|
+
webp: 'image/webp',
|
|
151
|
+
woff: 'font/woff',
|
|
152
|
+
woff2: 'font/woff2',
|
|
153
|
+
xhtml: 'application/xhtml+xml',
|
|
154
|
+
xml: 'application/xml',
|
|
155
|
+
zip: 'application/zip',
|
|
156
|
+
'3gp': 'video/3gpp',
|
|
157
|
+
'3g2': 'video/3gpp2',
|
|
158
|
+
gltf: 'model/gltf+json',
|
|
159
|
+
glb: 'model/gltf-binary',
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const getMimeType = (
|
|
163
|
+
filename: string,
|
|
164
|
+
|
|
165
|
+
mimes2?: Record<string, string>,
|
|
166
|
+
): string | undefined => {
|
|
167
|
+
const regexp = /\.([a-zA-Z0-9]+?)$/
|
|
168
|
+
const match = filename.match(regexp)
|
|
169
|
+
if (!match) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
let mimeType = mimes2?.[match[1]] ?? baseMimes[match[1]]
|
|
173
|
+
if (
|
|
174
|
+
(mimeType && mimeType.startsWith('text')) ||
|
|
175
|
+
mimeType === 'application/json'
|
|
176
|
+
) {
|
|
177
|
+
mimeType += '; charset=utf-8'
|
|
178
|
+
}
|
|
179
|
+
return mimeType
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const getExtension = (mimeType: string): string | undefined => {
|
|
183
|
+
for (const ext in baseMimes) {
|
|
184
|
+
if (baseMimes[ext] === mimeType) {
|
|
185
|
+
return ext
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type FilePathOptions = {
|
|
191
|
+
filename: string
|
|
192
|
+
root?: string
|
|
193
|
+
defaultDocument?: string
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const getFilePath = (options: FilePathOptions): string | undefined => {
|
|
197
|
+
let filename = options.filename
|
|
198
|
+
const defaultDocument = options.defaultDocument || 'index.html'
|
|
199
|
+
|
|
200
|
+
if (filename.endsWith('/')) {
|
|
201
|
+
// /top/ => /top/index.html
|
|
202
|
+
filename = filename.concat(defaultDocument)
|
|
203
|
+
} else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
|
|
204
|
+
// /top => /top/index.html
|
|
205
|
+
filename = filename.concat('/' + defaultDocument)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const path = getFilePathWithoutDefaultDocument({
|
|
209
|
+
root: options.root,
|
|
210
|
+
filename,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return path
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const getFilePathWithoutDefaultDocument = (
|
|
217
|
+
options: Omit<FilePathOptions, 'defaultDocument'>,
|
|
218
|
+
): string | undefined => {
|
|
219
|
+
let root = options.root || ''
|
|
220
|
+
let filename = options.filename
|
|
221
|
+
|
|
222
|
+
if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// /foo.html => foo.html
|
|
227
|
+
filename = filename.replace(/^\.?[\/\\]/, '')
|
|
228
|
+
|
|
229
|
+
// foo\bar.txt => foo/bar.txt
|
|
230
|
+
filename = filename.replace(/\\/, '/')
|
|
231
|
+
|
|
232
|
+
// assets/ => assets
|
|
233
|
+
root = root.replace(/\/$/, '')
|
|
234
|
+
|
|
235
|
+
// ./assets/foo.html => assets/foo.html
|
|
236
|
+
let path = root ? root + '/' + filename : filename
|
|
237
|
+
path = path.replace(/^\.?\//, '')
|
|
238
|
+
|
|
239
|
+
return path
|
|
240
|
+
}
|