sleepy-serv 0.11.0 → 0.12.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 +59 -5
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/socket.d.ts +2 -9
- package/dist/socket.d.ts.map +1 -1
- package/dist/utils.d.ts +7 -3
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +20 -21
- package/src/socket.ts +12 -21
- package/src/utils.ts +13 -3
package/README.md
CHANGED
|
@@ -12,10 +12,7 @@ A directory-driven web server designed for REST-ful applications
|
|
|
12
12
|
Here's a minimalist example on how to create a sleepy-serv app:
|
|
13
13
|
|
|
14
14
|
```js
|
|
15
|
-
import {
|
|
16
|
-
middleware,
|
|
17
|
-
createApp,
|
|
18
|
-
} from 'sleepy-serv'
|
|
15
|
+
import { createApp } from 'sleepy-serv'
|
|
19
16
|
|
|
20
17
|
const PORT = 3000
|
|
21
18
|
|
|
@@ -29,7 +26,7 @@ The parameter for `import.meta.dirname` can be any directory you prefer, but it'
|
|
|
29
26
|
`sleepy-serv` was originally built for NodeJS, but it was ported to `bun` recently (before the initial release). The `createApp()` function merely calls `Bun.serve()` under-the-hood, and returns the `app` object that contains these properties:
|
|
30
27
|
- `routes`: Contains a list of all of the routes defined by the file structure. This is useful for debugging.
|
|
31
28
|
- `server`: this is the object that's returned from `Bun.serve()`. The `server` object has an `async` `.stop()` method on it, but prefer `app.close()` (see [Shutting Down](#shutting-down)), which stops the server and releases everything else the app holds.
|
|
32
|
-
- `
|
|
29
|
+
- `ws`: WebSocket commands for interacting with connected clients: `send(fn, event, body)`, `broadcast(event, body)`, and `drop(clientId, code?, reason?)`.
|
|
33
30
|
- `close`: an `async` function that shuts the app down. See [Shutting Down](#shutting-down).
|
|
34
31
|
|
|
35
32
|
### Shutting Down
|
|
@@ -488,3 +485,60 @@ const app = await createApp(PORT, import.meta.dirname, {
|
|
|
488
485
|
onClose: () => console.info('closing down...'),
|
|
489
486
|
})
|
|
490
487
|
```
|
|
488
|
+
|
|
489
|
+
### `ws`
|
|
490
|
+
|
|
491
|
+
WebSocket tuning and lifecycle hooks:
|
|
492
|
+
|
|
493
|
+
```js
|
|
494
|
+
const app = await createApp(PORT, import.meta.dirname, {
|
|
495
|
+
ws: {
|
|
496
|
+
heartbeatInterval: 30_000,
|
|
497
|
+
dropThreshold: 120_000,
|
|
498
|
+
reclaimTtl: 300_000,
|
|
499
|
+
ticketTtl: 10_000,
|
|
500
|
+
onOpen: clientId => console.log('connected:', clientId),
|
|
501
|
+
onClose: (clientId, reason) => console.log('closed:', clientId, reason),
|
|
502
|
+
},
|
|
503
|
+
})
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
- `heartbeatInterval`: how often the client should send heartbeats, in milliseconds. Sent to the client in the welcome message. Defaults to `30_000`.
|
|
507
|
+
- `dropThreshold`: how long the server waits without an inbound message before reaping the connection, in milliseconds. Defaults to `120_000`.
|
|
508
|
+
- `reclaimTtl`: how long an inactive (reaped/dropped) session stays reclaimable, in milliseconds. Defaults to `300_000`.
|
|
509
|
+
- `ticketTtl`: how long a minted upgrade ticket stays valid, in milliseconds. Defaults to `10_000`.
|
|
510
|
+
- `onOpen(clientId)`: fires after a client's welcome message is sent. Wrapped in try/catch so a throwing hook does not break the connection.
|
|
511
|
+
- `onClose(clientId, reason)`: fires when a connection closes. `reason` is a `CloseReason` value: `'ok'`, `'dropped'`, `'reaped'`, or `'superseded'`. Also wrapped in try/catch.
|
|
512
|
+
|
|
513
|
+
## WebSocket Commands
|
|
514
|
+
|
|
515
|
+
The `app.ws` object exposes three methods for interacting with connected clients:
|
|
516
|
+
|
|
517
|
+
- `send(fn, event, body)`: push a notification to clients matching a filter. The filter function receives `(clientId, data, index)` and returns a boolean. To target one client: `app.ws.send(id => id === targetId, event, body)`.
|
|
518
|
+
- `broadcast(event, body)`: push a notification to all connected clients.
|
|
519
|
+
- `drop(clientId, code?, reason?)`: close a client's connection from the server side. The default code is `CloseCode.Ok` (1000), which tells the client not to reconnect. Passing a custom code (e.g. 4000) allows the client to reconnect.
|
|
520
|
+
|
|
521
|
+
The same commands are available inside endpoint handlers via `req.ws`:
|
|
522
|
+
|
|
523
|
+
```js
|
|
524
|
+
export default function (req) {
|
|
525
|
+
const targetId = req.query.targetId
|
|
526
|
+
|
|
527
|
+
req.ws.send(id => id === targetId, 'ping', { from: 'handler' })
|
|
528
|
+
|
|
529
|
+
return Response.json({ ok: true })
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
This works from both HTTP and WebSocket transports.
|
|
534
|
+
|
|
535
|
+
## Exports
|
|
536
|
+
|
|
537
|
+
`sleepy-serv` exports several runtime constants and types:
|
|
538
|
+
|
|
539
|
+
- `CloseCode`: WebSocket close codes: `Ok` (1000), `Abnormal` (1006), `Reaped` (4999)
|
|
540
|
+
- `CloseReason`: close reason values: `Ok`, `Dropped`, `Reaped`, `Superseded`
|
|
541
|
+
- `StatusCode`: the full range of HTTP status codes (1xx through 5xx)
|
|
542
|
+
- `HttpMethod`: HTTP verbs: `Head`, `Get`, `Post`, `Put`, `Patch`, `Delete`
|
|
543
|
+
- Error classes for every 4xx and 5xx status (e.g. `NotFoundError`, `UnauthorizedError`, `InternalServerError`)
|
|
544
|
+
- Middleware helpers: `parseJsonBody`, `validateSchemas`, `setValidationFormats`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import type { AppOptions, Server } from './utils';
|
|
2
|
-
import type { SocketCommands } from './socket';
|
|
1
|
+
import type { SocketCommands, AppOptions, Server } from './utils';
|
|
3
2
|
export * from './errors';
|
|
4
3
|
export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils';
|
|
5
4
|
export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
|
|
6
|
-
export type { SocketCommands } from './
|
|
7
|
-
export type { ActiveSessions } from './utils';
|
|
5
|
+
export type { FilterFn, SocketCommands } from './utils';
|
|
8
6
|
export type { AppOptions, AsyncHandlerResult, BaseRequest, EndpointRequest, FormattedError, Handler, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
|
|
9
7
|
export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
|
|
10
8
|
type OutputRoutes = Record<string, string[]>;
|
|
11
9
|
type CloseFn = (force?: boolean) => Promise<void>;
|
|
12
10
|
export type App = {
|
|
13
11
|
server: Server;
|
|
14
|
-
commands: SocketCommands;
|
|
15
12
|
routes: OutputRoutes;
|
|
13
|
+
ws: SocketCommands;
|
|
16
14
|
close: CloseFn;
|
|
17
15
|
};
|
|
18
16
|
export declare function createApp(port: number, rootPath: string, opts?: AppOptions): Promise<App>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAMV,cAAc,EACd,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAOhB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAExE,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA4C5C,KAAK,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,YAAY,CAAA;IACpB,EAAE,EAAE,cAAc,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AA0aD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
|
package/dist/socket.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CloseReason } from './utils';
|
|
2
2
|
import type { WebSocketHandler } from 'bun';
|
|
3
|
-
import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketData, ActiveSession, InactiveSession, AppOptions } from './utils';
|
|
3
|
+
import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, AppOptions } from './utils';
|
|
4
4
|
type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
|
|
5
|
-
type FilterFn = (clientId: string, data: unknown) => boolean;
|
|
6
5
|
type SocketEndpoint = {
|
|
7
6
|
method: HttpMethod;
|
|
8
7
|
path: string;
|
|
@@ -31,14 +30,8 @@ export type SocketRoute = {
|
|
|
31
30
|
segments: string[];
|
|
32
31
|
chain: MiddlewareChain;
|
|
33
32
|
};
|
|
34
|
-
export type SocketCommands = {
|
|
35
|
-
send: (clientId: string, event: string, body: unknown) => void;
|
|
36
|
-
sendToGroup: (fn: FilterFn, event: string, body: unknown) => void;
|
|
37
|
-
broadcast: (event: string, body: unknown) => void;
|
|
38
|
-
drop: (clientId: string, code?: number, reason?: string) => void;
|
|
39
|
-
};
|
|
40
33
|
export declare function buildSocketState(opts?: AppOptions): SocketState;
|
|
41
|
-
export declare function buildSocketServer(routes: SocketRoute[], state: SocketState): WebSocketHandler<SocketData>;
|
|
34
|
+
export declare function buildSocketServer(routes: SocketRoute[], state: SocketState, commands: SocketCommands): WebSocketHandler<SocketData>;
|
|
42
35
|
export declare function buildSocketHandlers(state: SocketState): SocketEndpoint[];
|
|
43
36
|
export declare function buildSocketCommands(state: SocketState): SocketCommands;
|
|
44
37
|
export {};
|
package/dist/socket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,WAAW,EAIZ,MAAM,SAAS,CAAA;AAchB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,OAAO,EACP,eAAe,
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,WAAW,EAIZ,MAAM,SAAS,CAAA;AAchB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,OAAO,EACP,eAAe,EACf,cAAc,EAEd,UAAU,EAEV,aAAa,EACb,eAAe,EAEf,UAAU,EACX,MAAM,SAAS,CAAA;AAShB,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAA;AAYvE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,aAAa,CAAA;CACvB,CAAA;AAqBD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC1C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC9C,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CAClE,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,eAAe,CAAA;CACvB,CAAA;AAiTD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,UAAe,GAAG,WAAW,CAapE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,cAAc,GACvB,gBAAgB,CAAC,UAAU,CAAC,CAoJ9B;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,EAAE,CAyIzE;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,CA6CvE"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -96,6 +96,12 @@ export type InactiveSession = {
|
|
|
96
96
|
};
|
|
97
97
|
export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
|
|
98
98
|
export type Session = ActiveSession | InactiveSession;
|
|
99
|
+
export type FilterFn = (clientId: string, data: unknown, index: number) => boolean;
|
|
100
|
+
export type SocketCommands = {
|
|
101
|
+
send: (fn: FilterFn, event: string, body: unknown) => void;
|
|
102
|
+
broadcast: (event: string, body: unknown) => void;
|
|
103
|
+
drop: (clientId: string, code?: number, reason?: string) => void;
|
|
104
|
+
};
|
|
99
105
|
export type BaseRequest = {
|
|
100
106
|
method: HttpMethod;
|
|
101
107
|
route: string;
|
|
@@ -103,9 +109,7 @@ export type BaseRequest = {
|
|
|
103
109
|
params: Record<string, string>;
|
|
104
110
|
query: Record<string, unknown>;
|
|
105
111
|
json: () => Promise<unknown>;
|
|
106
|
-
ws:
|
|
107
|
-
active: ActiveSessions;
|
|
108
|
-
};
|
|
112
|
+
ws: SocketCommands;
|
|
109
113
|
};
|
|
110
114
|
export type EndpointRequest = BaseRequest & {
|
|
111
115
|
raw: BunRequest;
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,KAAK,CAAA;AAE1D,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAClD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,kBAAkB,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,aAAa,CAAA;AAEtD,MAAM,MAAM,UAAU,GAAG,CACvB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,KACT,aAAa,CAAA;AAElB,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,KACT,aAAa,CAAA;AAElB,MAAM,MAAM,eAAe,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC,EAAE,CAAA;AAEtD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,gBAAgB,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,OAAO,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;AAC/D,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,eAAe,CAAA;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,EAAE,EAAE
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import type {
|
|
|
32
32
|
Middleware,
|
|
33
33
|
MiddlewareChain,
|
|
34
34
|
EndpointRequest,
|
|
35
|
-
|
|
35
|
+
SocketCommands,
|
|
36
36
|
AppOptions,
|
|
37
37
|
Server,
|
|
38
38
|
} from './utils'
|
|
@@ -40,7 +40,6 @@ import type {
|
|
|
40
40
|
import type {
|
|
41
41
|
SocketRoute,
|
|
42
42
|
SocketState,
|
|
43
|
-
SocketCommands,
|
|
44
43
|
} from './socket'
|
|
45
44
|
|
|
46
45
|
export * from './errors'
|
|
@@ -52,8 +51,7 @@ export {
|
|
|
52
51
|
validateSchemas,
|
|
53
52
|
} from './middleware'
|
|
54
53
|
|
|
55
|
-
export type { SocketCommands } from './
|
|
56
|
-
export type { ActiveSessions } from './utils'
|
|
54
|
+
export type { FilterFn, SocketCommands } from './utils'
|
|
57
55
|
|
|
58
56
|
export type {
|
|
59
57
|
AppOptions,
|
|
@@ -127,8 +125,8 @@ type CloseFn = (force?: boolean) => Promise<void>
|
|
|
127
125
|
|
|
128
126
|
export type App = {
|
|
129
127
|
server: Server
|
|
130
|
-
commands: SocketCommands
|
|
131
128
|
routes: OutputRoutes
|
|
129
|
+
ws: SocketCommands
|
|
132
130
|
close: CloseFn
|
|
133
131
|
}
|
|
134
132
|
|
|
@@ -167,7 +165,7 @@ function defaultMethodMap (): Record<string, EndpointHandler> {
|
|
|
167
165
|
function buildEndpointRequest (
|
|
168
166
|
bunReq: BunRequest,
|
|
169
167
|
server: Server,
|
|
170
|
-
|
|
168
|
+
ws: SocketCommands,
|
|
171
169
|
): EndpointRequest {
|
|
172
170
|
const url = new URL(bunReq.url)
|
|
173
171
|
const qs = url.search.replace('?', '')
|
|
@@ -191,9 +189,7 @@ function buildEndpointRequest (
|
|
|
191
189
|
raw: bunReq,
|
|
192
190
|
server,
|
|
193
191
|
json,
|
|
194
|
-
ws
|
|
195
|
-
active: activeSessions,
|
|
196
|
-
},
|
|
192
|
+
ws,
|
|
197
193
|
}
|
|
198
194
|
}
|
|
199
195
|
|
|
@@ -405,15 +401,11 @@ function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
|
|
|
405
401
|
|
|
406
402
|
function buildModuleRoutes (
|
|
407
403
|
socketRoutes: SocketRoute[],
|
|
408
|
-
|
|
404
|
+
ws: SocketCommands,
|
|
409
405
|
): ModuleRoute[] {
|
|
410
406
|
return socketRoutes.map(route => {
|
|
411
407
|
const handler: EndpointHandler = async (bunReq, server) => {
|
|
412
|
-
const req = buildEndpointRequest(
|
|
413
|
-
bunReq,
|
|
414
|
-
server,
|
|
415
|
-
state.activeSessions,
|
|
416
|
-
)
|
|
408
|
+
const req = buildEndpointRequest(bunReq, server, ws)
|
|
417
409
|
|
|
418
410
|
return executeMiddlewareChain(req, route.chain)
|
|
419
411
|
}
|
|
@@ -451,6 +443,7 @@ function buildOutputRoutes (moduleRoutes: ModuleRoute[]): OutputRoutes {
|
|
|
451
443
|
async function buildRoutes (
|
|
452
444
|
rootPath: string,
|
|
453
445
|
state: SocketState,
|
|
446
|
+
ws: SocketCommands,
|
|
454
447
|
opts: AppOptions,
|
|
455
448
|
): Promise<AppRoutes> {
|
|
456
449
|
const basePath = `${rootPath}/api`
|
|
@@ -474,7 +467,7 @@ async function buildRoutes (
|
|
|
474
467
|
)
|
|
475
468
|
|
|
476
469
|
const socketRoutes = buildSocketRoutes(mergedRoutes)
|
|
477
|
-
const moduleRoutes = buildModuleRoutes(socketRoutes,
|
|
470
|
+
const moduleRoutes = buildModuleRoutes(socketRoutes, ws)
|
|
478
471
|
const serverRoutes = buildServerRoutes(moduleRoutes)
|
|
479
472
|
const outputRoutes = buildOutputRoutes(moduleRoutes)
|
|
480
473
|
|
|
@@ -489,10 +482,16 @@ function buildServer (
|
|
|
489
482
|
port: number,
|
|
490
483
|
routes: AppRoutes,
|
|
491
484
|
state: SocketState,
|
|
485
|
+
ws: SocketCommands,
|
|
492
486
|
opts: AppOptions,
|
|
493
487
|
): Server {
|
|
494
488
|
const hostname = opts.hostname || '0.0.0.0'
|
|
495
|
-
|
|
489
|
+
|
|
490
|
+
const websocketServer = buildSocketServer(
|
|
491
|
+
routes.socket,
|
|
492
|
+
state,
|
|
493
|
+
ws,
|
|
494
|
+
)
|
|
496
495
|
|
|
497
496
|
return Bun.serve({
|
|
498
497
|
port,
|
|
@@ -561,15 +560,15 @@ export async function createApp (
|
|
|
561
560
|
opts: AppOptions = {},
|
|
562
561
|
): Promise<App> {
|
|
563
562
|
const state = buildSocketState(opts)
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
const
|
|
563
|
+
const ws = buildSocketCommands(state)
|
|
564
|
+
const routes = await buildRoutes(rootPath, state, ws, opts)
|
|
565
|
+
const server = buildServer(port, routes, state, ws, opts)
|
|
567
566
|
const close = processIO(port, server, opts)
|
|
568
567
|
|
|
569
568
|
return {
|
|
570
569
|
routes: routes.output,
|
|
571
570
|
server,
|
|
572
|
-
|
|
571
|
+
ws,
|
|
573
572
|
close,
|
|
574
573
|
}
|
|
575
574
|
}
|
package/src/socket.ts
CHANGED
|
@@ -36,11 +36,11 @@ import type {
|
|
|
36
36
|
HttpMethod,
|
|
37
37
|
Request,
|
|
38
38
|
MiddlewareChain,
|
|
39
|
+
SocketCommands,
|
|
39
40
|
WebSocketRequest,
|
|
40
41
|
SocketData,
|
|
41
42
|
SocketConnection,
|
|
42
43
|
ActiveSession,
|
|
43
|
-
ActiveSessions,
|
|
44
44
|
InactiveSession,
|
|
45
45
|
Session,
|
|
46
46
|
AppOptions,
|
|
@@ -54,7 +54,6 @@ import type {
|
|
|
54
54
|
} from './messages'
|
|
55
55
|
|
|
56
56
|
type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult
|
|
57
|
-
type FilterFn = (clientId: string, data: unknown) => boolean
|
|
58
57
|
|
|
59
58
|
type UpgradeData = {
|
|
60
59
|
clientId?: string
|
|
@@ -117,13 +116,6 @@ export type SocketRoute = {
|
|
|
117
116
|
chain: MiddlewareChain
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
export type SocketCommands = {
|
|
121
|
-
send: (clientId: string, event: string, body: unknown) => void
|
|
122
|
-
sendToGroup: (fn: FilterFn, event: string, body: unknown) => void
|
|
123
|
-
broadcast: (event: string, body: unknown) => void
|
|
124
|
-
drop: (clientId: string, code?: number, reason?: string) => void
|
|
125
|
-
}
|
|
126
|
-
|
|
127
119
|
const ajv = new Ajv({
|
|
128
120
|
allErrors: true,
|
|
129
121
|
})
|
|
@@ -361,7 +353,7 @@ function buildParams (
|
|
|
361
353
|
function buildRequest (
|
|
362
354
|
params: Record<string, string>,
|
|
363
355
|
message: RequestMessage,
|
|
364
|
-
|
|
356
|
+
ws: SocketCommands,
|
|
365
357
|
): WebSocketRequest {
|
|
366
358
|
const { id, clientId, method, route } = message
|
|
367
359
|
const headers = new Headers(message.headers ?? {})
|
|
@@ -376,10 +368,8 @@ function buildRequest (
|
|
|
376
368
|
headers,
|
|
377
369
|
params,
|
|
378
370
|
query,
|
|
371
|
+
ws,
|
|
379
372
|
json,
|
|
380
|
-
ws: {
|
|
381
|
-
active: activeSessions,
|
|
382
|
-
},
|
|
383
373
|
}
|
|
384
374
|
}
|
|
385
375
|
|
|
@@ -447,6 +437,7 @@ export function buildSocketState (opts: AppOptions = {}): SocketState {
|
|
|
447
437
|
export function buildSocketServer (
|
|
448
438
|
routes: SocketRoute[],
|
|
449
439
|
state: SocketState,
|
|
440
|
+
commands: SocketCommands,
|
|
450
441
|
): WebSocketHandler<SocketData> {
|
|
451
442
|
const {
|
|
452
443
|
dropThreshold,
|
|
@@ -581,7 +572,7 @@ export function buildSocketServer (
|
|
|
581
572
|
const { id, clientId } = message
|
|
582
573
|
const route = matchRoute(routes, message)
|
|
583
574
|
const params = buildParams(route, message)
|
|
584
|
-
const req = buildRequest(params, message,
|
|
575
|
+
const req = buildRequest(params, message, commands)
|
|
585
576
|
const res = await executeMiddlewareChain(req, route.chain)
|
|
586
577
|
const outgoingMsg = await buildOutgoingMessage(id, clientId, res)
|
|
587
578
|
|
|
@@ -754,16 +745,16 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
754
745
|
}
|
|
755
746
|
|
|
756
747
|
return {
|
|
757
|
-
send (
|
|
758
|
-
|
|
759
|
-
},
|
|
760
|
-
sendToGroup (fn, event, body) {
|
|
761
|
-
for (const [clientId, session] of state.activeSessions) {
|
|
762
|
-
const allow = fn(clientId, session.ws.data)
|
|
748
|
+
send (fn, event, body) {
|
|
749
|
+
let index = 0
|
|
763
750
|
|
|
764
|
-
|
|
751
|
+
/* TODO: look into concurrency at some point */
|
|
752
|
+
for (const [clientId, session] of state.activeSessions) {
|
|
753
|
+
if (fn(clientId, session.ws.data, index)) {
|
|
765
754
|
sendToClient(clientId, event, body)
|
|
766
755
|
}
|
|
756
|
+
|
|
757
|
+
index += 1
|
|
767
758
|
}
|
|
768
759
|
},
|
|
769
760
|
broadcast (event, body) {
|
package/src/utils.ts
CHANGED
|
@@ -121,6 +121,18 @@ export type InactiveSession = {
|
|
|
121
121
|
export type ActiveSessions = ReadonlyMap<string, ActiveSession>
|
|
122
122
|
export type Session = ActiveSession | InactiveSession
|
|
123
123
|
|
|
124
|
+
export type FilterFn = (
|
|
125
|
+
clientId: string,
|
|
126
|
+
data: unknown,
|
|
127
|
+
index: number,
|
|
128
|
+
) => boolean
|
|
129
|
+
|
|
130
|
+
export type SocketCommands = {
|
|
131
|
+
send: (fn: FilterFn, event: string, body: unknown) => void
|
|
132
|
+
broadcast: (event: string, body: unknown) => void
|
|
133
|
+
drop: (clientId: string, code?: number, reason?: string) => void
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
export type BaseRequest = {
|
|
125
137
|
method: HttpMethod
|
|
126
138
|
route: string
|
|
@@ -128,9 +140,7 @@ export type BaseRequest = {
|
|
|
128
140
|
params: Record<string, string>
|
|
129
141
|
query: Record<string, unknown>
|
|
130
142
|
json: () => Promise<unknown>
|
|
131
|
-
ws:
|
|
132
|
-
active: ActiveSessions
|
|
133
|
-
}
|
|
143
|
+
ws: SocketCommands
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
export type EndpointRequest = BaseRequest & {
|