sleepy-serv 0.11.0 → 0.13.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: `query(fn)`, `send(fn, event, body)`, `broadcast(event, body)`, and `drop(fn, 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,64 @@ 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 four methods for interacting with connected clients:
516
+
517
+ - `query(fn)`: return a filtered list of active sessions. The filter function receives `(clientId, data, index)` and returns a boolean. Each entry in the returned array is a `SessionEntry` with `clientId` and `app` (the application context). To list all sessions: `app.ws.query(() => true)`.
518
+ - `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)`.
519
+ - `broadcast(event, body)`: push a notification to all connected clients.
520
+ - `drop(fn, code?, reason?)`: close connections matching a filter. The filter function receives `(clientId, data, index)` and returns a boolean. 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. To drop one client: `app.ws.drop(id => id === targetId)`.
521
+
522
+ The same commands are available inside endpoint handlers via `req.ws`:
523
+
524
+ ```js
525
+ export default function (req) {
526
+ const targetId = req.query.targetId
527
+
528
+ req.ws.send(id => id === targetId, 'ping', { from: 'handler' })
529
+
530
+ return Response.json({ ok: true })
531
+ }
532
+ ```
533
+
534
+ This works from both HTTP and WebSocket transports.
535
+
536
+ ## Exports
537
+
538
+ `sleepy-serv` exports several runtime constants and types:
539
+
540
+ - `CloseCode`: WebSocket close codes: `Ok` (1000), `Abnormal` (1006), `Reaped` (4999)
541
+ - `CloseReason`: close reason values: `Ok`, `Dropped`, `Reaped`, `Superseded`
542
+ - `StatusCode`: the full range of HTTP status codes (1xx through 5xx)
543
+ - `HttpMethod`: HTTP verbs: `Head`, `Get`, `Post`, `Put`, `Patch`, `Delete`
544
+ - `SessionEntry`: the shape returned by `query()`: `{ clientId: string, app: unknown }`
545
+ - `FilterFn`: the filter callback type: `(clientId: string, data: unknown, index: number) => boolean`
546
+ - `SocketCommands`: the type for `app.ws` and `req.ws`
547
+ - Error classes for every 4xx and 5xx status (e.g. `NotFoundError`, `UnauthorizedError`, `InternalServerError`)
548
+ - 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 './socket';
7
- export type { ActiveSessions } from './utils';
5
+ export type { FilterFn, SessionEntry, 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,UAAU,EAAE,MAAM,SAAS,CAAA;AAExE,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,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,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAErE,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,EAGd,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,CAgEvE"}
package/dist/utils.d.ts CHANGED
@@ -96,6 +96,17 @@ export type InactiveSession = {
96
96
  };
97
97
  export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
98
98
  export type Session = ActiveSession | InactiveSession;
99
+ export type SessionEntry = {
100
+ clientId: string;
101
+ app: unknown;
102
+ };
103
+ export type FilterFn = (clientId: string, data: unknown, index: number) => boolean;
104
+ export type SocketCommands = {
105
+ query: (fn: FilterFn) => SessionEntry[];
106
+ send: (fn: FilterFn, event: string, body: unknown) => void;
107
+ broadcast: (event: string, body: unknown) => void;
108
+ drop: (fn: FilterFn, code?: number, reason?: string) => void;
109
+ };
99
110
  export type BaseRequest = {
100
111
  method: HttpMethod;
101
112
  route: string;
@@ -103,9 +114,7 @@ export type BaseRequest = {
103
114
  params: Record<string, string>;
104
115
  query: Record<string, unknown>;
105
116
  json: () => Promise<unknown>;
106
- ws: {
107
- active: ActiveSessions;
108
- };
117
+ ws: SocketCommands;
109
118
  };
110
119
  export type EndpointRequest = BaseRequest & {
111
120
  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.11.0",
6
+ "version": "0.13.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,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 './socket'
56
- export type { ActiveSessions } from './utils'
54
+ export type { FilterFn, SessionEntry, 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,12 @@ import type {
36
36
  HttpMethod,
37
37
  Request,
38
38
  MiddlewareChain,
39
+ SocketCommands,
40
+ SessionEntry,
39
41
  WebSocketRequest,
40
42
  SocketData,
41
43
  SocketConnection,
42
44
  ActiveSession,
43
- ActiveSessions,
44
45
  InactiveSession,
45
46
  Session,
46
47
  AppOptions,
@@ -54,7 +55,6 @@ import type {
54
55
  } from './messages'
55
56
 
56
57
  type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult
57
- type FilterFn = (clientId: string, data: unknown) => boolean
58
58
 
59
59
  type UpgradeData = {
60
60
  clientId?: string
@@ -117,13 +117,6 @@ export type SocketRoute = {
117
117
  chain: MiddlewareChain
118
118
  }
119
119
 
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
120
  const ajv = new Ajv({
128
121
  allErrors: true,
129
122
  })
@@ -361,7 +354,7 @@ function buildParams (
361
354
  function buildRequest (
362
355
  params: Record<string, string>,
363
356
  message: RequestMessage,
364
- activeSessions: ActiveSessions,
357
+ ws: SocketCommands,
365
358
  ): WebSocketRequest {
366
359
  const { id, clientId, method, route } = message
367
360
  const headers = new Headers(message.headers ?? {})
@@ -376,10 +369,8 @@ function buildRequest (
376
369
  headers,
377
370
  params,
378
371
  query,
372
+ ws,
379
373
  json,
380
- ws: {
381
- active: activeSessions,
382
- },
383
374
  }
384
375
  }
385
376
 
@@ -447,6 +438,7 @@ export function buildSocketState (opts: AppOptions = {}): SocketState {
447
438
  export function buildSocketServer (
448
439
  routes: SocketRoute[],
449
440
  state: SocketState,
441
+ commands: SocketCommands,
450
442
  ): WebSocketHandler<SocketData> {
451
443
  const {
452
444
  dropThreshold,
@@ -581,7 +573,7 @@ export function buildSocketServer (
581
573
  const { id, clientId } = message
582
574
  const route = matchRoute(routes, message)
583
575
  const params = buildParams(route, message)
584
- const req = buildRequest(params, message, activeSessions)
576
+ const req = buildRequest(params, message, commands)
585
577
  const res = await executeMiddlewareChain(req, route.chain)
586
578
  const outgoingMsg = await buildOutgoingMessage(id, clientId, res)
587
579
 
@@ -754,16 +746,33 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
754
746
  }
755
747
 
756
748
  return {
757
- send (clientId, event, body) {
758
- sendToClient(clientId, event, body)
759
- },
760
- sendToGroup (fn, event, body) {
749
+ query (fn) {
750
+ const results: SessionEntry[] = []
751
+ let index = 0
752
+
761
753
  for (const [clientId, session] of state.activeSessions) {
762
- const allow = fn(clientId, session.ws.data)
754
+ if (fn(clientId, session.ws.data, index)) {
755
+ results.push({
756
+ clientId,
757
+ app: session.ws.data.app,
758
+ })
759
+ }
760
+
761
+ index += 1
762
+ }
763
763
 
764
- if (allow) {
764
+ return results
765
+ },
766
+ send (fn, event, body) {
767
+ let index = 0
768
+
769
+ /* TODO: look into concurrency at some point */
770
+ for (const [clientId, session] of state.activeSessions) {
771
+ if (fn(clientId, session.ws.data, index)) {
765
772
  sendToClient(clientId, event, body)
766
773
  }
774
+
775
+ index += 1
767
776
  }
768
777
  },
769
778
  broadcast (event, body) {
@@ -771,14 +780,16 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
771
780
  sendToClient(clientId, event, body)
772
781
  }
773
782
  },
774
- drop (clientId, code, reason) {
775
- const session = state.activeSessions.get(clientId)
783
+ drop (fn, code, reason) {
784
+ let index = 0
776
785
 
777
- if (!session) {
778
- throw new ReferenceError(`No active socket for client: ${clientId}`)
779
- }
786
+ for (const [clientId, session] of state.activeSessions) {
787
+ if (fn(clientId, session.ws.data, index)) {
788
+ session.ws.close(code, reason)
789
+ }
780
790
 
781
- session.ws.close(code, reason)
791
+ index += 1
792
+ }
782
793
  },
783
794
  }
784
795
  }
package/src/utils.ts CHANGED
@@ -121,6 +121,24 @@ export type InactiveSession = {
121
121
  export type ActiveSessions = ReadonlyMap<string, ActiveSession>
122
122
  export type Session = ActiveSession | InactiveSession
123
123
 
124
+ export type SessionEntry = {
125
+ clientId: string
126
+ app: unknown
127
+ }
128
+
129
+ export type FilterFn = (
130
+ clientId: string,
131
+ data: unknown,
132
+ index: number,
133
+ ) => boolean
134
+
135
+ export type SocketCommands = {
136
+ query: (fn: FilterFn) => SessionEntry[]
137
+ send: (fn: FilterFn, event: string, body: unknown) => void
138
+ broadcast: (event: string, body: unknown) => void
139
+ drop: (fn: FilterFn, code?: number, reason?: string) => void
140
+ }
141
+
124
142
  export type BaseRequest = {
125
143
  method: HttpMethod
126
144
  route: string
@@ -128,9 +146,7 @@ export type BaseRequest = {
128
146
  params: Record<string, string>
129
147
  query: Record<string, unknown>
130
148
  json: () => Promise<unknown>
131
- ws: {
132
- active: ActiveSessions
133
- }
149
+ ws: SocketCommands
134
150
  }
135
151
 
136
152
  export type EndpointRequest = BaseRequest & {