sleepy-serv 0.22.0 → 0.24.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
 
@@ -551,7 +551,7 @@ const app = createApp(3000, {
551
551
  reclaimTtl: 300_000,
552
552
  ticketTtl: 10_000,
553
553
  onOpen: clientId => console.log('connected:', clientId),
554
- onClose: (clientId, reason) => console.log('closed:', clientId, reason),
554
+ onClose: (clientId, signal) => console.log('closed:', clientId, signal),
555
555
  },
556
556
  })
557
557
  ```
@@ -565,16 +565,16 @@ and the server does not accept WebSocket upgrades. Calling `app.ws` or
565
565
  - `reclaimTtl`: how long an inactive (reaped/dropped) session stays reclaimable, in milliseconds. Defaults to `300_000`.
566
566
  - `ticketTtl`: how long a minted upgrade ticket stays valid, in milliseconds. Defaults to `10_000`.
567
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.
568
- - `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.
569
569
 
570
570
  ## WebSocket Commands
571
571
 
572
572
  The `app.ws` object exposes four methods for interacting with connected clients:
573
573
 
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)`.
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)`.
574
+ - `query(fn, filter?)`: return a filtered list of sessions. The filter function receives `(session, index)` where `session` is a `SessionEntry` (`{ clientId, type, data }`), and returns a boolean. The optional `filter` parameter controls which sessions are iterated: `'active'` (default), `'inactive'`, or `'all'`. To list all active sessions: `app.ws.query(() => true)`. To include inactive sessions: `app.ws.query(() => true, 'all')`.
575
+ - `send(event, body, fn)`: push a notification to clients matching a filter. The filter function receives `(session, index)` and returns a boolean. To target one client: `app.ws.send('ping', body, s => s.clientId === targetId)`.
576
576
  - `broadcast(event, body)`: push a notification to all connected clients.
577
- - `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 `(session, index)` and returns a boolean. To drop one client: `app.ws.drop({ code: 4000, reason: 'kicked' }, s => s.clientId === targetId)`.
578
578
 
579
579
  The same commands are available inside endpoint handlers via `req.ws`:
580
580
 
@@ -582,7 +582,7 @@ The same commands are available inside endpoint handlers via `req.ws`:
582
582
  export default function (req) {
583
583
  const targetId = req.query.targetId
584
584
 
585
- req.ws.send('ping', { from: 'handler' }, id => id === targetId)
585
+ req.ws.send('ping', { from: 'handler' }, s => s.clientId === targetId)
586
586
 
587
587
  return Response.json({ ok: true })
588
588
  }
@@ -594,12 +594,14 @@ This works from both HTTP and WebSocket transports.
594
594
 
595
595
  `sleepy-serv` exports several runtime constants and types:
596
596
 
597
- - `CloseCode`: WebSocket close codes: `Ok` (1000), `Abnormal` (1006), `Reaped` (4999)
598
- - `CloseReason`: close reason values: `Ok`, `Dropped`, `Reaped`, `Superseded`
597
+ - `ServerCloseSignals`: server 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 }`
599
599
  - `StatusCode`: the full range of HTTP status codes (1xx through 5xx)
600
600
  - `HttpMethod`: HTTP verbs: `Head`, `Get`, `Post`, `Put`, `Patch`, `Delete`
601
- - `SessionEntry`: the shape returned by `query()`: `{ clientId: string, app: unknown }`
602
- - `FilterFn`: the filter callback type: `(clientId: string, data: unknown, index: number) => boolean`
601
+ - `SessionType`: session state: `Active` (`'active'`), `Inactive` (`'inactive'`)
602
+ - `SessionFilter`: session filter: `Active` (`'active'`), `Inactive` (`'inactive'`), `All` (`'all'`)
603
+ - `SessionEntry`: the shape returned by `query()` and received by filter callbacks: `{ clientId: string, type: SessionType, data: unknown }`
604
+ - `FilterFn`: the filter callback type: `(session: SessionEntry, index: number) => boolean`
603
605
  - `SocketCommands`: the type for `app.ws` and `req.ws`
604
606
  - Error classes for every 4xx and 5xx status (e.g. `NotFoundError`, `UnauthorizedError`, `InternalServerError`)
605
607
  - Middleware helpers: `parseJsonBody`, `validateSchemas`, `setValidationFormats`
@@ -1,10 +1,33 @@
1
- import type { RouteConfig, SocketCommands, AppOptions, Server } from './utils';
1
+ import type { HttpMethod, Middleware, MiddlewareChain, Handler, Server } from './utils';
2
+ import type { SocketCommands } from './utils';
3
+ import type { SocketOptions } from './socket';
2
4
  export * from './errors';
3
- export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils';
5
+ export { ServerCloseSignals } from './socket';
6
+ export { StatusCode, HttpMethod, SessionType, SessionFilter, } from './utils';
4
7
  export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
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';
8
+ export type { ActiveSession, ActiveSessions, InactiveSession, SocketConnection, SocketOptions, } from './socket';
9
+ export type { AsyncHandlerResult, BaseRequest, CloseSignal, EndpointRequest, FilterFn, FormattedError, Handler, HandlerResult, Middleware, MiddlewareChain, NextFn, Request, Server, SessionEntry, SocketCommands, SocketData, WebSocketRequest, } from './utils';
7
10
  export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
11
+ export type RouteDefinition = {
12
+ method: HttpMethod;
13
+ path: string;
14
+ chain: Handler | MiddlewareChain;
15
+ };
16
+ export type MetaEntry = {
17
+ path: string;
18
+ middleware: Middleware[];
19
+ };
20
+ export type RouteConfig = {
21
+ routes: RouteDefinition[];
22
+ meta?: MetaEntry[];
23
+ };
24
+ export type AppOptions = {
25
+ hostname?: string;
26
+ mountPath?: string;
27
+ middleware?: Middleware[];
28
+ ws?: boolean | SocketOptions;
29
+ onClose?: () => Promise<void> | void;
30
+ };
8
31
  type OutputRoutes = Record<string, string[]>;
9
32
  type CloseFn = (force?: boolean) => Promise<void>;
10
33
  export type App = {
@@ -1 +1 @@
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;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;AAsRD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,UAAe,GACpB,GAAG,CAqBL"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAEV,UAAU,EACV,UAAU,EACV,eAAe,EAEf,OAAO,EACP,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EACV,cAAc,EACf,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EACV,aAAa,EAGd,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7C,OAAO,EACL,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,SAAS,CAAA;AAEhB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,UAAU,CAAA;AAEjB,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,eAAe,EACf,QAAQ,EACR,cAAc,EACd,OAAO,EACP,aAAa,EACb,UAAU,EACV,eAAe,EACf,MAAM,EACN,OAAO,EACP,MAAM,EACN,YAAY,EACZ,cAAc,EACd,UAAU,EACV,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,GAAG,eAAe,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,UAAU,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE,SAAS,EAAE,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,UAAU,EAAE,CAAA;IACzB,EAAE,CAAC,EAAE,OAAO,GAAG,aAAa,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACrC,CAAA;AAED,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,12 +1,38 @@
1
- import { CloseReason } from './utils';
2
1
  import type { WebSocketHandler } from 'bun';
3
- import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, SocketOptions } from './utils';
2
+ import type { AsyncHandlerResult, CloseSignal, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData } from './utils';
4
3
  type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
5
4
  type SocketEndpoint = {
6
5
  method: HttpMethod;
7
6
  path: string;
8
7
  handler: SocketHandler;
9
8
  };
9
+ export type SocketConnection = {
10
+ data: SocketData;
11
+ send: (data: string) => unknown;
12
+ close: (code?: number, reason?: string) => void;
13
+ };
14
+ export type ActiveSession = {
15
+ token: string;
16
+ ws: SocketConnection;
17
+ };
18
+ export type InactiveSession = {
19
+ token: string;
20
+ expiresAt: number;
21
+ data: unknown;
22
+ };
23
+ export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
24
+ export declare const ServerCloseSignals: Record<string, CloseSignal>;
25
+ export type ServerCloseSignals = typeof ServerCloseSignals[keyof typeof ServerCloseSignals];
26
+ export type Session = ActiveSession | InactiveSession;
27
+ export type SocketOptions = {
28
+ dropThreshold?: number;
29
+ heartbeatInterval?: number;
30
+ maxTickets?: number;
31
+ reclaimTtl?: number;
32
+ ticketTtl?: number;
33
+ onOpen?: (clientId: string) => void;
34
+ onClose?: (clientId: string, signal: CloseSignal) => void;
35
+ };
10
36
  export type Ticket = {
11
37
  clientId: string;
12
38
  expiresAt: number;
@@ -22,7 +48,7 @@ export type SocketState = {
22
48
  activeSessions: Map<string, ActiveSession>;
23
49
  inactiveSessions: Map<string, InactiveSession>;
24
50
  onOpen: ((clientId: string) => void) | null;
25
- onClose: ((clientId: string, reason: CloseReason) => void) | null;
51
+ onClose: ((clientId: string, signal: CloseSignal) => void) | null;
26
52
  };
27
53
  export type SocketRoute = {
28
54
  method: HttpMethod;
@@ -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;AAahB,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,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;AAyRD,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,CAoJ9B;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,CAgEvE"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/core/socket.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { ErrorObject } from 'ajv';
2
2
  import type { BunRequest, Server as BunServer } from 'bun';
3
+ export type TimeoutHandle = ReturnType<typeof setTimeout>;
3
4
  export declare const HttpMethod: {
4
5
  readonly Head: "HEAD";
5
6
  readonly Get: "GET";
@@ -85,27 +86,39 @@ export type NextFn = (data?: unknown) => HandlerResult;
85
86
  export type Middleware = (req: Request, res: unknown, next: NextFn) => HandlerResult;
86
87
  export type Handler = (req: Request, res: unknown) => HandlerResult;
87
88
  export type MiddlewareChain = (Middleware | Handler)[];
88
- export type ActiveSession = {
89
- token: string;
90
- ws: SocketConnection;
89
+ export type CloseSignal = {
90
+ code: number;
91
+ reason: string;
91
92
  };
92
- export type InactiveSession = {
93
- token: string;
94
- expiresAt: number;
95
- app: unknown;
93
+ export type SocketData = {
94
+ clientId: string;
95
+ superseded: boolean;
96
+ reaped: boolean;
97
+ reaperHandle: TimeoutHandle | null;
98
+ data: unknown;
99
+ };
100
+ export declare const SessionType: {
101
+ readonly Active: "active";
102
+ readonly Inactive: "inactive";
103
+ };
104
+ export type SessionType = typeof SessionType[keyof typeof SessionType];
105
+ export declare const SessionFilter: {
106
+ readonly Active: "active";
107
+ readonly Inactive: "inactive";
108
+ readonly All: "all";
96
109
  };
97
- export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
98
- export type Session = ActiveSession | InactiveSession;
110
+ export type SessionFilter = typeof SessionFilter[keyof typeof SessionFilter];
99
111
  export type SessionEntry = {
100
112
  clientId: string;
101
- app: unknown;
113
+ type: SessionType;
114
+ data: unknown;
102
115
  };
103
- export type FilterFn = (clientId: string, data: unknown, index: number) => boolean;
116
+ export type FilterFn = (session: SessionEntry, index: number) => boolean;
104
117
  export type SocketCommands = {
105
118
  broadcast: (event: string, body: unknown) => void;
106
119
  send: (event: string, body: unknown, fn: FilterFn) => void;
107
- drop: (fn: FilterFn, code?: number, reason?: string) => void;
108
- query: (fn: FilterFn) => SessionEntry[];
120
+ drop: (signal: CloseSignal, fn: FilterFn) => void;
121
+ query: (fn: FilterFn, filter?: SessionFilter) => SessionEntry[];
109
122
  };
110
123
  export type BaseRequest = {
111
124
  method: HttpMethod;
@@ -125,61 +138,7 @@ export type WebSocketRequest = BaseRequest & {
125
138
  clientId: string;
126
139
  };
127
140
  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];
141
- export type SocketOptions = {
142
- dropThreshold?: number;
143
- heartbeatInterval?: number;
144
- maxTickets?: number;
145
- reclaimTtl?: number;
146
- ticketTtl?: number;
147
- onOpen?: (clientId: string) => void;
148
- onClose?: (clientId: string, reason: CloseReason) => void;
149
- };
150
- export type SocketData = {
151
- clientId: string;
152
- superseded: boolean;
153
- reaped: boolean;
154
- reaperHandle: ReturnType<typeof setTimeout> | null;
155
- app: unknown;
156
- };
157
- export type SocketConnection = {
158
- data: SocketData;
159
- send: (data: string) => unknown;
160
- close: (code?: number, reason?: string) => void;
161
- };
162
141
  export type Server = BunServer<SocketData>;
163
- export type RouteDefinition = {
164
- method: HttpMethod;
165
- path: string;
166
- chain: Handler | MiddlewareChain;
167
- };
168
- export type MetaEntry = {
169
- path: string;
170
- middleware: Middleware[];
171
- };
172
- export type RouteConfig = {
173
- routes: RouteDefinition[];
174
- meta?: MetaEntry[];
175
- };
176
- export type AppOptions = {
177
- hostname?: string;
178
- mountPath?: string;
179
- middleware?: Middleware[];
180
- ws?: boolean | SocketOptions;
181
- onClose?: () => Promise<void> | void;
182
- };
183
142
  export declare function toSegments(pathString: string): string[];
184
143
  export declare function formatError(prefix: string, input: ValidationError): FormattedError;
185
144
  export declare function executeMiddlewareChain(req: Request, chain: MiddlewareChain): AsyncHandlerResult;
@@ -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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './core';
2
- import type { App, AppOptions } from './core';
2
+ import type { AppOptions, App } from './core';
3
3
  export declare function createApp(port: number, opts?: AppOptions): App;
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AAEtB,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAE7C,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,UAAU,GAChB,GAAG,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAE7C,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,UAAU,GAChB,GAAG,CAAA"}
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.22.0",
6
+ "version": "0.24.0",
7
7
  "type": "module",
8
8
  "bin": {
9
9
  "sleepy": "./src/cli/index.ts"
package/src/core/index.ts CHANGED
@@ -31,22 +31,29 @@ import type {
31
31
  Middleware,
32
32
  MiddlewareChain,
33
33
  EndpointRequest,
34
- MetaEntry,
35
- RouteConfig,
36
- RouteDefinition,
37
- SocketCommands,
38
- SocketOptions,
39
- AppOptions,
34
+ Handler,
40
35
  Server,
41
36
  } from './utils'
42
37
 
43
38
  import type {
39
+ SocketCommands,
40
+ } from './utils'
41
+
42
+ import type {
43
+ SocketOptions,
44
44
  SocketRoute,
45
45
  SocketState,
46
46
  } from './socket'
47
47
 
48
48
  export * from './errors'
49
- export { StatusCode, CloseCode, CloseReason, HttpMethod } from './utils'
49
+ export { ServerCloseSignals } from './socket'
50
+
51
+ export {
52
+ StatusCode,
53
+ HttpMethod,
54
+ SessionType,
55
+ SessionFilter,
56
+ } from './utils'
50
57
 
51
58
  export {
52
59
  parseJsonBody,
@@ -54,26 +61,31 @@ export {
54
61
  validateSchemas,
55
62
  } from './middleware'
56
63
 
57
- export type { FilterFn, SessionEntry, SocketCommands } from './utils'
64
+ export type {
65
+ ActiveSession,
66
+ ActiveSessions,
67
+ InactiveSession,
68
+ SocketConnection,
69
+ SocketOptions,
70
+ } from './socket'
58
71
 
59
72
  export type {
60
- AppOptions,
61
73
  AsyncHandlerResult,
62
74
  BaseRequest,
75
+ CloseSignal,
63
76
  EndpointRequest,
77
+ FilterFn,
64
78
  FormattedError,
65
79
  Handler,
66
- MetaEntry,
80
+ HandlerResult,
67
81
  Middleware,
68
82
  MiddlewareChain,
69
83
  NextFn,
70
- HandlerResult,
71
84
  Request,
72
- RouteConfig,
73
- RouteDefinition,
74
85
  Server,
75
- SocketConnection,
76
- SocketOptions,
86
+ SessionEntry,
87
+ SocketCommands,
88
+ SocketData,
77
89
  WebSocketRequest,
78
90
  } from './utils'
79
91
 
@@ -83,6 +95,30 @@ export type {
83
95
  ValidationSchemas,
84
96
  } from './middleware'
85
97
 
98
+ export type RouteDefinition = {
99
+ method: HttpMethod
100
+ path: string
101
+ chain: Handler | MiddlewareChain
102
+ }
103
+
104
+ export type MetaEntry = {
105
+ path: string
106
+ middleware: Middleware[]
107
+ }
108
+
109
+ export type RouteConfig = {
110
+ routes: RouteDefinition[]
111
+ meta?: MetaEntry[]
112
+ }
113
+
114
+ export type AppOptions = {
115
+ hostname?: string
116
+ mountPath?: string
117
+ middleware?: Middleware[]
118
+ ws?: boolean | SocketOptions
119
+ onClose?: () => Promise<void> | void
120
+ }
121
+
86
122
  type OutputRoutes = Record<string, string[]>
87
123
  type ServerRoutes = Record<string, Record<string, EndpointHandler>>
88
124
 
@@ -10,8 +10,8 @@ import {
10
10
 
11
11
  import {
12
12
  StatusCode,
13
- CloseCode,
14
- CloseReason,
13
+ SessionType,
14
+ SessionFilter,
15
15
  toSegments,
16
16
  formatError,
17
17
  executeMiddlewareChain,
@@ -32,18 +32,15 @@ import type { WebSocketHandler } from 'bun'
32
32
 
33
33
  import type {
34
34
  AsyncHandlerResult,
35
+ CloseSignal,
35
36
  HttpMethod,
36
37
  Request,
38
+ FilterFn,
37
39
  MiddlewareChain,
38
40
  SocketCommands,
41
+ SocketData,
39
42
  SessionEntry,
40
43
  WebSocketRequest,
41
- SocketData,
42
- SocketConnection,
43
- ActiveSession,
44
- InactiveSession,
45
- Session,
46
- SocketOptions,
47
44
  } from './utils'
48
45
 
49
46
  import type {
@@ -90,6 +87,55 @@ type UpdateTicketRequest = {
90
87
  }
91
88
  }
92
89
 
90
+ export type SocketConnection = {
91
+ data: SocketData
92
+ send: (data: string) => unknown
93
+ close: (code?: number, reason?: string) => void
94
+ }
95
+
96
+ export type ActiveSession = {
97
+ token: string
98
+ ws: SocketConnection
99
+ }
100
+
101
+ export type InactiveSession = {
102
+ token: string
103
+ expiresAt: number
104
+ data: unknown
105
+ }
106
+
107
+ export type ActiveSessions = ReadonlyMap<string, ActiveSession>
108
+
109
+ export const ServerCloseSignals: Record<string, CloseSignal> = {
110
+ Ok: {
111
+ code: 1000,
112
+ reason: 'ok',
113
+ },
114
+ Reaped: {
115
+ code: 4998,
116
+ reason: 'reaped',
117
+ },
118
+ Superseded: {
119
+ code: 4999,
120
+ reason: 'superseded',
121
+ },
122
+ } as const
123
+
124
+ export type ServerCloseSignals =
125
+ typeof ServerCloseSignals[keyof typeof ServerCloseSignals]
126
+
127
+ export type Session = ActiveSession | InactiveSession
128
+
129
+ export type SocketOptions = {
130
+ dropThreshold?: number
131
+ heartbeatInterval?: number
132
+ maxTickets?: number
133
+ reclaimTtl?: number
134
+ ticketTtl?: number
135
+ onOpen?: (clientId: string) => void
136
+ onClose?: (clientId: string, signal: CloseSignal) => void
137
+ }
138
+
93
139
  export type Ticket = {
94
140
  clientId: string
95
141
  expiresAt: number
@@ -106,7 +152,7 @@ export type SocketState = {
106
152
  activeSessions: Map<string, ActiveSession>
107
153
  inactiveSessions: Map<string, InactiveSession>
108
154
  onOpen: ((clientId: string) => void) | null
109
- onClose: ((clientId: string, reason: CloseReason) => void) | null
155
+ onClose: ((clientId: string, signal: CloseSignal) => void) | null
110
156
  }
111
157
 
112
158
  export type SocketRoute = {
@@ -385,16 +431,6 @@ function buildErrorMessage (
385
431
  })
386
432
  }
387
433
 
388
- function getCloseReason (ws: SocketConnection, code: number): CloseReason {
389
- if (ws.data.reaped) {
390
- return CloseReason.Reaped
391
- } else if (code === CloseCode.Ok) {
392
- return CloseReason.Ok
393
- }
394
-
395
- return CloseReason.Dropped
396
- }
397
-
398
434
  export function buildSocketState (opts: SocketOptions = {}): SocketState {
399
435
  return {
400
436
  dropThreshold: opts.dropThreshold ?? 120_000,
@@ -433,7 +469,10 @@ export function buildSocketServer (
433
469
  ws.data.reaperHandle = setTimeout(() => {
434
470
  ws.data.reaped = true
435
471
 
436
- ws.close(CloseCode.Reaped)
472
+ ws.close(
473
+ ServerCloseSignals.Reaped.code,
474
+ ServerCloseSignals.Reaped.reason,
475
+ )
437
476
  }, dropThreshold)
438
477
  }
439
478
 
@@ -447,10 +486,10 @@ export function buildSocketServer (
447
486
  }
448
487
  }
449
488
 
450
- function invokeClose (ws: SocketConnection, reason: CloseReason) {
489
+ function invokeClose (ws: SocketConnection, signal: CloseSignal) {
451
490
  if (onClose) {
452
491
  try {
453
- onClose(ws.data.clientId, reason)
492
+ onClose(ws.data.clientId, signal)
454
493
  } catch (err) {
455
494
  console.error(err)
456
495
  }
@@ -467,7 +506,10 @@ export function buildSocketServer (
467
506
  if (existingSession) {
468
507
  existingSession.ws.data.superseded = true
469
508
 
470
- existingSession.ws.close()
509
+ existingSession.ws.close(
510
+ ServerCloseSignals.Superseded.code,
511
+ ServerCloseSignals.Superseded.reason,
512
+ )
471
513
  }
472
514
 
473
515
  inactiveSessions.delete(ws.data.clientId)
@@ -494,18 +536,22 @@ export function buildSocketServer (
494
536
  ws.send(JSON.stringify(welcomeMessage))
495
537
  invokeOpen(ws)
496
538
  },
497
- close (ws: SocketConnection, code: number): void {
539
+ close (ws: SocketConnection, code: number, reason: string): void {
540
+ const signal: CloseSignal = {
541
+ code,
542
+ reason,
543
+ }
544
+
498
545
  if (ws.data.reaperHandle) {
499
546
  clearTimeout(ws.data.reaperHandle)
500
547
  }
501
548
 
502
549
  if (ws.data.superseded) {
503
- invokeClose(ws, CloseReason.Superseded)
550
+ invokeClose(ws, signal)
504
551
 
505
552
  return
506
553
  }
507
554
 
508
- const reason = getCloseReason(ws, code)
509
555
  const exists = activeSessions.get(ws.data.clientId)
510
556
 
511
557
  if (!exists || exists.ws !== ws) {
@@ -514,15 +560,15 @@ export function buildSocketServer (
514
560
 
515
561
  activeSessions.delete(ws.data.clientId)
516
562
 
517
- if (code !== CloseCode.Ok || ws.data.reaped) {
563
+ if (code !== ServerCloseSignals.Ok.code || ws.data.reaped) {
518
564
  inactiveSessions.set(ws.data.clientId, {
519
565
  token: exists.token,
520
566
  expiresAt: Date.now() + reclaimTtl,
521
- app: ws.data.app,
567
+ data: ws.data.data,
522
568
  })
523
569
  }
524
570
 
525
- invokeClose(ws, reason)
571
+ invokeClose(ws, signal)
526
572
  },
527
573
  async message (ws: SocketConnection, raw: string | Buffer): Promise<void> {
528
574
  const incomingMsg = parseMessage(raw)
@@ -634,7 +680,7 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
634
680
  ctx.data.superseded = false
635
681
  ctx.data.reaped = false
636
682
  ctx.data.reaperHandle = null
637
- ctx.data.app = ticket.data
683
+ ctx.data.data = ticket.data
638
684
 
639
685
  const useSocket = validReq.server.upgrade(validReq.raw, ctx)
640
686
 
@@ -690,7 +736,7 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
690
736
  throw new UnauthorizedError('Invalid token')
691
737
  }
692
738
 
693
- const appData = 'ws' in session ? session.ws.data.app : session.app
739
+ const appData = 'ws' in session ? session.ws.data.data : session.data
694
740
 
695
741
  return Response.json({
696
742
  clientId: validReq.params.clientId,
@@ -731,49 +777,101 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
731
777
  }
732
778
 
733
779
  return {
734
- broadcast (event, body) {
780
+ broadcast (event: string, body: unknown) {
735
781
  for (const clientId of state.activeSessions.keys()) {
736
782
  sendToClient(clientId, event, body)
737
783
  }
738
784
  },
739
- send (event, body, fn) {
785
+ send (event: string, body: unknown, fn: FilterFn) {
740
786
  let index = 0
741
787
 
742
788
  /* TODO: look into concurrency at some point */
743
789
  for (const [clientId, session] of state.activeSessions) {
744
- if (fn(clientId, session.ws.data, index)) {
790
+ const entry: SessionEntry = {
791
+ clientId,
792
+ type: SessionType.Active,
793
+ data: session.ws.data.data,
794
+ }
795
+
796
+ if (fn(entry, index)) {
745
797
  sendToClient(clientId, event, body)
746
798
  }
747
799
 
748
800
  index += 1
749
801
  }
750
802
  },
751
- drop (fn, code, reason) {
803
+ drop (signal: CloseSignal, fn: FilterFn) {
804
+ const { code, reason } = signal
805
+
752
806
  let index = 0
753
807
 
808
+ if (!Number.isInteger(code) || code < 4000 || code > 4099) {
809
+ throw new RangeError(
810
+ `Signal code must be an integer in [4000, 4099], got ${code}`,
811
+ )
812
+ }
813
+
754
814
  for (const [clientId, session] of state.activeSessions) {
755
- if (fn(clientId, session.ws.data, index)) {
815
+ const entry: SessionEntry = {
816
+ clientId,
817
+ type: SessionType.Active,
818
+ data: session.ws.data.data,
819
+ }
820
+
821
+ if (fn(entry, index)) {
756
822
  session.ws.close(code, reason)
757
823
  }
758
824
 
759
825
  index += 1
760
826
  }
761
827
  },
762
- query (fn) {
828
+ query (
829
+ fn: FilterFn,
830
+ filter: SessionFilter = SessionFilter.Active,
831
+ ) {
763
832
  const results: SessionEntry[] = []
764
- let index = 0
765
833
 
766
- for (const [clientId, session] of state.activeSessions) {
767
- if (fn(clientId, session.ws.data, index)) {
768
- results.push({
834
+ function processSessions (
835
+ type: SessionType,
836
+ filter: SessionFilter,
837
+ startIndex: number,
838
+ ): number {
839
+ let index = startIndex
840
+
841
+ const sessions = type === SessionType.Active
842
+ ? state.activeSessions
843
+ : state.inactiveSessions
844
+
845
+ if (type !== filter && filter !== SessionFilter.All) {
846
+ return index
847
+ }
848
+
849
+ for (const [clientId, session] of sessions) {
850
+ const data = 'ws' in session ? session.ws.data.data : session.data
851
+
852
+ const entry: SessionEntry = {
769
853
  clientId,
770
- app: session.ws.data.app,
771
- })
854
+ type,
855
+ data,
856
+ }
857
+
858
+ if (fn(entry, index)) {
859
+ results.push(entry)
860
+ }
861
+
862
+ index += 1
772
863
  }
773
864
 
774
- index += 1
865
+ return index
775
866
  }
776
867
 
868
+ let index = 0
869
+
870
+ sweepInactiveSessions(state)
871
+
872
+ index = processSessions(SessionType.Active, filter, index)
873
+ index = processSessions(SessionType.Inactive, filter, index)
874
+
777
875
  return results
778
876
  },
779
877
  }
package/src/core/utils.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ErrorObject } from 'ajv'
2
2
  import type { BunRequest, Server as BunServer } from 'bun'
3
3
 
4
+ export type TimeoutHandle = ReturnType<typeof setTimeout>
5
+
4
6
  export const HttpMethod = {
5
7
  Head: 'HEAD',
6
8
  Get: 'GET',
@@ -107,36 +109,50 @@ export type Handler = (
107
109
 
108
110
  export type MiddlewareChain = (Middleware | Handler)[]
109
111
 
110
- export type ActiveSession = {
111
- token: string
112
- ws: SocketConnection
112
+ export type CloseSignal = {
113
+ code: number
114
+ reason: string
113
115
  }
114
116
 
115
- export type InactiveSession = {
116
- token: string
117
- expiresAt: number
118
- app: unknown
117
+ export type SocketData = {
118
+ clientId: string
119
+ superseded: boolean
120
+ reaped: boolean
121
+ reaperHandle: TimeoutHandle | null
122
+ data: unknown
119
123
  }
120
124
 
121
- export type ActiveSessions = ReadonlyMap<string, ActiveSession>
122
- export type Session = ActiveSession | InactiveSession
125
+ export const SessionType = {
126
+ Active: 'active',
127
+ Inactive: 'inactive',
128
+ } as const
129
+
130
+ export type SessionType = typeof SessionType[keyof typeof SessionType]
131
+
132
+ export const SessionFilter = {
133
+ Active: 'active',
134
+ Inactive: 'inactive',
135
+ All: 'all',
136
+ } as const
137
+
138
+ export type SessionFilter = typeof SessionFilter[keyof typeof SessionFilter]
123
139
 
124
140
  export type SessionEntry = {
125
141
  clientId: string
126
- app: unknown
142
+ type: SessionType
143
+ data: unknown
127
144
  }
128
145
 
129
146
  export type FilterFn = (
130
- clientId: string,
131
- data: unknown,
147
+ session: SessionEntry,
132
148
  index: number,
133
149
  ) => boolean
134
150
 
135
151
  export type SocketCommands = {
136
152
  broadcast: (event: string, body: unknown) => void
137
153
  send: (event: string, body: unknown, fn: FilterFn) => void
138
- drop: (fn: FilterFn, code?: number, reason?: string) => void
139
- query: (fn: FilterFn) => SessionEntry[]
154
+ drop: (signal: CloseSignal, fn: FilterFn) => void
155
+ query: (fn: FilterFn, filter?: SessionFilter) => SessionEntry[]
140
156
  }
141
157
 
142
158
  export type BaseRequest = {
@@ -161,73 +177,8 @@ export type WebSocketRequest = BaseRequest & {
161
177
 
162
178
  export type Request = EndpointRequest | WebSocketRequest
163
179
 
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]
171
-
172
- export const CloseReason = {
173
- Ok: 'ok',
174
- Dropped: 'dropped',
175
- Reaped: 'reaped',
176
- Superseded: 'superseded',
177
- } as const
178
-
179
- export type CloseReason = typeof CloseReason[keyof typeof CloseReason]
180
-
181
- export type SocketOptions = {
182
- dropThreshold?: number
183
- heartbeatInterval?: number
184
- maxTickets?: number
185
- reclaimTtl?: number
186
- ticketTtl?: number
187
- onOpen?: (clientId: string) => void
188
- onClose?: (clientId: string, reason: CloseReason) => void
189
- }
190
-
191
- export type SocketData = {
192
- clientId: string
193
- superseded: boolean
194
- reaped: boolean
195
- reaperHandle: ReturnType<typeof setTimeout> | null
196
- app: unknown
197
- }
198
-
199
- export type SocketConnection = {
200
- data: SocketData
201
- send: (data: string) => unknown
202
- close: (code?: number, reason?: string) => void
203
- }
204
-
205
180
  export type Server = BunServer<SocketData>
206
181
 
207
- export type RouteDefinition = {
208
- method: HttpMethod
209
- path: string
210
- chain: Handler | MiddlewareChain
211
- }
212
-
213
- export type MetaEntry = {
214
- path: string
215
- middleware: Middleware[]
216
- }
217
-
218
- export type RouteConfig = {
219
- routes: RouteDefinition[]
220
- meta?: MetaEntry[]
221
- }
222
-
223
- export type AppOptions = {
224
- hostname?: string
225
- mountPath?: string
226
- middleware?: Middleware[]
227
- ws?: boolean | SocketOptions
228
- onClose?: () => Promise<void> | void
229
- }
230
-
231
182
  export function toSegments (pathString: string): string[] {
232
183
  const [pathname] = String(pathString).split('?')
233
184
  const segments = pathname.split('/')
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './core'
2
2
 
3
- import type { App, AppOptions } from './core'
3
+ import type { AppOptions, App } from './core'
4
4
 
5
5
  export declare function createApp (
6
6
  port: number,