sleepy-serv 0.21.0 → 0.23.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
@@ -65,7 +65,7 @@ object with these properties:
65
65
  directly.
66
66
  - `ws`: WebSocket commands for interacting with connected clients:
67
67
  `query(fn)`, `send(fn, event, body)`, `broadcast(event, body)`, and
68
- `drop(fn, code?, reason?)`.
68
+ `drop(signal, fn)`.
69
69
  - `close`: an `async` function that shuts the app down. See
70
70
  [Shutting Down](#shutting-down).
71
71
 
@@ -476,7 +476,13 @@ const app = createApp(3000, {
476
476
 
477
477
  App-level middleware is executed before any directory-level or route-level middleware.
478
478
 
479
- Note that the reserved `/ws` handshake routes are folded into these same chains, so app-level middleware runs against them too. A catch-all validator (for example a `validateSchemas` that requires a JSON body or a specific header on every request) will therefore also run against the body-less `/ws` handshake requests and reject them. Scope such validators below the reserved paths rather than applying them app-wide.
479
+ When WebSocket support is enabled (`ws: true` or `ws: { ... }`), the
480
+ reserved `/ws` handshake routes are folded into these same chains, so
481
+ app-level middleware runs against them too. A catch-all validator (for
482
+ example a `validateSchemas` that requires a JSON body or a specific
483
+ header on every request) will therefore also run against the body-less
484
+ `/ws` handshake requests and reject them. Scope such validators below
485
+ the reserved paths rather than applying them app-wide.
480
486
 
481
487
  ### `hostname`
482
488
 
@@ -528,7 +534,14 @@ const app = createApp(3000, {
528
534
 
529
535
  ### `ws`
530
536
 
531
- WebSocket tuning and lifecycle hooks:
537
+ WebSocket support is opt-in. Pass `ws: true` to enable with defaults,
538
+ or pass an options object to customize behavior:
539
+
540
+ ```js
541
+ createApp(3000, { ws: true })
542
+ ```
543
+
544
+ With custom tuning and lifecycle hooks:
532
545
 
533
546
  ```js
534
547
  const app = createApp(3000, {
@@ -538,17 +551,21 @@ const app = createApp(3000, {
538
551
  reclaimTtl: 300_000,
539
552
  ticketTtl: 10_000,
540
553
  onOpen: clientId => console.log('connected:', clientId),
541
- onClose: (clientId, reason) => console.log('closed:', clientId, reason),
554
+ onClose: (clientId, signal) => console.log('closed:', clientId, signal),
542
555
  },
543
556
  })
544
557
  ```
545
558
 
559
+ When `ws` is omitted (or `false`), no `/ws` endpoints are registered
560
+ and the server does not accept WebSocket upgrades. Calling `app.ws` or
561
+ `req.ws` methods when WebSocket support is disabled throws an error.
562
+
546
563
  - `heartbeatInterval`: how often the client should send heartbeats, in milliseconds. Sent to the client in the welcome message. Defaults to `30_000`.
547
564
  - `dropThreshold`: how long the server waits without an inbound message before reaping the connection, in milliseconds. Defaults to `120_000`.
548
565
  - `reclaimTtl`: how long an inactive (reaped/dropped) session stays reclaimable, in milliseconds. Defaults to `300_000`.
549
566
  - `ticketTtl`: how long a minted upgrade ticket stays valid, in milliseconds. Defaults to `10_000`.
550
567
  - `onOpen(clientId)`: fires after a client's welcome message is sent. Wrapped in try/catch so a throwing hook does not break the connection.
551
- - `onClose(clientId, reason)`: fires when a connection closes. `reason` is a `CloseReason` value: `'ok'`, `'dropped'`, `'reaped'`, or `'superseded'`. Also wrapped in try/catch.
568
+ - `onClose(clientId, signal)`: fires when a connection closes. `signal` is a `CloseSignal` value (`{ code, reason }`). Also wrapped in try/catch.
552
569
 
553
570
  ## WebSocket Commands
554
571
 
@@ -557,7 +574,7 @@ The `app.ws` object exposes four methods for interacting with connected clients:
557
574
  - `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)`.
558
575
  - `send(event, body, fn)`: 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('ping', body, id => id === targetId)`.
559
576
  - `broadcast(event, body)`: push a notification to all connected clients.
560
- - `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)`.
577
+ - `drop(signal, fn)`: close connections matching a filter. `signal` is a `CloseSignal` (`{ code, reason }`) with `code` validated in the range [4000-4099]. The filter function receives `(clientId, data, index)` and returns a boolean. To drop one client: `app.ws.drop({ code: 4000, reason: 'kicked' }, id => id === targetId)`.
561
578
 
562
579
  The same commands are available inside endpoint handlers via `req.ws`:
563
580
 
@@ -577,8 +594,8 @@ This works from both HTTP and WebSocket transports.
577
594
 
578
595
  `sleepy-serv` exports several runtime constants and types:
579
596
 
580
- - `CloseCode`: WebSocket close codes: `Ok` (1000), `Abnormal` (1006), `Reaped` (4999)
581
- - `CloseReason`: close reason values: `Ok`, `Dropped`, `Reaped`, `Superseded`
597
+ - `InternalCloseSignal`: internal close signals: `Ok` (`{ code: 1000, reason: 'ok' }`), `Reaped` (`{ code: 4998, reason: 'reaped' }`), `Superseded` (`{ code: 4999, reason: 'superseded' }`)
598
+ - `CloseSignal`: the type for close signals: `{ code: number, reason: string }`
582
599
  - `StatusCode`: the full range of HTTP status codes (1xx through 5xx)
583
600
  - `HttpMethod`: HTTP verbs: `Head`, `Get`, `Post`, `Put`, `Patch`, `Delete`
584
601
  - `SessionEntry`: the shape returned by `query()`: `{ clientId: string, app: unknown }`
@@ -1,9 +1,9 @@
1
1
  import type { RouteConfig, SocketCommands, AppOptions, Server } from './utils';
2
2
  export * from './errors';
3
- export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils';
3
+ export { StatusCode, HttpMethod, InternalCloseSignal, } from './utils';
4
4
  export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
5
5
  export type { FilterFn, SessionEntry, SocketCommands } from './utils';
6
- export type { AppOptions, AsyncHandlerResult, BaseRequest, EndpointRequest, FormattedError, Handler, MetaEntry, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, RouteConfig, RouteDefinition, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
6
+ export type { AppOptions, AsyncHandlerResult, BaseRequest, CloseSignal, EndpointRequest, FormattedError, Handler, MetaEntry, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, RouteConfig, RouteDefinition, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
7
7
  export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
8
8
  type OutputRoutes = Record<string, string[]>;
9
9
  type CloseFn = (force?: boolean) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAOV,WAAW,EAEX,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,SAAS,EACT,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,WAAW,EACX,eAAe,EACf,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;AA0B5C,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;AAgRD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,UAAe,GACpB,GAAG,CAaL"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAOV,WAAW,EAEX,cAAc,EAEd,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAOhB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,UAAU,EACV,UAAU,EACV,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAEhB,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,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,SAAS,EACT,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,WAAW,EACX,eAAe,EACf,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;AA0B5C,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;AAsRD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,UAAe,GACpB,GAAG,CAqBL"}
@@ -1,6 +1,5 @@
1
- import { CloseReason } from './utils';
2
1
  import type { WebSocketHandler } from 'bun';
3
- import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, AppOptions } from './utils';
2
+ import type { AsyncHandlerResult, CloseSignal, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, SocketOptions } from './utils';
4
3
  type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
5
4
  type SocketEndpoint = {
6
5
  method: HttpMethod;
@@ -22,7 +21,7 @@ export type SocketState = {
22
21
  activeSessions: Map<string, ActiveSession>;
23
22
  inactiveSessions: Map<string, InactiveSession>;
24
23
  onOpen: ((clientId: string) => void) | null;
25
- onClose: ((clientId: string, reason: CloseReason) => void) | null;
24
+ onClose: ((clientId: string, signal: CloseSignal) => void) | null;
26
25
  };
27
26
  export type SocketRoute = {
28
27
  method: HttpMethod;
@@ -30,9 +29,10 @@ export type SocketRoute = {
30
29
  segments: string[];
31
30
  chain: MiddlewareChain;
32
31
  };
33
- export declare function buildSocketState(opts?: AppOptions): SocketState;
32
+ export declare function buildSocketState(opts?: SocketOptions): SocketState;
34
33
  export declare function buildSocketServer(routes: SocketRoute[], state: SocketState, commands: SocketCommands): WebSocketHandler<SocketData>;
35
34
  export declare function buildSocketHandlers(state: SocketState): SocketEndpoint[];
35
+ export declare function buildDisabledSocketCommands(): SocketCommands;
36
36
  export declare function buildSocketCommands(state: SocketState): SocketCommands;
37
37
  export {};
38
38
  //# sourceMappingURL=socket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/core/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"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/core/socket.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,OAAO,EAEP,eAAe,EACf,cAAc,EAGd,UAAU,EAEV,aAAa,EACb,eAAe,EAEf,aAAa,EACd,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;AA+QD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,aAAkB,GAAG,WAAW,CAavE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,cAAc,GACvB,gBAAgB,CAAC,UAAU,CAAC,CA8J9B;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,EAAE,CAwIzE;AAED,wBAAgB,2BAA2B,IAAK,cAAc,CAS7D;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,CAwEvE"}
@@ -104,7 +104,7 @@ export type FilterFn = (clientId: string, data: unknown, index: number) => boole
104
104
  export type SocketCommands = {
105
105
  broadcast: (event: string, body: unknown) => void;
106
106
  send: (event: string, body: unknown, fn: FilterFn) => void;
107
- drop: (fn: FilterFn, code?: number, reason?: string) => void;
107
+ drop: (signal: CloseSignal, fn: FilterFn) => void;
108
108
  query: (fn: FilterFn) => SessionEntry[];
109
109
  };
110
110
  export type BaseRequest = {
@@ -125,19 +125,24 @@ export type WebSocketRequest = BaseRequest & {
125
125
  clientId: string;
126
126
  };
127
127
  export type Request = EndpointRequest | WebSocketRequest;
128
- export declare const CloseCode: {
129
- readonly Ok: 1000;
130
- readonly Abnormal: 1006;
131
- readonly Reaped: 4999;
132
- };
133
- export type CloseCode = typeof CloseCode[keyof typeof CloseCode];
134
- export declare const CloseReason: {
135
- readonly Ok: "ok";
136
- readonly Dropped: "dropped";
137
- readonly Reaped: "reaped";
138
- readonly Superseded: "superseded";
139
- };
140
- export type CloseReason = typeof CloseReason[keyof typeof CloseReason];
128
+ export type CloseSignal = {
129
+ code: number;
130
+ reason: string;
131
+ };
132
+ export declare const InternalCloseSignal: {
133
+ readonly Ok: {
134
+ readonly code: 1000;
135
+ readonly reason: "ok";
136
+ };
137
+ readonly Reaped: {
138
+ readonly code: 4998;
139
+ readonly reason: "reaped";
140
+ };
141
+ readonly Superseded: {
142
+ readonly code: 4999;
143
+ readonly reason: "superseded";
144
+ };
145
+ };
141
146
  export type SocketOptions = {
142
147
  dropThreshold?: number;
143
148
  heartbeatInterval?: number;
@@ -145,7 +150,7 @@ export type SocketOptions = {
145
150
  reclaimTtl?: number;
146
151
  ticketTtl?: number;
147
152
  onOpen?: (clientId: string) => void;
148
- onClose?: (clientId: string, reason: CloseReason) => void;
153
+ onClose?: (clientId: string, signal: CloseSignal) => void;
149
154
  };
150
155
  export type SocketData = {
151
156
  clientId: string;
@@ -177,7 +182,7 @@ export type AppOptions = {
177
182
  hostname?: string;
178
183
  mountPath?: string;
179
184
  middleware?: Middleware[];
180
- ws?: SocketOptions;
185
+ ws?: boolean | SocketOptions;
181
186
  onClose?: () => Promise<void> | void;
182
187
  };
183
188
  export declare function toSegments(pathString: string): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/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.21.0",
6
+ "version": "0.23.0",
7
7
  "type": "module",
8
8
  "bin": {
9
9
  "sleepy": "./src/cli/index.ts"
package/src/core/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  buildSocketServer,
14
14
  buildSocketHandlers,
15
15
  buildSocketCommands,
16
+ buildDisabledSocketCommands,
16
17
  } from './socket'
17
18
 
18
19
  import {
@@ -34,6 +35,7 @@ import type {
34
35
  RouteConfig,
35
36
  RouteDefinition,
36
37
  SocketCommands,
38
+ SocketOptions,
37
39
  AppOptions,
38
40
  Server,
39
41
  } from './utils'
@@ -44,7 +46,12 @@ import type {
44
46
  } from './socket'
45
47
 
46
48
  export * from './errors'
47
- export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils'
49
+
50
+ export {
51
+ StatusCode,
52
+ HttpMethod,
53
+ InternalCloseSignal,
54
+ } from './utils'
48
55
 
49
56
  export {
50
57
  parseJsonBody,
@@ -58,6 +65,7 @@ export type {
58
65
  AppOptions,
59
66
  AsyncHandlerResult,
60
67
  BaseRequest,
68
+ CloseSignal,
61
69
  EndpointRequest,
62
70
  FormattedError,
63
71
  Handler,
@@ -131,6 +139,20 @@ function defaultMethodMap (): Record<string, EndpointHandler> {
131
139
  }
132
140
  }
133
141
 
142
+ function resolveSocketOptions (
143
+ ws: boolean | SocketOptions | undefined,
144
+ ): SocketOptions | null {
145
+ if (!ws) {
146
+ return null
147
+ }
148
+
149
+ if (ws === true) {
150
+ return {}
151
+ }
152
+
153
+ return ws
154
+ }
155
+
134
156
  function buildEndpointRequest (
135
157
  bunReq: BunRequest,
136
158
  server: Server,
@@ -162,9 +184,7 @@ function buildEndpointRequest (
162
184
  }
163
185
  }
164
186
 
165
- function normalizeChain (
166
- route: RouteDefinition,
167
- ): ChainRoute {
187
+ function normalizeChain (route: RouteDefinition): ChainRoute {
168
188
  const chain = Array.isArray(route.chain)
169
189
  ? route.chain
170
190
  : [route.chain]
@@ -268,7 +288,7 @@ function buildOutputRoutes (moduleRoutes: ModuleRoute[]): OutputRoutes {
268
288
 
269
289
  function buildRoutes (
270
290
  config: RouteConfig,
271
- state: SocketState,
291
+ state: SocketState | null,
272
292
  ws: SocketCommands,
273
293
  opts: AppOptions,
274
294
  ): AppRoutes {
@@ -294,13 +314,9 @@ function buildRoutes (
294
314
  }
295
315
  })
296
316
 
297
- const mergedRoutes = buildMergedRoutes(
298
- normalRoutes,
299
- middleware,
300
- meta,
301
- state,
302
- mountPath,
303
- )
317
+ const mergedRoutes = state
318
+ ? buildMergedRoutes(normalRoutes, middleware, meta, state, mountPath)
319
+ : normalRoutes
304
320
 
305
321
  const socketRoutes = buildSocketRoutes(mergedRoutes)
306
322
  const moduleRoutes = buildModuleRoutes(socketRoutes, ws)
@@ -317,23 +333,21 @@ function buildRoutes (
317
333
  function buildServer (
318
334
  port: number,
319
335
  routes: AppRoutes,
320
- state: SocketState,
336
+ state: SocketState | null,
321
337
  ws: SocketCommands,
322
338
  opts: AppOptions,
323
339
  ): Server {
324
340
  const hostname = opts.hostname || '0.0.0.0'
325
341
 
326
- const websocketServer = buildSocketServer(
327
- routes.socket,
328
- state,
329
- ws,
330
- )
342
+ const websocket = state
343
+ ? buildSocketServer(routes.socket, state, ws)
344
+ : undefined
331
345
 
332
346
  return Bun.serve({
333
347
  port,
334
348
  hostname,
335
349
  routes: routes.server,
336
- websocket: websocketServer,
350
+ ...(websocket ? { websocket } : {}),
337
351
  async fetch (_req, _server) {
338
352
  throw new NotFoundError()
339
353
  },
@@ -346,7 +360,7 @@ function buildServer (
346
360
 
347
361
  return Response.json(httpError.output, { status })
348
362
  },
349
- })
363
+ }) as Server
350
364
  }
351
365
 
352
366
  function processIO (server: Server, opts: AppOptions): CloseFn {
@@ -391,8 +405,16 @@ export function createApp (
391
405
  config: RouteConfig,
392
406
  opts: AppOptions = {},
393
407
  ): App {
394
- const state = buildSocketState(opts)
395
- const ws = buildSocketCommands(state)
408
+ const socketOpts = resolveSocketOptions(opts.ws)
409
+
410
+ const state = socketOpts
411
+ ? buildSocketState(socketOpts)
412
+ : null
413
+
414
+ const ws = state
415
+ ? buildSocketCommands(state)
416
+ : buildDisabledSocketCommands()
417
+
396
418
  const routes = buildRoutes(config, state, ws, opts)
397
419
  const server = buildServer(port, routes, state, ws, opts)
398
420
  const close = processIO(server, opts)
@@ -10,8 +10,7 @@ import {
10
10
 
11
11
  import {
12
12
  StatusCode,
13
- CloseCode,
14
- CloseReason,
13
+ InternalCloseSignal,
15
14
  toSegments,
16
15
  formatError,
17
16
  executeMiddlewareChain,
@@ -19,7 +18,6 @@ import {
19
18
 
20
19
  import {
21
20
  RequestError,
22
- BadRequestError,
23
21
  NotFoundError,
24
22
  UnauthorizedError,
25
23
  MethodNotAllowedError,
@@ -33,8 +31,10 @@ import type { WebSocketHandler } from 'bun'
33
31
 
34
32
  import type {
35
33
  AsyncHandlerResult,
34
+ CloseSignal,
36
35
  HttpMethod,
37
36
  Request,
37
+ FilterFn,
38
38
  MiddlewareChain,
39
39
  SocketCommands,
40
40
  SessionEntry,
@@ -44,7 +44,7 @@ import type {
44
44
  ActiveSession,
45
45
  InactiveSession,
46
46
  Session,
47
- AppOptions,
47
+ SocketOptions,
48
48
  } from './utils'
49
49
 
50
50
  import type {
@@ -107,7 +107,7 @@ export type SocketState = {
107
107
  activeSessions: Map<string, ActiveSession>
108
108
  inactiveSessions: Map<string, InactiveSession>
109
109
  onOpen: ((clientId: string) => void) | null
110
- onClose: ((clientId: string, reason: CloseReason) => void) | null
110
+ onClose: ((clientId: string, signal: CloseSignal) => void) | null
111
111
  }
112
112
 
113
113
  export type SocketRoute = {
@@ -240,30 +240,6 @@ function parseMessage (raw: string | Buffer): RawMessage | undefined {
240
240
  }
241
241
  }
242
242
 
243
- async function parseJsonBody (req: Request): Promise<unknown> {
244
- try {
245
- const body = await req.json()
246
-
247
- return body
248
- } catch {
249
- throw new BadRequestError('Invalid JSON')
250
- }
251
- }
252
-
253
- async function parseJsonBodyAppData (req: Request): Promise<unknown> {
254
- const contentType = req.headers.get('content-type')
255
- const usingJsonBody = contentType?.startsWith('application/json')
256
-
257
- if (!usingJsonBody) {
258
- return null
259
- }
260
-
261
- const rawBody = await parseJsonBody(req)
262
- const appData = (rawBody as Record<string, unknown> | null)?.data ?? null
263
-
264
- return appData
265
- }
266
-
267
243
  function sweepInactiveSessions (state: SocketState): void {
268
244
  for (const [key, session] of state.inactiveSessions) {
269
245
  if (!isSessionActive(session)) {
@@ -410,28 +386,18 @@ function buildErrorMessage (
410
386
  })
411
387
  }
412
388
 
413
- function getCloseReason (ws: SocketConnection, code: number): CloseReason {
414
- if (ws.data.reaped) {
415
- return CloseReason.Reaped
416
- } else if (code === CloseCode.Ok) {
417
- return CloseReason.Ok
418
- }
419
-
420
- return CloseReason.Dropped
421
- }
422
-
423
- export function buildSocketState (opts: AppOptions = {}): SocketState {
389
+ export function buildSocketState (opts: SocketOptions = {}): SocketState {
424
390
  return {
425
- dropThreshold: opts.ws?.dropThreshold ?? 120_000,
426
- heartbeatInterval: opts.ws?.heartbeatInterval ?? 30_000,
427
- maxTickets: opts.ws?.maxTickets ?? 100_000,
428
- reclaimTtl: opts.ws?.reclaimTtl ?? 300_000,
429
- ticketTtl: opts.ws?.ticketTtl ?? 10_000,
391
+ dropThreshold: opts.dropThreshold ?? 120_000,
392
+ heartbeatInterval: opts.heartbeatInterval ?? 30_000,
393
+ maxTickets: opts.maxTickets ?? 100_000,
394
+ reclaimTtl: opts.reclaimTtl ?? 300_000,
395
+ ticketTtl: opts.ticketTtl ?? 10_000,
430
396
  tickets: new Map(),
431
397
  activeSessions: new Map(),
432
398
  inactiveSessions: new Map(),
433
- onOpen: opts.ws?.onOpen ?? null,
434
- onClose: opts.ws?.onClose ?? null,
399
+ onOpen: opts.onOpen ?? null,
400
+ onClose: opts.onClose ?? null,
435
401
  }
436
402
  }
437
403
 
@@ -458,7 +424,10 @@ export function buildSocketServer (
458
424
  ws.data.reaperHandle = setTimeout(() => {
459
425
  ws.data.reaped = true
460
426
 
461
- ws.close(CloseCode.Reaped)
427
+ ws.close(
428
+ InternalCloseSignal.Reaped.code,
429
+ InternalCloseSignal.Reaped.reason,
430
+ )
462
431
  }, dropThreshold)
463
432
  }
464
433
 
@@ -472,10 +441,10 @@ export function buildSocketServer (
472
441
  }
473
442
  }
474
443
 
475
- function invokeClose (ws: SocketConnection, reason: CloseReason) {
444
+ function invokeClose (ws: SocketConnection, signal: CloseSignal) {
476
445
  if (onClose) {
477
446
  try {
478
- onClose(ws.data.clientId, reason)
447
+ onClose(ws.data.clientId, signal)
479
448
  } catch (err) {
480
449
  console.error(err)
481
450
  }
@@ -492,7 +461,10 @@ export function buildSocketServer (
492
461
  if (existingSession) {
493
462
  existingSession.ws.data.superseded = true
494
463
 
495
- existingSession.ws.close()
464
+ existingSession.ws.close(
465
+ InternalCloseSignal.Superseded.code,
466
+ InternalCloseSignal.Superseded.reason,
467
+ )
496
468
  }
497
469
 
498
470
  inactiveSessions.delete(ws.data.clientId)
@@ -519,18 +491,22 @@ export function buildSocketServer (
519
491
  ws.send(JSON.stringify(welcomeMessage))
520
492
  invokeOpen(ws)
521
493
  },
522
- close (ws: SocketConnection, code: number): void {
494
+ close (ws: SocketConnection, code: number, reason: string): void {
495
+ const signal: CloseSignal = {
496
+ code,
497
+ reason,
498
+ }
499
+
523
500
  if (ws.data.reaperHandle) {
524
501
  clearTimeout(ws.data.reaperHandle)
525
502
  }
526
503
 
527
504
  if (ws.data.superseded) {
528
- invokeClose(ws, CloseReason.Superseded)
505
+ invokeClose(ws, signal)
529
506
 
530
507
  return
531
508
  }
532
509
 
533
- const reason = getCloseReason(ws, code)
534
510
  const exists = activeSessions.get(ws.data.clientId)
535
511
 
536
512
  if (!exists || exists.ws !== ws) {
@@ -539,7 +515,7 @@ export function buildSocketServer (
539
515
 
540
516
  activeSessions.delete(ws.data.clientId)
541
517
 
542
- if (code !== CloseCode.Ok || ws.data.reaped) {
518
+ if (code !== InternalCloseSignal.Ok.code || ws.data.reaped) {
543
519
  inactiveSessions.set(ws.data.clientId, {
544
520
  token: exists.token,
545
521
  expiresAt: Date.now() + reclaimTtl,
@@ -547,7 +523,7 @@ export function buildSocketServer (
547
523
  })
548
524
  }
549
525
 
550
- invokeClose(ws, reason)
526
+ invokeClose(ws, signal)
551
527
  },
552
528
  async message (ws: SocketConnection, raw: string | Buffer): Promise<void> {
553
529
  const incomingMsg = parseMessage(raw)
@@ -677,8 +653,7 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
677
653
  validateSchema(req, createTicketValidator)
678
654
 
679
655
  const clientId = crypto.randomUUID()
680
- const appData = await parseJsonBodyAppData(req)
681
- const ticket = issueTicket(clientId, appData)
656
+ const ticket = issueTicket(clientId, res)
682
657
 
683
658
  return Response.json({
684
659
  clientId,
@@ -728,6 +703,17 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
728
703
  ]
729
704
  }
730
705
 
706
+ export function buildDisabledSocketCommands (): SocketCommands {
707
+ const msg = 'WebSocket support is not enabled'
708
+
709
+ return {
710
+ broadcast () { throw new Error(msg) },
711
+ send () { throw new Error(msg) },
712
+ drop () { throw new Error(msg) },
713
+ query () { throw new Error(msg) },
714
+ }
715
+ }
716
+
731
717
  export function buildSocketCommands (state: SocketState): SocketCommands {
732
718
  function sendToClient (clientId: string, event: string, body: unknown) {
733
719
  const session = state.activeSessions.get(clientId)
@@ -746,12 +732,12 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
746
732
  }
747
733
 
748
734
  return {
749
- broadcast (event, body) {
735
+ broadcast (event: string, body: unknown) {
750
736
  for (const clientId of state.activeSessions.keys()) {
751
737
  sendToClient(clientId, event, body)
752
738
  }
753
739
  },
754
- send (event, body, fn) {
740
+ send (event: string, body: unknown, fn: FilterFn) {
755
741
  let index = 0
756
742
 
757
743
  /* TODO: look into concurrency at some point */
@@ -763,9 +749,17 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
763
749
  index += 1
764
750
  }
765
751
  },
766
- drop (fn, code, reason) {
752
+ drop (signal: CloseSignal, fn: FilterFn) {
753
+ const { code, reason } = signal
754
+
767
755
  let index = 0
768
756
 
757
+ if (!Number.isInteger(code) || code < 4000 || code > 4099) {
758
+ throw new RangeError(
759
+ `Signal code must be an integer in [4000, 4099], got ${code}`,
760
+ )
761
+ }
762
+
769
763
  for (const [clientId, session] of state.activeSessions) {
770
764
  if (fn(clientId, session.ws.data, index)) {
771
765
  session.ws.close(code, reason)
@@ -774,7 +768,7 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
774
768
  index += 1
775
769
  }
776
770
  },
777
- query (fn) {
771
+ query (fn: FilterFn) {
778
772
  const results: SessionEntry[] = []
779
773
  let index = 0
780
774
 
package/src/core/utils.ts CHANGED
@@ -135,7 +135,7 @@ export type FilterFn = (
135
135
  export type SocketCommands = {
136
136
  broadcast: (event: string, body: unknown) => void
137
137
  send: (event: string, body: unknown, fn: FilterFn) => void
138
- drop: (fn: FilterFn, code?: number, reason?: string) => void
138
+ drop: (signal: CloseSignal, fn: FilterFn) => void
139
139
  query: (fn: FilterFn) => SessionEntry[]
140
140
  }
141
141
 
@@ -161,23 +161,26 @@ export type WebSocketRequest = BaseRequest & {
161
161
 
162
162
  export type Request = EndpointRequest | WebSocketRequest
163
163
 
164
- export const CloseCode = {
165
- Ok: 1000,
166
- Abnormal: 1006,
167
- Reaped: 4999,
168
- } as const
169
-
170
- export type CloseCode = typeof CloseCode[keyof typeof CloseCode]
164
+ export type CloseSignal = {
165
+ code: number
166
+ reason: string
167
+ }
171
168
 
172
- export const CloseReason = {
173
- Ok: 'ok',
174
- Dropped: 'dropped',
175
- Reaped: 'reaped',
176
- Superseded: 'superseded',
169
+ export const InternalCloseSignal = {
170
+ Ok: {
171
+ code: 1000,
172
+ reason: 'ok',
173
+ },
174
+ Reaped: {
175
+ code: 4998,
176
+ reason: 'reaped',
177
+ },
178
+ Superseded: {
179
+ code: 4999,
180
+ reason: 'superseded',
181
+ },
177
182
  } as const
178
183
 
179
- export type CloseReason = typeof CloseReason[keyof typeof CloseReason]
180
-
181
184
  export type SocketOptions = {
182
185
  dropThreshold?: number
183
186
  heartbeatInterval?: number
@@ -185,7 +188,7 @@ export type SocketOptions = {
185
188
  reclaimTtl?: number
186
189
  ticketTtl?: number
187
190
  onOpen?: (clientId: string) => void
188
- onClose?: (clientId: string, reason: CloseReason) => void
191
+ onClose?: (clientId: string, signal: CloseSignal) => void
189
192
  }
190
193
 
191
194
  export type SocketData = {
@@ -224,7 +227,7 @@ export type AppOptions = {
224
227
  hostname?: string
225
228
  mountPath?: string
226
229
  middleware?: Middleware[]
227
- ws?: SocketOptions
230
+ ws?: boolean | SocketOptions
228
231
  onClose?: () => Promise<void> | void
229
232
  }
230
233