sleepy-serv 0.18.0 → 0.20.0
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 +113 -17
- package/dist/cli/build.d.ts +2 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/socket.d.ts.map +1 -0
- package/dist/{utils.d.ts → core/utils.d.ts} +15 -2
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/index.d.ts +3 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/codegen.d.ts +3 -0
- package/dist/plugin/codegen.d.ts.map +1 -0
- package/dist/plugin/config.d.ts +14 -0
- package/dist/plugin/config.d.ts.map +1 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/scanner.d.ts +15 -0
- package/dist/plugin/scanner.d.ts.map +1 -0
- package/package.json +8 -1
- package/src/cli/build.ts +105 -0
- package/src/cli/dev.ts +27 -0
- package/src/cli/index.ts +53 -0
- package/src/cli/init.ts +108 -0
- package/src/core/index.ts +406 -0
- package/src/{socket.ts → core/socket.ts} +18 -18
- package/src/{utils.ts → core/utils.ts} +18 -2
- package/src/index.ts +5 -571
- package/src/plugin/codegen.ts +101 -0
- package/src/plugin/config.ts +34 -0
- package/src/plugin/index.ts +30 -0
- package/src/plugin/scanner.ts +148 -0
- package/dist/errors.d.ts.map +0 -1
- package/dist/messages.d.ts.map +0 -1
- package/dist/middleware.d.ts.map +0 -1
- package/dist/socket.d.ts.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- /package/dist/{errors.d.ts → core/errors.d.ts} +0 -0
- /package/dist/{messages.d.ts → core/messages.d.ts} +0 -0
- /package/dist/{middleware.d.ts → core/middleware.d.ts} +0 -0
- /package/dist/{socket.d.ts → core/socket.d.ts} +0 -0
- /package/src/{errors.ts → core/errors.ts} +0 -0
- /package/src/{messages.ts → core/messages.ts} +0 -0
- /package/src/{middleware.ts → core/middleware.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,574 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import querystring from 'querystring'
|
|
4
|
-
import readline from 'node:readline'
|
|
1
|
+
export * from './core'
|
|
5
2
|
|
|
6
|
-
import {
|
|
3
|
+
import type { App, AppOptions } from './core'
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
toSegments,
|
|
10
|
-
executeMiddlewareChain,
|
|
11
|
-
} from './utils'
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
buildSocketState,
|
|
15
|
-
buildSocketServer,
|
|
16
|
-
buildSocketHandlers,
|
|
17
|
-
buildSocketCommands,
|
|
18
|
-
} from './socket'
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
RequestError,
|
|
22
|
-
NotFoundError,
|
|
23
|
-
MethodNotAllowedError,
|
|
24
|
-
InternalServerError,
|
|
25
|
-
} from './errors'
|
|
26
|
-
|
|
27
|
-
import type { BunRequest } from 'bun'
|
|
28
|
-
|
|
29
|
-
import type {
|
|
30
|
-
AsyncHandlerResult,
|
|
31
|
-
HttpMethod,
|
|
32
|
-
Middleware,
|
|
33
|
-
MiddlewareChain,
|
|
34
|
-
EndpointRequest,
|
|
35
|
-
SocketCommands,
|
|
36
|
-
AppOptions,
|
|
37
|
-
Server,
|
|
38
|
-
} from './utils'
|
|
39
|
-
|
|
40
|
-
import type {
|
|
41
|
-
SocketRoute,
|
|
42
|
-
SocketState,
|
|
43
|
-
} from './socket'
|
|
44
|
-
|
|
45
|
-
export * from './errors'
|
|
46
|
-
export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils'
|
|
47
|
-
|
|
48
|
-
export {
|
|
49
|
-
parseJsonBody,
|
|
50
|
-
setValidationFormats,
|
|
51
|
-
validateSchemas,
|
|
52
|
-
} from './middleware'
|
|
53
|
-
|
|
54
|
-
export type { FilterFn, SessionEntry, SocketCommands } from './utils'
|
|
55
|
-
|
|
56
|
-
export type {
|
|
57
|
-
AppOptions,
|
|
58
|
-
AsyncHandlerResult,
|
|
59
|
-
BaseRequest,
|
|
60
|
-
EndpointRequest,
|
|
61
|
-
FormattedError,
|
|
62
|
-
Handler,
|
|
63
|
-
Middleware,
|
|
64
|
-
MiddlewareChain,
|
|
65
|
-
NextFn,
|
|
66
|
-
HandlerResult,
|
|
67
|
-
Request,
|
|
68
|
-
Server,
|
|
69
|
-
SocketConnection,
|
|
70
|
-
SocketOptions,
|
|
71
|
-
WebSocketRequest,
|
|
72
|
-
} from './utils'
|
|
73
|
-
|
|
74
|
-
export type {
|
|
75
|
-
FormatterField,
|
|
76
|
-
FormatterSchema,
|
|
77
|
-
ValidationSchemas,
|
|
78
|
-
} from './middleware'
|
|
79
|
-
|
|
80
|
-
type OutputRoutes = Record<string, string[]>
|
|
81
|
-
type ServerRoutes = Record<string, Record<string, EndpointHandler>>
|
|
82
|
-
|
|
83
|
-
type EndpointHandler = (
|
|
84
|
-
bunReq: BunRequest,
|
|
85
|
-
server: Server,
|
|
86
|
-
) => AsyncHandlerResult
|
|
87
|
-
|
|
88
|
-
type DirEntry = {
|
|
89
|
-
path: string
|
|
90
|
-
stat: fs.Stats
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
type RoutePath = {
|
|
94
|
-
method: HttpMethod
|
|
95
|
-
path: string
|
|
96
|
-
metaMiddlewarePath: string[]
|
|
97
|
-
modulePath: string
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
type ChainRoute = {
|
|
101
|
-
method: HttpMethod
|
|
102
|
-
path: string
|
|
103
|
-
chain: MiddlewareChain
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
type ModuleRoute = {
|
|
107
|
-
method: HttpMethod
|
|
108
|
-
path: string
|
|
109
|
-
handler: EndpointHandler
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
type RoutingOptions = {
|
|
113
|
-
basePath: string
|
|
114
|
-
mountPath: string
|
|
115
|
-
metadata: string[]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
type AppRoutes = {
|
|
119
|
-
server: ServerRoutes
|
|
120
|
-
output: OutputRoutes
|
|
121
|
-
socket: SocketRoute[]
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
type CloseFn = (force?: boolean) => Promise<void>
|
|
125
|
-
|
|
126
|
-
export type App = {
|
|
127
|
-
server: Server
|
|
128
|
-
routes: OutputRoutes
|
|
129
|
-
ws: SocketCommands
|
|
130
|
-
close: CloseFn
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const ALLOWED_FILES_META = ['meta.js', 'meta.ts']
|
|
134
|
-
|
|
135
|
-
const ALLOWED_FILES_METHODS = [
|
|
136
|
-
'head.js',
|
|
137
|
-
'head.ts',
|
|
138
|
-
'get.js',
|
|
139
|
-
'get.ts',
|
|
140
|
-
'put.js',
|
|
141
|
-
'put.ts',
|
|
142
|
-
'post.js',
|
|
143
|
-
'post.ts',
|
|
144
|
-
'patch.js',
|
|
145
|
-
'patch.ts',
|
|
146
|
-
'delete.js',
|
|
147
|
-
'delete.ts',
|
|
148
|
-
]
|
|
149
|
-
|
|
150
|
-
function methodNotAllowedHandler (_req: unknown): never {
|
|
151
|
-
throw new MethodNotAllowedError()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function defaultMethodMap (): Record<string, EndpointHandler> {
|
|
155
|
-
return {
|
|
156
|
-
HEAD: methodNotAllowedHandler,
|
|
157
|
-
GET: methodNotAllowedHandler,
|
|
158
|
-
PUT: methodNotAllowedHandler,
|
|
159
|
-
POST: methodNotAllowedHandler,
|
|
160
|
-
PATCH: methodNotAllowedHandler,
|
|
161
|
-
DELETE: methodNotAllowedHandler,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function buildEndpointRequest (
|
|
166
|
-
bunReq: BunRequest,
|
|
167
|
-
server: Server,
|
|
168
|
-
ws: SocketCommands,
|
|
169
|
-
): EndpointRequest {
|
|
170
|
-
const url = new URL(bunReq.url)
|
|
171
|
-
const qs = url.search.replace('?', '')
|
|
172
|
-
|
|
173
|
-
let bodyPromise: Promise<unknown> | null = null
|
|
174
|
-
|
|
175
|
-
const json = (): Promise<unknown> => {
|
|
176
|
-
if (!bodyPromise) {
|
|
177
|
-
bodyPromise = bunReq.json()
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return bodyPromise
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
method: bunReq.method as HttpMethod,
|
|
185
|
-
route: url.pathname,
|
|
186
|
-
headers: bunReq.headers,
|
|
187
|
-
params: bunReq.params ?? {},
|
|
188
|
-
query: querystring.parse(qs),
|
|
189
|
-
raw: bunReq,
|
|
190
|
-
server,
|
|
191
|
-
json,
|
|
192
|
-
ws,
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function validateLeafDirectory (
|
|
197
|
-
targetPath: string,
|
|
198
|
-
filenames: string[],
|
|
199
|
-
entries: DirEntry[],
|
|
200
|
-
): void {
|
|
201
|
-
const hasDirectories = entries.some(entry => entry.stat.isDirectory())
|
|
202
|
-
|
|
203
|
-
if (!hasDirectories) {
|
|
204
|
-
const hasMethodEntry = filenames.some(filename =>
|
|
205
|
-
ALLOWED_FILES_METHODS.includes(filename),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
if (!hasMethodEntry) {
|
|
209
|
-
throw new TypeError(`
|
|
210
|
-
Directory is a leaf, but doesn't contain a method file:
|
|
211
|
-
${targetPath}
|
|
212
|
-
`.trim())
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function validateDirectory (targetPath: string, entries: DirEntry[]): void {
|
|
218
|
-
const filenames = entries
|
|
219
|
-
.filter(entry => entry.stat.isFile())
|
|
220
|
-
.map(entry => path.basename(entry.path))
|
|
221
|
-
|
|
222
|
-
validateLeafDirectory(targetPath, filenames, entries)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function getAllFilePathsRec (targetPath: string, paths: string[]): string[] {
|
|
226
|
-
const entries = fs.readdirSync(targetPath)
|
|
227
|
-
|
|
228
|
-
const children = entries.map(item => {
|
|
229
|
-
const fullPath = path.join(targetPath, item)
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
path: fullPath,
|
|
233
|
-
stat: fs.statSync(fullPath),
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
validateDirectory(targetPath, children)
|
|
238
|
-
|
|
239
|
-
return children.reduce<string[]>((accum, curr) => {
|
|
240
|
-
const result = curr.stat.isDirectory()
|
|
241
|
-
? getAllFilePathsRec(curr.path, paths)
|
|
242
|
-
: [curr.path]
|
|
243
|
-
|
|
244
|
-
return [...accum, ...result]
|
|
245
|
-
}, [])
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function getFilteredFilePaths (
|
|
249
|
-
targetPath: string,
|
|
250
|
-
allowedFiles: string[],
|
|
251
|
-
): string[] {
|
|
252
|
-
const allPaths = getAllFilePathsRec(targetPath, [])
|
|
253
|
-
|
|
254
|
-
return allPaths.filter(item =>
|
|
255
|
-
allowedFiles.includes(path.basename(item)),
|
|
256
|
-
)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function getMethodFilePaths (targetPath: string): string[] {
|
|
260
|
-
return getFilteredFilePaths(targetPath, ALLOWED_FILES_METHODS)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function getMetaFilePaths (targetPath: string): string[] {
|
|
264
|
-
return getFilteredFilePaths(targetPath, ALLOWED_FILES_META)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function selectMetaPaths (metadata: string[], modulePath: string): string[] {
|
|
268
|
-
return metadata
|
|
269
|
-
.filter(metaPath => modulePath.startsWith(path.dirname(metaPath)))
|
|
270
|
-
.sort((a, b) => a.length - b.length)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function resolveMetaMiddleware (
|
|
274
|
-
metaPaths: string[],
|
|
275
|
-
): Promise<Middleware[]> {
|
|
276
|
-
const modules = await Promise.all(metaPaths.map(item => import(item)))
|
|
277
|
-
|
|
278
|
-
return modules
|
|
279
|
-
.map(item => item.middleware)
|
|
280
|
-
.reduce((accum, curr) => [
|
|
281
|
-
...accum,
|
|
282
|
-
...(curr || []),
|
|
283
|
-
], [])
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function buildRoutePaths (
|
|
287
|
-
rootPath: string,
|
|
288
|
-
mountPath: string,
|
|
289
|
-
metadata: string[],
|
|
290
|
-
): RoutePath[] {
|
|
291
|
-
const paths = getMethodFilePaths(rootPath)
|
|
292
|
-
|
|
293
|
-
return paths.map(modulePath => {
|
|
294
|
-
const trimmedPath = modulePath.replace(rootPath, '')
|
|
295
|
-
const segments = trimmedPath.split('.')[0].split('/')
|
|
296
|
-
const lastIndex = segments.length - 1
|
|
297
|
-
const basePath = segments.slice(0, segments.length - 1).join('/')
|
|
298
|
-
|
|
299
|
-
const joinedPath = [mountPath, basePath]
|
|
300
|
-
.filter(item => item)
|
|
301
|
-
.join('') || '/'
|
|
302
|
-
|
|
303
|
-
const metaMiddlewarePath = selectMetaPaths(metadata, modulePath)
|
|
304
|
-
const rawMethod = segments[lastIndex].toUpperCase()
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
method: rawMethod as HttpMethod,
|
|
308
|
-
path: joinedPath,
|
|
309
|
-
metaMiddlewarePath,
|
|
310
|
-
modulePath,
|
|
311
|
-
}
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async function buildChain (
|
|
316
|
-
route: RoutePath,
|
|
317
|
-
rootMiddleware: Middleware[],
|
|
318
|
-
): Promise<ChainRoute> {
|
|
319
|
-
const module = await import(route.modulePath)
|
|
320
|
-
const metaMiddleware = await resolveMetaMiddleware(route.metaMiddlewarePath)
|
|
321
|
-
|
|
322
|
-
if (!module.default) {
|
|
323
|
-
throw new ReferenceError(`
|
|
324
|
-
No default export defined in:
|
|
325
|
-
${route.modulePath}
|
|
326
|
-
`.trim())
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const baseChain = Array.isArray(module.default)
|
|
330
|
-
? module.default
|
|
331
|
-
: [module.default]
|
|
332
|
-
|
|
333
|
-
return {
|
|
334
|
-
method: route.method,
|
|
335
|
-
path: route.path,
|
|
336
|
-
chain: [
|
|
337
|
-
...rootMiddleware,
|
|
338
|
-
...metaMiddleware,
|
|
339
|
-
...baseChain,
|
|
340
|
-
],
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function buildNormalRoutes (
|
|
345
|
-
routePaths: RoutePath[],
|
|
346
|
-
rootMiddleware: Middleware[],
|
|
347
|
-
): Promise<ChainRoute[]> {
|
|
348
|
-
return Promise.all(
|
|
349
|
-
routePaths.map(route => buildChain(route, rootMiddleware)),
|
|
350
|
-
)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
async function buildMergedRoutes (
|
|
354
|
-
routePaths: ChainRoute[],
|
|
355
|
-
middleware: Middleware[],
|
|
356
|
-
state: SocketState,
|
|
357
|
-
opts: RoutingOptions,
|
|
358
|
-
): Promise<ChainRoute[]> {
|
|
359
|
-
const { basePath, mountPath, metadata } = opts
|
|
360
|
-
const socketRoutes = buildSocketHandlers(state)
|
|
361
|
-
|
|
362
|
-
for (const socketRoute of socketRoutes) {
|
|
363
|
-
const mountedPath = `${mountPath}${socketRoute.path}`
|
|
364
|
-
|
|
365
|
-
const targetItem = routePaths.find(item => (
|
|
366
|
-
item.method === socketRoute.method &&
|
|
367
|
-
item.path === mountedPath
|
|
368
|
-
))
|
|
369
|
-
|
|
370
|
-
if (targetItem) {
|
|
371
|
-
targetItem.chain.push(socketRoute.handler)
|
|
372
|
-
|
|
373
|
-
continue
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const method = socketRoute.method.toLowerCase()
|
|
377
|
-
const modulePath = path.join(basePath, socketRoute.path, `${method}.js`)
|
|
378
|
-
const metaPaths = selectMetaPaths(metadata, modulePath)
|
|
379
|
-
const metaMiddleware = await resolveMetaMiddleware(metaPaths)
|
|
380
|
-
|
|
381
|
-
routePaths.push({
|
|
382
|
-
method: socketRoute.method,
|
|
383
|
-
path: mountedPath,
|
|
384
|
-
chain: [
|
|
385
|
-
...middleware,
|
|
386
|
-
...metaMiddleware,
|
|
387
|
-
socketRoute.handler,
|
|
388
|
-
],
|
|
389
|
-
})
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return routePaths
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
|
|
396
|
-
return mergedRoutes.map(route => ({
|
|
397
|
-
...route,
|
|
398
|
-
segments: toSegments(route.path),
|
|
399
|
-
}))
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function buildModuleRoutes (
|
|
403
|
-
socketRoutes: SocketRoute[],
|
|
404
|
-
ws: SocketCommands,
|
|
405
|
-
): ModuleRoute[] {
|
|
406
|
-
return socketRoutes.map(route => {
|
|
407
|
-
const handler: EndpointHandler = async (bunReq, server) => {
|
|
408
|
-
const req = buildEndpointRequest(bunReq, server, ws)
|
|
409
|
-
|
|
410
|
-
return executeMiddlewareChain(req, route.chain)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
method: route.method,
|
|
415
|
-
path: route.path,
|
|
416
|
-
handler,
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function buildServerRoutes (moduleRoutes: ModuleRoute[]): ServerRoutes {
|
|
422
|
-
return moduleRoutes.reduce<ServerRoutes>(
|
|
423
|
-
(accum, curr) => {
|
|
424
|
-
if (!accum[curr.path]) {
|
|
425
|
-
accum[curr.path] = defaultMethodMap()
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
accum[curr.path][curr.method] = curr.handler
|
|
429
|
-
|
|
430
|
-
return accum
|
|
431
|
-
}, {})
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function buildOutputRoutes (moduleRoutes: ModuleRoute[]): OutputRoutes {
|
|
435
|
-
return moduleRoutes.reduce<OutputRoutes>((accum, curr) => {
|
|
436
|
-
accum[curr.path] = accum[curr.path] || []
|
|
437
|
-
accum[curr.path].push(curr.method)
|
|
438
|
-
|
|
439
|
-
return accum
|
|
440
|
-
}, {})
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async function buildRoutes (
|
|
444
|
-
rootPath: string,
|
|
445
|
-
state: SocketState,
|
|
446
|
-
ws: SocketCommands,
|
|
447
|
-
opts: AppOptions,
|
|
448
|
-
): Promise<AppRoutes> {
|
|
449
|
-
const basePath = `${rootPath}/api`
|
|
450
|
-
const mountPath = opts.mountPath || ''
|
|
451
|
-
const middleware = opts.middleware || []
|
|
452
|
-
const metadata = getMetaFilePaths(basePath)
|
|
453
|
-
const routePaths = buildRoutePaths(basePath, mountPath, metadata)
|
|
454
|
-
const normalRoutes = await buildNormalRoutes(routePaths, middleware)
|
|
455
|
-
|
|
456
|
-
const routingOpts = {
|
|
457
|
-
basePath,
|
|
458
|
-
mountPath,
|
|
459
|
-
metadata,
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const mergedRoutes = await buildMergedRoutes(
|
|
463
|
-
normalRoutes,
|
|
464
|
-
middleware,
|
|
465
|
-
state,
|
|
466
|
-
routingOpts,
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
const socketRoutes = buildSocketRoutes(mergedRoutes)
|
|
470
|
-
const moduleRoutes = buildModuleRoutes(socketRoutes, ws)
|
|
471
|
-
const serverRoutes = buildServerRoutes(moduleRoutes)
|
|
472
|
-
const outputRoutes = buildOutputRoutes(moduleRoutes)
|
|
473
|
-
|
|
474
|
-
return {
|
|
475
|
-
server: serverRoutes,
|
|
476
|
-
socket: socketRoutes,
|
|
477
|
-
output: outputRoutes,
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function buildServer (
|
|
482
|
-
port: number,
|
|
483
|
-
routes: AppRoutes,
|
|
484
|
-
state: SocketState,
|
|
485
|
-
ws: SocketCommands,
|
|
486
|
-
opts: AppOptions,
|
|
487
|
-
): Server {
|
|
488
|
-
const hostname = opts.hostname || '0.0.0.0'
|
|
489
|
-
|
|
490
|
-
const websocketServer = buildSocketServer(
|
|
491
|
-
routes.socket,
|
|
492
|
-
state,
|
|
493
|
-
ws,
|
|
494
|
-
)
|
|
495
|
-
|
|
496
|
-
return Bun.serve({
|
|
497
|
-
port,
|
|
498
|
-
hostname,
|
|
499
|
-
routes: routes.server,
|
|
500
|
-
websocket: websocketServer,
|
|
501
|
-
async fetch (_req, _server) {
|
|
502
|
-
throw new NotFoundError()
|
|
503
|
-
},
|
|
504
|
-
error (err) {
|
|
505
|
-
console.error(err)
|
|
506
|
-
|
|
507
|
-
const isRequestError = !(err instanceof RequestError)
|
|
508
|
-
const httpError = isRequestError ? new InternalServerError() : err
|
|
509
|
-
const { status } = httpError.constructor as typeof RequestError
|
|
510
|
-
|
|
511
|
-
return Response.json(httpError.output, { status })
|
|
512
|
-
},
|
|
513
|
-
})
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function processIO (
|
|
517
|
-
port: number,
|
|
518
|
-
server: Server,
|
|
519
|
-
opts: AppOptions,
|
|
520
|
-
): CloseFn {
|
|
521
|
-
const onClose = opts.onClose || (() => {})
|
|
522
|
-
|
|
523
|
-
console.info(`Running on port: ${port}`)
|
|
524
|
-
console.info('')
|
|
525
|
-
console.info('Press Ctrl+D to gracefully shutdown')
|
|
526
|
-
console.info('')
|
|
527
|
-
|
|
528
|
-
const shutdown = async (force = false): Promise<void> => {
|
|
529
|
-
await server.stop(force)
|
|
530
|
-
await onClose()
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (!stdin.isTTY) {
|
|
534
|
-
return shutdown
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const rl = readline.createInterface({
|
|
538
|
-
input: stdin,
|
|
539
|
-
output: stdout,
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
const handleClose = async (): Promise<void> => {
|
|
543
|
-
await shutdown()
|
|
544
|
-
process.exit(0)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
rl.on('close', handleClose)
|
|
548
|
-
|
|
549
|
-
return async (force = false) => {
|
|
550
|
-
rl.off('close', handleClose)
|
|
551
|
-
rl.close()
|
|
552
|
-
|
|
553
|
-
await shutdown(force)
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
export async function createApp (
|
|
5
|
+
export declare function createApp (
|
|
558
6
|
port: number,
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
): Promise<App> {
|
|
562
|
-
const state = buildSocketState(opts)
|
|
563
|
-
const ws = buildSocketCommands(state)
|
|
564
|
-
const routes = await buildRoutes(rootPath, state, ws, opts)
|
|
565
|
-
const server = buildServer(port, routes, state, ws, opts)
|
|
566
|
-
const close = processIO(port, server, opts)
|
|
567
|
-
|
|
568
|
-
return {
|
|
569
|
-
routes: routes.output,
|
|
570
|
-
server,
|
|
571
|
-
ws,
|
|
572
|
-
close,
|
|
573
|
-
}
|
|
574
|
-
}
|
|
7
|
+
opts?: AppOptions,
|
|
8
|
+
): App
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ScanResult } from './scanner'
|
|
2
|
+
|
|
3
|
+
export function generateBarrelModule (
|
|
4
|
+
scanResult: ScanResult,
|
|
5
|
+
): string {
|
|
6
|
+
const lines: string[] = [
|
|
7
|
+
'export * from \'./core\'',
|
|
8
|
+
'',
|
|
9
|
+
'import { createApp as _createApp } from \'./core\'',
|
|
10
|
+
'',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < scanResult.methods.length; i++) {
|
|
14
|
+
const entry = scanResult.methods[i]
|
|
15
|
+
|
|
16
|
+
lines.push(
|
|
17
|
+
`import handler_${i} from '${entry.modulePath}'`,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < scanResult.meta.length; i++) {
|
|
22
|
+
const entry = scanResult.meta[i]
|
|
23
|
+
|
|
24
|
+
lines.push(
|
|
25
|
+
`import * as metaModule_${i}`
|
|
26
|
+
+ ` from '${entry.modulePath}'`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
lines.push('')
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < scanResult.methods.length; i++) {
|
|
33
|
+
const entry = scanResult.methods[i]
|
|
34
|
+
|
|
35
|
+
lines.push(
|
|
36
|
+
`if (typeof handler_${i} === 'undefined') {`,
|
|
37
|
+
' throw new ReferenceError(',
|
|
38
|
+
` 'No default export defined in:\\n${entry.modulePath}',`,
|
|
39
|
+
' )',
|
|
40
|
+
'}',
|
|
41
|
+
'',
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < scanResult.methods.length; i++) {
|
|
46
|
+
lines.push(
|
|
47
|
+
`const chain_${i} = Array.isArray(handler_${i})`
|
|
48
|
+
+ ` ? handler_${i}`
|
|
49
|
+
+ ` : [handler_${i}]`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
lines.push('')
|
|
54
|
+
|
|
55
|
+
const routeEntries = scanResult.methods.map((entry, i) => {
|
|
56
|
+
const applicableMeta = scanResult.meta
|
|
57
|
+
.filter(m => entry.path.startsWith(m.path))
|
|
58
|
+
.sort((a, b) => a.path.length - b.path.length)
|
|
59
|
+
|
|
60
|
+
const metaSpreads = applicableMeta
|
|
61
|
+
.map(m => {
|
|
62
|
+
const metaIndex = scanResult.meta.indexOf(m)
|
|
63
|
+
|
|
64
|
+
return `...(metaModule_${metaIndex}.middleware ?? [])`
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const chainParts = [...metaSpreads, `...chain_${i}`]
|
|
68
|
+
|
|
69
|
+
return [
|
|
70
|
+
' {',
|
|
71
|
+
` method: '${entry.method}',`,
|
|
72
|
+
` path: '${entry.path}',`,
|
|
73
|
+
` chain: [${chainParts.join(', ')}],`,
|
|
74
|
+
' }',
|
|
75
|
+
].join('\n')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const metaEntries = scanResult.meta.map((entry, i) => {
|
|
79
|
+
return [
|
|
80
|
+
' {',
|
|
81
|
+
` path: '${entry.path}',`,
|
|
82
|
+
` middleware: metaModule_${i}.middleware ?? [],`,
|
|
83
|
+
' }',
|
|
84
|
+
].join('\n')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
lines.push('const config = {')
|
|
88
|
+
lines.push(' routes: [')
|
|
89
|
+
lines.push(routeEntries.join(',\n'))
|
|
90
|
+
lines.push(' ],')
|
|
91
|
+
lines.push(' meta: [')
|
|
92
|
+
lines.push(metaEntries.join(',\n'))
|
|
93
|
+
lines.push(' ],')
|
|
94
|
+
lines.push('}')
|
|
95
|
+
lines.push('')
|
|
96
|
+
lines.push('export function createApp (port, opts = {}) {')
|
|
97
|
+
lines.push(' return _createApp(port, config, opts)')
|
|
98
|
+
lines.push('}')
|
|
99
|
+
|
|
100
|
+
return lines.join('\n')
|
|
101
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
export type SleepyConfig = {
|
|
4
|
+
app?: {
|
|
5
|
+
root?: string
|
|
6
|
+
entrypoint?: string
|
|
7
|
+
}
|
|
8
|
+
build?: {
|
|
9
|
+
compile?: boolean | string
|
|
10
|
+
bytecode?: boolean
|
|
11
|
+
outdir?: string
|
|
12
|
+
plugins?: unknown[]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function loadConfig (
|
|
17
|
+
startDir?: string,
|
|
18
|
+
): Promise<SleepyConfig> {
|
|
19
|
+
const dir = startDir ?? process.cwd()
|
|
20
|
+
|
|
21
|
+
for (const ext of ['.ts', '.js']) {
|
|
22
|
+
const configPath = path.join(dir, `sleepy.config${ext}`)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const mod = await import(configPath)
|
|
26
|
+
|
|
27
|
+
return mod.default ?? {}
|
|
28
|
+
} catch {
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {}
|
|
34
|
+
}
|