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.
Files changed (82) hide show
  1. package/README.md +177 -92
  2. package/dist/client/errors.d.ts.map +1 -1
  3. package/dist/client/errors.js.map +1 -1
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/index.js +9 -13
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/client/types.d.ts.map +1 -1
  8. package/dist/client/utils.js.map +1 -1
  9. package/dist/client/ws.d.ts.map +1 -1
  10. package/dist/client/ws.js +1 -3
  11. package/dist/client/ws.js.map +1 -1
  12. package/dist/client.test.js +1 -1
  13. package/dist/client.test.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/cors.d.ts.map +1 -1
  16. package/dist/cors.js.map +1 -1
  17. package/dist/cors.test.js.map +1 -1
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/error.js.map +1 -1
  20. package/dist/middleware.test.js.map +1 -1
  21. package/dist/openapi.d.ts.map +1 -1
  22. package/dist/openapi.js +1 -1
  23. package/dist/openapi.js.map +1 -1
  24. package/dist/openapi.test.js.map +1 -1
  25. package/dist/simple.benchmark.d.ts +2 -0
  26. package/dist/simple.benchmark.d.ts.map +1 -0
  27. package/dist/{benchmark.benchmark.js → simple.benchmark.js} +1 -1
  28. package/dist/simple.benchmark.js.map +1 -0
  29. package/dist/spiceflow.d.ts.map +1 -1
  30. package/dist/spiceflow.js +14 -2
  31. package/dist/spiceflow.js.map +1 -1
  32. package/dist/spiceflow.test.js.map +1 -1
  33. package/dist/static-node.d.ts +4 -0
  34. package/dist/static-node.d.ts.map +1 -0
  35. package/dist/static-node.js +35 -0
  36. package/dist/static-node.js.map +1 -0
  37. package/dist/static.benchmark.d.ts +2 -0
  38. package/dist/static.benchmark.d.ts.map +1 -0
  39. package/dist/static.benchmark.js +19 -0
  40. package/dist/static.benchmark.js.map +1 -0
  41. package/dist/static.d.ts +28 -0
  42. package/dist/static.d.ts.map +1 -0
  43. package/dist/static.js +181 -0
  44. package/dist/static.js.map +1 -0
  45. package/dist/stream.test.js +1 -1
  46. package/dist/stream.test.js.map +1 -1
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js.map +1 -1
  49. package/dist/types.test.js +3 -7
  50. package/dist/types.test.js.map +1 -1
  51. package/dist/utils.d.ts.map +1 -1
  52. package/dist/utils.js.map +1 -1
  53. package/dist/zod.test.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/client/errors.ts +17 -17
  56. package/src/client/index.ts +437 -469
  57. package/src/client/types.ts +168 -191
  58. package/src/client/utils.ts +5 -5
  59. package/src/client/ws.ts +87 -89
  60. package/src/client.test.ts +176 -183
  61. package/src/context.ts +82 -82
  62. package/src/cors.test.ts +38 -38
  63. package/src/cors.ts +87 -92
  64. package/src/error.ts +13 -13
  65. package/src/middleware.test.ts +201 -201
  66. package/src/openapi.test.ts +97 -97
  67. package/src/openapi.ts +365 -365
  68. package/src/simple.benchmark.ts +16 -0
  69. package/src/spiceflow.test.ts +461 -467
  70. package/src/spiceflow.ts +1132 -1161
  71. package/src/static-node.ts +38 -0
  72. package/src/static.benchmark.ts +22 -0
  73. package/src/static.ts +240 -0
  74. package/src/stream.test.ts +310 -310
  75. package/src/types.test.ts +46 -50
  76. package/src/types.ts +698 -701
  77. package/src/utils.ts +79 -79
  78. package/src/zod.test.ts +64 -64
  79. package/dist/benchmark.benchmark.d.ts +0 -2
  80. package/dist/benchmark.benchmark.d.ts.map +0 -1
  81. package/dist/benchmark.benchmark.js.map +0 -1
  82. 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
+ }