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/cli/init.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
const TEMPLATES: Record<string, string> = {
|
|
5
|
+
'sleepy.config.ts': 'export default {}',
|
|
6
|
+
|
|
7
|
+
'tsconfig.json': JSON.stringify({
|
|
8
|
+
compilerOptions: {
|
|
9
|
+
strict: true,
|
|
10
|
+
skipLibCheck: true,
|
|
11
|
+
target: 'ESNext',
|
|
12
|
+
module: 'ESNext',
|
|
13
|
+
moduleResolution: 'bundler',
|
|
14
|
+
types: ['bun-types'],
|
|
15
|
+
},
|
|
16
|
+
}, null, 2) + '\n',
|
|
17
|
+
|
|
18
|
+
'src/index.ts': `
|
|
19
|
+
import { createApp } from \'sleepy-serv\'
|
|
20
|
+
|
|
21
|
+
createApp(3000)
|
|
22
|
+
`.trim().concat('\n'),
|
|
23
|
+
|
|
24
|
+
'src/api/get.ts': `
|
|
25
|
+
export default function handler (): Response {
|
|
26
|
+
return Response.json({ ok: true })
|
|
27
|
+
}
|
|
28
|
+
`.trim().concat('\n'),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeIfMissing (filePath: string, content: string): boolean {
|
|
32
|
+
if (fs.existsSync(filePath)) {
|
|
33
|
+
console.warn(` skip: ${filePath} (already exists)`)
|
|
34
|
+
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const dir = path.dirname(filePath)
|
|
39
|
+
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
41
|
+
fs.writeFileSync(filePath, content)
|
|
42
|
+
console.log(` create: ${filePath}`)
|
|
43
|
+
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writePackageJson (): void {
|
|
48
|
+
const filePath = 'package.json'
|
|
49
|
+
const name = path.basename(process.cwd())
|
|
50
|
+
|
|
51
|
+
const scripts = {
|
|
52
|
+
dev: 'sleepy dev',
|
|
53
|
+
build: 'sleepy build',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (fs.existsSync(filePath)) {
|
|
57
|
+
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
58
|
+
const existing = JSON.parse(raw)
|
|
59
|
+
|
|
60
|
+
existing.scripts = {
|
|
61
|
+
...existing.scripts,
|
|
62
|
+
...scripts,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
filePath,
|
|
67
|
+
JSON.stringify(existing, null, 2) + '\n',
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
console.log(` update: ${filePath}`)
|
|
71
|
+
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pkg = {
|
|
76
|
+
name,
|
|
77
|
+
version: '0.0.0',
|
|
78
|
+
type: 'module',
|
|
79
|
+
scripts,
|
|
80
|
+
dependencies: {
|
|
81
|
+
'sleepy-serv': 'latest',
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
filePath,
|
|
87
|
+
JSON.stringify(pkg, null, 2) + '\n',
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
console.log(` create: ${filePath}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function init (): Promise<void> {
|
|
94
|
+
console.log('')
|
|
95
|
+
|
|
96
|
+
writePackageJson()
|
|
97
|
+
|
|
98
|
+
for (const [relPath, content] of Object.entries(TEMPLATES)) {
|
|
99
|
+
writeIfMissing(relPath, content)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('')
|
|
103
|
+
console.log('Created sleepy-serv project in ./')
|
|
104
|
+
console.log('')
|
|
105
|
+
console.log(' bun install')
|
|
106
|
+
console.log(' sleepy dev')
|
|
107
|
+
console.log('')
|
|
108
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import querystring from 'querystring'
|
|
2
|
+
import readline from 'node:readline'
|
|
3
|
+
|
|
4
|
+
import { stdin, stdout } from 'node:process'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
toSegments,
|
|
8
|
+
executeMiddlewareChain,
|
|
9
|
+
} from './utils'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
buildSocketState,
|
|
13
|
+
buildSocketServer,
|
|
14
|
+
buildSocketHandlers,
|
|
15
|
+
buildSocketCommands,
|
|
16
|
+
} from './socket'
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
RequestError,
|
|
20
|
+
NotFoundError,
|
|
21
|
+
MethodNotAllowedError,
|
|
22
|
+
InternalServerError,
|
|
23
|
+
} from './errors'
|
|
24
|
+
|
|
25
|
+
import type { BunRequest } from 'bun'
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
AsyncHandlerResult,
|
|
29
|
+
HttpMethod,
|
|
30
|
+
Middleware,
|
|
31
|
+
MiddlewareChain,
|
|
32
|
+
EndpointRequest,
|
|
33
|
+
MetaEntry,
|
|
34
|
+
RouteConfig,
|
|
35
|
+
RouteDefinition,
|
|
36
|
+
SocketCommands,
|
|
37
|
+
AppOptions,
|
|
38
|
+
Server,
|
|
39
|
+
} from './utils'
|
|
40
|
+
|
|
41
|
+
import type {
|
|
42
|
+
SocketRoute,
|
|
43
|
+
SocketState,
|
|
44
|
+
} from './socket'
|
|
45
|
+
|
|
46
|
+
export * from './errors'
|
|
47
|
+
export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils'
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
parseJsonBody,
|
|
51
|
+
setValidationFormats,
|
|
52
|
+
validateSchemas,
|
|
53
|
+
} from './middleware'
|
|
54
|
+
|
|
55
|
+
export type { FilterFn, SessionEntry, SocketCommands } from './utils'
|
|
56
|
+
|
|
57
|
+
export type {
|
|
58
|
+
AppOptions,
|
|
59
|
+
AsyncHandlerResult,
|
|
60
|
+
BaseRequest,
|
|
61
|
+
EndpointRequest,
|
|
62
|
+
FormattedError,
|
|
63
|
+
Handler,
|
|
64
|
+
MetaEntry,
|
|
65
|
+
Middleware,
|
|
66
|
+
MiddlewareChain,
|
|
67
|
+
NextFn,
|
|
68
|
+
HandlerResult,
|
|
69
|
+
Request,
|
|
70
|
+
RouteConfig,
|
|
71
|
+
RouteDefinition,
|
|
72
|
+
Server,
|
|
73
|
+
SocketConnection,
|
|
74
|
+
SocketOptions,
|
|
75
|
+
WebSocketRequest,
|
|
76
|
+
} from './utils'
|
|
77
|
+
|
|
78
|
+
export type {
|
|
79
|
+
FormatterField,
|
|
80
|
+
FormatterSchema,
|
|
81
|
+
ValidationSchemas,
|
|
82
|
+
} from './middleware'
|
|
83
|
+
|
|
84
|
+
type OutputRoutes = Record<string, string[]>
|
|
85
|
+
type ServerRoutes = Record<string, Record<string, EndpointHandler>>
|
|
86
|
+
|
|
87
|
+
type EndpointHandler = (
|
|
88
|
+
bunReq: BunRequest,
|
|
89
|
+
server: Server,
|
|
90
|
+
) => AsyncHandlerResult
|
|
91
|
+
|
|
92
|
+
type ChainRoute = {
|
|
93
|
+
method: HttpMethod
|
|
94
|
+
path: string
|
|
95
|
+
chain: MiddlewareChain
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type ModuleRoute = {
|
|
99
|
+
method: HttpMethod
|
|
100
|
+
path: string
|
|
101
|
+
handler: EndpointHandler
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type AppRoutes = {
|
|
105
|
+
server: ServerRoutes
|
|
106
|
+
output: OutputRoutes
|
|
107
|
+
socket: SocketRoute[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type CloseFn = (force?: boolean) => Promise<void>
|
|
111
|
+
|
|
112
|
+
export type App = {
|
|
113
|
+
server: Server
|
|
114
|
+
routes: OutputRoutes
|
|
115
|
+
ws: SocketCommands
|
|
116
|
+
close: CloseFn
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function methodNotAllowedHandler (_req: unknown): never {
|
|
120
|
+
throw new MethodNotAllowedError()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function defaultMethodMap (): Record<string, EndpointHandler> {
|
|
124
|
+
return {
|
|
125
|
+
HEAD: methodNotAllowedHandler,
|
|
126
|
+
GET: methodNotAllowedHandler,
|
|
127
|
+
PUT: methodNotAllowedHandler,
|
|
128
|
+
POST: methodNotAllowedHandler,
|
|
129
|
+
PATCH: methodNotAllowedHandler,
|
|
130
|
+
DELETE: methodNotAllowedHandler,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildEndpointRequest (
|
|
135
|
+
bunReq: BunRequest,
|
|
136
|
+
server: Server,
|
|
137
|
+
ws: SocketCommands,
|
|
138
|
+
): EndpointRequest {
|
|
139
|
+
const url = new URL(bunReq.url)
|
|
140
|
+
const qs = url.search.replace('?', '')
|
|
141
|
+
|
|
142
|
+
let bodyPromise: Promise<unknown> | null = null
|
|
143
|
+
|
|
144
|
+
const json = (): Promise<unknown> => {
|
|
145
|
+
if (!bodyPromise) {
|
|
146
|
+
bodyPromise = bunReq.json()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return bodyPromise
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
method: bunReq.method as HttpMethod,
|
|
154
|
+
route: url.pathname,
|
|
155
|
+
headers: bunReq.headers,
|
|
156
|
+
params: bunReq.params ?? {},
|
|
157
|
+
query: querystring.parse(qs),
|
|
158
|
+
raw: bunReq,
|
|
159
|
+
server,
|
|
160
|
+
json,
|
|
161
|
+
ws,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizeChain (
|
|
166
|
+
route: RouteDefinition,
|
|
167
|
+
): ChainRoute {
|
|
168
|
+
const chain = Array.isArray(route.chain)
|
|
169
|
+
? route.chain
|
|
170
|
+
: [route.chain]
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
method: route.method,
|
|
174
|
+
path: route.path,
|
|
175
|
+
chain,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function buildMergedRoutes (
|
|
180
|
+
routePaths: ChainRoute[],
|
|
181
|
+
middleware: Middleware[],
|
|
182
|
+
meta: MetaEntry[],
|
|
183
|
+
state: SocketState,
|
|
184
|
+
mountPath: string,
|
|
185
|
+
): ChainRoute[] {
|
|
186
|
+
const socketRoutes = buildSocketHandlers(state)
|
|
187
|
+
|
|
188
|
+
for (const socketRoute of socketRoutes) {
|
|
189
|
+
const mountedPath = `${mountPath}${socketRoute.path}`
|
|
190
|
+
|
|
191
|
+
const targetItem = routePaths.find(item => (
|
|
192
|
+
item.method === socketRoute.method &&
|
|
193
|
+
item.path === mountedPath
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
if (targetItem) {
|
|
197
|
+
targetItem.chain.push(socketRoute.handler)
|
|
198
|
+
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const metaMiddleware = meta
|
|
203
|
+
.filter(entry => socketRoute.path.startsWith(entry.path))
|
|
204
|
+
.sort((a, b) => a.path.length - b.path.length)
|
|
205
|
+
.flatMap(entry => entry.middleware)
|
|
206
|
+
|
|
207
|
+
routePaths.push({
|
|
208
|
+
method: socketRoute.method,
|
|
209
|
+
path: mountedPath,
|
|
210
|
+
chain: [
|
|
211
|
+
...middleware,
|
|
212
|
+
...metaMiddleware,
|
|
213
|
+
socketRoute.handler,
|
|
214
|
+
],
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return routePaths
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
|
|
222
|
+
return mergedRoutes.map(route => ({
|
|
223
|
+
...route,
|
|
224
|
+
segments: toSegments(route.path),
|
|
225
|
+
}))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildModuleRoutes (
|
|
229
|
+
socketRoutes: SocketRoute[],
|
|
230
|
+
ws: SocketCommands,
|
|
231
|
+
): ModuleRoute[] {
|
|
232
|
+
return socketRoutes.map(route => {
|
|
233
|
+
const handler: EndpointHandler = async (bunReq, server) => {
|
|
234
|
+
const req = buildEndpointRequest(bunReq, server, ws)
|
|
235
|
+
|
|
236
|
+
return executeMiddlewareChain(req, route.chain)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
method: route.method,
|
|
241
|
+
path: route.path,
|
|
242
|
+
handler,
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildServerRoutes (moduleRoutes: ModuleRoute[]): ServerRoutes {
|
|
248
|
+
return moduleRoutes.reduce<ServerRoutes>(
|
|
249
|
+
(accum, curr) => {
|
|
250
|
+
if (!accum[curr.path]) {
|
|
251
|
+
accum[curr.path] = defaultMethodMap()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
accum[curr.path][curr.method] = curr.handler
|
|
255
|
+
|
|
256
|
+
return accum
|
|
257
|
+
}, {})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function buildOutputRoutes (moduleRoutes: ModuleRoute[]): OutputRoutes {
|
|
261
|
+
return moduleRoutes.reduce<OutputRoutes>((accum, curr) => {
|
|
262
|
+
accum[curr.path] = accum[curr.path] || []
|
|
263
|
+
accum[curr.path].push(curr.method)
|
|
264
|
+
|
|
265
|
+
return accum
|
|
266
|
+
}, {})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function buildRoutes (
|
|
270
|
+
config: RouteConfig,
|
|
271
|
+
state: SocketState,
|
|
272
|
+
ws: SocketCommands,
|
|
273
|
+
opts: AppOptions,
|
|
274
|
+
): AppRoutes {
|
|
275
|
+
const mountPath = opts.mountPath || ''
|
|
276
|
+
const middleware = opts.middleware || []
|
|
277
|
+
const meta = config.meta || []
|
|
278
|
+
|
|
279
|
+
const normalRoutes = config.routes.map(route => {
|
|
280
|
+
const normalized = normalizeChain(route)
|
|
281
|
+
const routePath = normalized.path === '/' ? '' : normalized.path
|
|
282
|
+
|
|
283
|
+
const joinedPath = [mountPath, routePath]
|
|
284
|
+
.filter(item => item)
|
|
285
|
+
.join('') || '/'
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
method: normalized.method,
|
|
289
|
+
path: joinedPath,
|
|
290
|
+
chain: [
|
|
291
|
+
...middleware,
|
|
292
|
+
...normalized.chain,
|
|
293
|
+
],
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
const mergedRoutes = buildMergedRoutes(
|
|
298
|
+
normalRoutes,
|
|
299
|
+
middleware,
|
|
300
|
+
meta,
|
|
301
|
+
state,
|
|
302
|
+
mountPath,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const socketRoutes = buildSocketRoutes(mergedRoutes)
|
|
306
|
+
const moduleRoutes = buildModuleRoutes(socketRoutes, ws)
|
|
307
|
+
const serverRoutes = buildServerRoutes(moduleRoutes)
|
|
308
|
+
const outputRoutes = buildOutputRoutes(moduleRoutes)
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
server: serverRoutes,
|
|
312
|
+
socket: socketRoutes,
|
|
313
|
+
output: outputRoutes,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function buildServer (
|
|
318
|
+
port: number,
|
|
319
|
+
routes: AppRoutes,
|
|
320
|
+
state: SocketState,
|
|
321
|
+
ws: SocketCommands,
|
|
322
|
+
opts: AppOptions,
|
|
323
|
+
): Server {
|
|
324
|
+
const hostname = opts.hostname || '0.0.0.0'
|
|
325
|
+
|
|
326
|
+
const websocketServer = buildSocketServer(
|
|
327
|
+
routes.socket,
|
|
328
|
+
state,
|
|
329
|
+
ws,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return Bun.serve({
|
|
333
|
+
port,
|
|
334
|
+
hostname,
|
|
335
|
+
routes: routes.server,
|
|
336
|
+
websocket: websocketServer,
|
|
337
|
+
async fetch (_req, _server) {
|
|
338
|
+
throw new NotFoundError()
|
|
339
|
+
},
|
|
340
|
+
error (err) {
|
|
341
|
+
console.error(err)
|
|
342
|
+
|
|
343
|
+
const isRequestError = !(err instanceof RequestError)
|
|
344
|
+
const httpError = isRequestError ? new InternalServerError() : err
|
|
345
|
+
const { status } = httpError.constructor as typeof RequestError
|
|
346
|
+
|
|
347
|
+
return Response.json(httpError.output, { status })
|
|
348
|
+
},
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function processIO (server: Server, opts: AppOptions): CloseFn {
|
|
353
|
+
const onClose = opts.onClose || (() => {})
|
|
354
|
+
|
|
355
|
+
console.info(`Running on port: ${server.port}`)
|
|
356
|
+
console.info('')
|
|
357
|
+
console.info('Press Ctrl+D to gracefully shutdown')
|
|
358
|
+
console.info('')
|
|
359
|
+
|
|
360
|
+
const shutdown = async (force = false): Promise<void> => {
|
|
361
|
+
await server.stop(force)
|
|
362
|
+
await onClose()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!stdin.isTTY) {
|
|
366
|
+
return shutdown
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const rl = readline.createInterface({
|
|
370
|
+
input: stdin,
|
|
371
|
+
output: stdout,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const handleClose = async (): Promise<void> => {
|
|
375
|
+
await shutdown()
|
|
376
|
+
process.exit(0)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
rl.on('close', handleClose)
|
|
380
|
+
|
|
381
|
+
return async (force = false) => {
|
|
382
|
+
rl.off('close', handleClose)
|
|
383
|
+
rl.close()
|
|
384
|
+
|
|
385
|
+
await shutdown(force)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function createApp (
|
|
390
|
+
port: number,
|
|
391
|
+
config: RouteConfig,
|
|
392
|
+
opts: AppOptions = {},
|
|
393
|
+
): App {
|
|
394
|
+
const state = buildSocketState(opts)
|
|
395
|
+
const ws = buildSocketCommands(state)
|
|
396
|
+
const routes = buildRoutes(config, state, ws, opts)
|
|
397
|
+
const server = buildServer(port, routes, state, ws, opts)
|
|
398
|
+
const close = processIO(server, opts)
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
routes: routes.output,
|
|
402
|
+
server,
|
|
403
|
+
ws,
|
|
404
|
+
close,
|
|
405
|
+
}
|
|
406
|
+
}
|
|
@@ -746,50 +746,50 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
746
746
|
}
|
|
747
747
|
|
|
748
748
|
return {
|
|
749
|
-
|
|
750
|
-
const
|
|
749
|
+
broadcast (event, body) {
|
|
750
|
+
for (const clientId of state.activeSessions.keys()) {
|
|
751
|
+
sendToClient(clientId, event, body)
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
send (event, body, fn) {
|
|
751
755
|
let index = 0
|
|
752
756
|
|
|
757
|
+
/* TODO: look into concurrency at some point */
|
|
753
758
|
for (const [clientId, session] of state.activeSessions) {
|
|
754
759
|
if (fn(clientId, session.ws.data, index)) {
|
|
755
|
-
|
|
756
|
-
clientId,
|
|
757
|
-
app: session.ws.data.app,
|
|
758
|
-
})
|
|
760
|
+
sendToClient(clientId, event, body)
|
|
759
761
|
}
|
|
760
762
|
|
|
761
763
|
index += 1
|
|
762
764
|
}
|
|
763
|
-
|
|
764
|
-
return results
|
|
765
765
|
},
|
|
766
|
-
|
|
766
|
+
drop (fn, code, reason) {
|
|
767
767
|
let index = 0
|
|
768
768
|
|
|
769
|
-
/* TODO: look into concurrency at some point */
|
|
770
769
|
for (const [clientId, session] of state.activeSessions) {
|
|
771
770
|
if (fn(clientId, session.ws.data, index)) {
|
|
772
|
-
|
|
771
|
+
session.ws.close(code, reason)
|
|
773
772
|
}
|
|
774
773
|
|
|
775
774
|
index += 1
|
|
776
775
|
}
|
|
777
776
|
},
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
sendToClient(clientId, event, body)
|
|
781
|
-
}
|
|
782
|
-
},
|
|
783
|
-
drop (fn, code, reason) {
|
|
777
|
+
query (fn) {
|
|
778
|
+
const results: SessionEntry[] = []
|
|
784
779
|
let index = 0
|
|
785
780
|
|
|
786
781
|
for (const [clientId, session] of state.activeSessions) {
|
|
787
782
|
if (fn(clientId, session.ws.data, index)) {
|
|
788
|
-
|
|
783
|
+
results.push({
|
|
784
|
+
clientId,
|
|
785
|
+
app: session.ws.data.app,
|
|
786
|
+
})
|
|
789
787
|
}
|
|
790
788
|
|
|
791
789
|
index += 1
|
|
792
790
|
}
|
|
791
|
+
|
|
792
|
+
return results
|
|
793
793
|
},
|
|
794
794
|
}
|
|
795
795
|
}
|
|
@@ -133,10 +133,10 @@ export type FilterFn = (
|
|
|
133
133
|
) => boolean
|
|
134
134
|
|
|
135
135
|
export type SocketCommands = {
|
|
136
|
-
query: (fn: FilterFn) => SessionEntry[]
|
|
137
|
-
send: (fn: FilterFn, event: string, body: unknown) => void
|
|
138
136
|
broadcast: (event: string, body: unknown) => void
|
|
137
|
+
send: (event: string, body: unknown, fn: FilterFn) => void
|
|
139
138
|
drop: (fn: FilterFn, code?: number, reason?: string) => void
|
|
139
|
+
query: (fn: FilterFn) => SessionEntry[]
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export type BaseRequest = {
|
|
@@ -204,6 +204,22 @@ export type SocketConnection = {
|
|
|
204
204
|
|
|
205
205
|
export type Server = BunServer<SocketData>
|
|
206
206
|
|
|
207
|
+
export type RouteDefinition = {
|
|
208
|
+
method: HttpMethod
|
|
209
|
+
path: string
|
|
210
|
+
chain: Handler | MiddlewareChain
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export type MetaEntry = {
|
|
214
|
+
path: string
|
|
215
|
+
middleware: Middleware[]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export type RouteConfig = {
|
|
219
|
+
routes: RouteDefinition[]
|
|
220
|
+
meta?: MetaEntry[]
|
|
221
|
+
}
|
|
222
|
+
|
|
207
223
|
export type AppOptions = {
|
|
208
224
|
hostname?: string
|
|
209
225
|
mountPath?: string
|