sleepy-serv 0.10.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 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
- - `commands`: WebSocket helpers for pushing messages out to connected clients: `send(clientId, event, body)` and `broadcast(event, body)`.
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
- export { StatusCode, CloseCode, CloseReason } from './utils';
3
+ export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils';
5
4
  export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
6
- export type { SocketCommands } from './socket';
7
- export type { HttpMethod, 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>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAOV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE5D,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEzD,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,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAyaD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
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 {};
@@ -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,EAEf,UAAU,EAEV,aAAa,EAEb,eAAe,EAEf,UAAU,EACX,MAAM,SAAS,CAAA;AAShB,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAA;AACvE,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;AAY5D,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;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9D,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACjE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACjD,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACjE,CAAA;AAmTD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,UAAe,GAAG,WAAW,CAapE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,GACjB,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"}
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;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A more opinionated web server for building REST-ful applications",
4
4
  "author": "Travis J True",
5
5
  "license": "MIT",
6
- "version": "0.10.0",
6
+ "version": "0.12.0",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": {
package/src/index.ts CHANGED
@@ -32,7 +32,7 @@ import type {
32
32
  Middleware,
33
33
  MiddlewareChain,
34
34
  EndpointRequest,
35
- ActiveSessions,
35
+ SocketCommands,
36
36
  AppOptions,
37
37
  Server,
38
38
  } from './utils'
@@ -40,11 +40,10 @@ 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'
47
- export { StatusCode, CloseCode, CloseReason } from './utils'
46
+ export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils'
48
47
 
49
48
  export {
50
49
  parseJsonBody,
@@ -52,8 +51,7 @@ export {
52
51
  validateSchemas,
53
52
  } from './middleware'
54
53
 
55
- export type { SocketCommands } from './socket'
56
- export type { HttpMethod, 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
- activeSessions: ActiveSessions,
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
- state: SocketState,
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, state)
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
- const websocketServer = buildSocketServer(routes.socket, state)
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 routes = await buildRoutes(rootPath, state, opts)
565
- const server = buildServer(port, routes, state, opts)
566
- const commands = buildSocketCommands(state)
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
- commands,
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
- activeSessions: ActiveSessions,
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, activeSessions)
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 (clientId, event, body) {
758
- sendToClient(clientId, event, body)
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
- if (allow) {
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 & {