sleepy-serv 0.8.0 → 0.10.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
@@ -5,7 +5,7 @@ A directory-driven web server designed for REST-ful applications
5
5
  ## Important Notes
6
6
 
7
7
  - This package requires [`bun.sh`](https://bun.sh) instead of NodeJS to run.
8
- - This project requires bun v1.2.3 or higher
8
+ - This project requires bun v1.4 or higher
9
9
 
10
10
  ## Getting Started
11
11
 
package/dist/errors.d.ts CHANGED
@@ -127,8 +127,7 @@ export declare class UnavailableForLegalReasonsError extends RequestError {
127
127
  }
128
128
  export declare class InternalServerError extends RequestError {
129
129
  static get status(): StatusCode;
130
- ctx: unknown;
131
- constructor(message: string, ctx?: unknown);
130
+ constructor();
132
131
  }
133
132
  export declare class NotImplementedError extends RequestError {
134
133
  static get status(): StatusCode;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { AppOptions, Server } from './utils';
2
2
  import type { SocketCommands } from './socket';
3
3
  export * from './errors';
4
+ export { StatusCode, CloseCode, CloseReason } from './utils';
4
5
  export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
5
- export { HttpMethod, StatusCode } from './utils';
6
- export type { AppOptions, EndpointRequest, FormattedError, Middleware, NextFn, Request, Server, SocketOptions, WebSocketRequest, } from './utils';
7
- export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
8
6
  export type { SocketCommands } from './socket';
7
+ export type { HttpMethod, ActiveSessions } from './utils';
8
+ export type { AppOptions, AsyncHandlerResult, BaseRequest, EndpointRequest, FormattedError, Handler, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
9
+ export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
9
10
  type OutputRoutes = Record<string, string[]>;
10
11
  type CloseFn = (force?: boolean) => Promise<void>;
11
12
  export type App = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAIV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhD,YAAY,EACV,UAAU,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACP,MAAM,EACN,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,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;AA4ZD,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,EAOV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE5D,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEzD,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA4C5C,KAAK,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAyaD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAoB,MAAM,KAAK,CAAA;AAC3D,OAAO,KAAK,EAAkB,UAAU,EAAmB,MAAM,SAAS,CAAA;AAE1E,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAA;AA2F/C,wBAAgB,aAAa,IAAK,UAAU,CAoB3C;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,IAAI,CAUN;AAID,wBAAgB,2BAA2B,IAAK,IAAI,CAGnD;AAcD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,iBAAiB,GACzB,UAAU,CAgCZ"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAoB,MAAM,KAAK,CAAA;AAE3D,OAAO,KAAK,EAIV,UAAU,EAGX,MAAM,SAAS,CAAA;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAA;AAmF/C,wBAAgB,aAAa,IAAK,UAAU,CAoB3C;AAED,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAU3E;AAID,wBAAgB,2BAA2B,IAAK,IAAI,CAGnD;AAcD,wBAAgB,eAAe,CAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAgCvE"}
package/dist/socket.d.ts CHANGED
@@ -1,25 +1,20 @@
1
+ import { CloseReason } from './utils';
1
2
  import type { WebSocketHandler } from 'bun';
2
- import type { HttpMethod, Request, Middleware, SocketData, AppOptions } from './utils';
3
+ import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketData, ActiveSession, InactiveSession, AppOptions } from './utils';
4
+ type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
5
+ type FilterFn = (clientId: string, data: unknown) => boolean;
6
+ type SocketEndpoint = {
7
+ method: HttpMethod;
8
+ path: string;
9
+ handler: SocketHandler;
10
+ };
3
11
  export type Ticket = {
4
12
  clientId: string;
5
13
  expiresAt: number;
14
+ data: unknown;
6
15
  };
7
- export type SocketConnection = {
8
- data: SocketData;
9
- send: (data: string) => unknown;
10
- close: () => void;
11
- };
12
- export type ActiveSession = {
13
- token: string;
14
- ws: SocketConnection;
15
- };
16
- export type InactiveSession = {
17
- token: string;
18
- expiresAt: number;
19
- };
20
- export type Session = ActiveSession | InactiveSession;
21
16
  export type SocketState = {
22
- disconnectThreshold: number;
17
+ dropThreshold: number;
23
18
  heartbeatInterval: number;
24
19
  maxTickets: number;
25
20
  reclaimTtl: number;
@@ -27,24 +22,24 @@ export type SocketState = {
27
22
  tickets: Map<string, Ticket>;
28
23
  activeSessions: Map<string, ActiveSession>;
29
24
  inactiveSessions: Map<string, InactiveSession>;
25
+ onOpen: ((clientId: string) => void) | null;
26
+ onClose: ((clientId: string, reason: CloseReason) => void) | null;
30
27
  };
31
28
  export type SocketRoute = {
32
29
  method: HttpMethod;
33
30
  path: string;
34
31
  segments: string[];
35
- chain: Middleware[];
36
- };
37
- export type SocketEndpoint = {
38
- method: HttpMethod;
39
- path: string;
40
- handler: (req: Request, res: unknown) => Response;
32
+ chain: MiddlewareChain;
41
33
  };
42
34
  export type SocketCommands = {
43
35
  send: (clientId: string, event: string, body: unknown) => void;
36
+ sendToGroup: (fn: FilterFn, event: string, body: unknown) => void;
44
37
  broadcast: (event: string, body: unknown) => void;
38
+ drop: (clientId: string, code?: number, reason?: string) => void;
45
39
  };
46
40
  export declare function buildSocketState(opts?: AppOptions): SocketState;
47
41
  export declare function buildSocketServer(routes: SocketRoute[], state: SocketState): WebSocketHandler<SocketData>;
48
42
  export declare function buildSocketHandlers(state: SocketState): SocketEndpoint[];
49
43
  export declare function buildSocketCommands(state: SocketState): SocketCommands;
44
+ export {};
50
45
  //# sourceMappingURL=socket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAG3C,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EAEP,UAAU,EACV,UAAU,EACV,UAAU,EACX,MAAM,SAAS,CAAA;AAShB,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAC/B,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,gBAAgB,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,eAAe,CAAA;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,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;CAC/C,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,UAAU,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,QAAQ,CAAA;CAClD,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,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CAClD,CAAA;AAmTD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,UAAe,GAAG,WAAW,CAWpE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,GACjB,gBAAgB,CAAC,UAAU,CAAC,CAuH9B;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,EAAE,CAqIzE;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,CA+BvE"}
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"}
package/dist/utils.d.ts CHANGED
@@ -79,7 +79,23 @@ export type FormattedError = {
79
79
  path: string;
80
80
  message: string;
81
81
  };
82
- export type NextFn = (data?: unknown) => Response | Promise<Response>;
82
+ export type AsyncHandlerResult = Promise<Response>;
83
+ export type HandlerResult = Response | AsyncHandlerResult;
84
+ export type NextFn = (data?: unknown) => HandlerResult;
85
+ export type Middleware = (req: Request, res: unknown, next: NextFn) => HandlerResult;
86
+ export type Handler = (req: Request, res: unknown) => HandlerResult;
87
+ export type MiddlewareChain = (Middleware | Handler)[];
88
+ export type ActiveSession = {
89
+ token: string;
90
+ ws: SocketConnection;
91
+ };
92
+ export type InactiveSession = {
93
+ token: string;
94
+ expiresAt: number;
95
+ app: unknown;
96
+ };
97
+ export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
98
+ export type Session = ActiveSession | InactiveSession;
83
99
  export type BaseRequest = {
84
100
  method: HttpMethod;
85
101
  route: string;
@@ -87,6 +103,9 @@ export type BaseRequest = {
87
103
  params: Record<string, string>;
88
104
  query: Record<string, unknown>;
89
105
  json: () => Promise<unknown>;
106
+ ws: {
107
+ active: ActiveSessions;
108
+ };
90
109
  };
91
110
  export type EndpointRequest = BaseRequest & {
92
111
  raw: BunRequest;
@@ -97,19 +116,39 @@ export type WebSocketRequest = BaseRequest & {
97
116
  clientId: string;
98
117
  };
99
118
  export type Request = EndpointRequest | WebSocketRequest;
100
- export type Middleware = (req: Request, res: unknown, next: NextFn | null) => unknown;
119
+ export declare const CloseCode: {
120
+ readonly Ok: 1000;
121
+ readonly Abnormal: 1006;
122
+ readonly Reaped: 4999;
123
+ };
124
+ export type CloseCode = typeof CloseCode[keyof typeof CloseCode];
125
+ export declare const CloseReason: {
126
+ readonly Ok: "ok";
127
+ readonly Dropped: "dropped";
128
+ readonly Reaped: "reaped";
129
+ readonly Superseded: "superseded";
130
+ };
131
+ export type CloseReason = typeof CloseReason[keyof typeof CloseReason];
101
132
  export type SocketOptions = {
102
- disconnectThreshold?: number;
133
+ dropThreshold?: number;
103
134
  heartbeatInterval?: number;
104
135
  maxTickets?: number;
105
136
  reclaimTtl?: number;
106
137
  ticketTtl?: number;
138
+ onOpen?: (clientId: string) => void;
139
+ onClose?: (clientId: string, reason: CloseReason) => void;
107
140
  };
108
141
  export type SocketData = {
109
142
  clientId: string;
110
143
  superseded: boolean;
111
144
  reaped: boolean;
112
145
  reaperHandle: ReturnType<typeof setTimeout> | null;
146
+ app: unknown;
147
+ };
148
+ export type SocketConnection = {
149
+ data: SocketData;
150
+ send: (data: string) => unknown;
151
+ close: (code?: number, reason?: string) => void;
113
152
  };
114
153
  export type Server = BunServer<SocketData>;
115
154
  export type AppOptions = {
@@ -121,5 +160,5 @@ export type AppOptions = {
121
160
  };
122
161
  export declare function toSegments(pathString: string): string[];
123
162
  export declare function formatError(prefix: string, input: ValidationError): FormattedError;
124
- export declare function executeMiddlewareChain(req: Request, chain: Middleware[]): Promise<Response>;
163
+ export declare function executeMiddlewareChain(req: Request, chain: MiddlewareChain): AsyncHandlerResult;
125
164
  //# sourceMappingURL=utils.d.ts.map
@@ -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.8.0",
6
+ "version": "0.10.0",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": {
package/src/errors.ts CHANGED
@@ -384,13 +384,10 @@ export class InternalServerError extends RequestError {
384
384
  return StatusCode.InternalServerError
385
385
  }
386
386
 
387
- ctx: unknown
388
-
389
- constructor (message: string, ctx?: unknown) {
390
- super(message)
387
+ constructor () {
388
+ super('An internal server error occurred')
391
389
 
392
390
  this.name = 'InternalServerError'
393
- this.ctx = ctx
394
391
  }
395
392
  }
396
393
 
package/src/index.ts CHANGED
@@ -4,7 +4,11 @@ import querystring from 'querystring'
4
4
  import readline from 'node:readline'
5
5
 
6
6
  import { stdin, stdout } from 'node:process'
7
- import { StatusCode, toSegments, executeMiddlewareChain } from './utils'
7
+
8
+ import {
9
+ toSegments,
10
+ executeMiddlewareChain,
11
+ } from './utils'
8
12
 
9
13
  import {
10
14
  buildSocketState,
@@ -17,14 +21,18 @@ import {
17
21
  RequestError,
18
22
  NotFoundError,
19
23
  MethodNotAllowedError,
24
+ InternalServerError,
20
25
  } from './errors'
21
26
 
22
27
  import type { BunRequest } from 'bun'
23
28
 
24
29
  import type {
30
+ AsyncHandlerResult,
25
31
  HttpMethod,
26
32
  Middleware,
33
+ MiddlewareChain,
27
34
  EndpointRequest,
35
+ ActiveSessions,
28
36
  AppOptions,
29
37
  Server,
30
38
  } from './utils'
@@ -36,6 +44,7 @@ import type {
36
44
  } from './socket'
37
45
 
38
46
  export * from './errors'
47
+ export { StatusCode, CloseCode, CloseReason } from './utils'
39
48
 
40
49
  export {
41
50
  parseJsonBody,
@@ -43,16 +52,23 @@ export {
43
52
  validateSchemas,
44
53
  } from './middleware'
45
54
 
46
- export { HttpMethod, StatusCode } from './utils'
55
+ export type { SocketCommands } from './socket'
56
+ export type { HttpMethod, ActiveSessions } from './utils'
47
57
 
48
58
  export type {
49
59
  AppOptions,
60
+ AsyncHandlerResult,
61
+ BaseRequest,
50
62
  EndpointRequest,
51
63
  FormattedError,
64
+ Handler,
52
65
  Middleware,
66
+ MiddlewareChain,
53
67
  NextFn,
68
+ HandlerResult,
54
69
  Request,
55
70
  Server,
71
+ SocketConnection,
56
72
  SocketOptions,
57
73
  WebSocketRequest,
58
74
  } from './utils'
@@ -63,15 +79,13 @@ export type {
63
79
  ValidationSchemas,
64
80
  } from './middleware'
65
81
 
66
- export type { SocketCommands } from './socket'
67
-
68
82
  type OutputRoutes = Record<string, string[]>
69
83
  type ServerRoutes = Record<string, Record<string, EndpointHandler>>
70
84
 
71
85
  type EndpointHandler = (
72
86
  bunReq: BunRequest,
73
87
  server: Server,
74
- ) => Promise<Response>
88
+ ) => AsyncHandlerResult
75
89
 
76
90
  type DirEntry = {
77
91
  path: string
@@ -88,7 +102,7 @@ type RoutePath = {
88
102
  type ChainRoute = {
89
103
  method: HttpMethod
90
104
  path: string
91
- chain: Middleware[]
105
+ chain: MiddlewareChain
92
106
  }
93
107
 
94
108
  type ModuleRoute = {
@@ -153,10 +167,20 @@ function defaultMethodMap (): Record<string, EndpointHandler> {
153
167
  function buildEndpointRequest (
154
168
  bunReq: BunRequest,
155
169
  server: Server,
170
+ activeSessions: ActiveSessions,
156
171
  ): EndpointRequest {
157
172
  const url = new URL(bunReq.url)
158
173
  const qs = url.search.replace('?', '')
159
- const json = () => bunReq.json()
174
+
175
+ let bodyPromise: Promise<unknown> | null = null
176
+
177
+ const json = (): Promise<unknown> => {
178
+ if (!bodyPromise) {
179
+ bodyPromise = bunReq.json()
180
+ }
181
+
182
+ return bodyPromise
183
+ }
160
184
 
161
185
  return {
162
186
  method: bunReq.method as HttpMethod,
@@ -167,6 +191,9 @@ function buildEndpointRequest (
167
191
  raw: bunReq,
168
192
  server,
169
193
  json,
194
+ ws: {
195
+ active: activeSessions,
196
+ },
170
197
  }
171
198
  }
172
199
 
@@ -278,9 +305,10 @@ function buildRoutePaths (
278
305
  .join('') || '/'
279
306
 
280
307
  const metaMiddlewarePath = selectMetaPaths(metadata, modulePath)
308
+ const rawMethod = segments[lastIndex].toUpperCase()
281
309
 
282
310
  return {
283
- method: segments[lastIndex].toUpperCase() as HttpMethod,
311
+ method: rawMethod as HttpMethod,
284
312
  path: joinedPath,
285
313
  metaMiddlewarePath,
286
314
  modulePath,
@@ -375,10 +403,17 @@ function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
375
403
  }))
376
404
  }
377
405
 
378
- function buildModuleRoutes (socketRoutes: SocketRoute[]): ModuleRoute[] {
406
+ function buildModuleRoutes (
407
+ socketRoutes: SocketRoute[],
408
+ state: SocketState,
409
+ ): ModuleRoute[] {
379
410
  return socketRoutes.map(route => {
380
411
  const handler: EndpointHandler = async (bunReq, server) => {
381
- const req = buildEndpointRequest(bunReq, server)
412
+ const req = buildEndpointRequest(
413
+ bunReq,
414
+ server,
415
+ state.activeSessions,
416
+ )
382
417
 
383
418
  return executeMiddlewareChain(req, route.chain)
384
419
  }
@@ -439,7 +474,7 @@ async function buildRoutes (
439
474
  )
440
475
 
441
476
  const socketRoutes = buildSocketRoutes(mergedRoutes)
442
- const moduleRoutes = buildModuleRoutes(socketRoutes)
477
+ const moduleRoutes = buildModuleRoutes(socketRoutes, state)
443
478
  const serverRoutes = buildServerRoutes(moduleRoutes)
444
479
  const outputRoutes = buildOutputRoutes(moduleRoutes)
445
480
 
@@ -470,15 +505,11 @@ function buildServer (
470
505
  error (err) {
471
506
  console.error(err)
472
507
 
473
- if (err instanceof RequestError) {
474
- const ctor = err.constructor as typeof RequestError
475
-
476
- return Response.json(err.output, { status: ctor.status })
477
- }
508
+ const isRequestError = !(err instanceof RequestError)
509
+ const httpError = isRequestError ? new InternalServerError() : err
510
+ const { status } = httpError.constructor as typeof RequestError
478
511
 
479
- return new Response(err.message, {
480
- status: StatusCode.InternalServerError,
481
- })
512
+ return Response.json(httpError.output, { status })
482
513
  },
483
514
  })
484
515
  }
@@ -504,14 +535,11 @@ function processIO (
504
535
  return shutdown
505
536
  }
506
537
 
507
- stdin.setRawMode(true)
508
-
509
538
  const rl = readline.createInterface({
510
539
  input: stdin,
511
540
  output: stdout,
512
541
  })
513
542
 
514
- /* istanbul ignore next */
515
543
  const handleClose = async (): Promise<void> => {
516
544
  await shutdown()
517
545
  process.exit(0)
@@ -522,7 +550,6 @@ function processIO (
522
550
  return async (force = false) => {
523
551
  rl.off('close', handleClose)
524
552
  rl.close()
525
- stdin.setRawMode(false)
526
553
 
527
554
  await shutdown(force)
528
555
  }
package/src/middleware.ts CHANGED
@@ -10,7 +10,15 @@ import {
10
10
  } from './errors'
11
11
 
12
12
  import type { Format, Schema, ValidateFunction } from 'ajv'
13
- import type { FormattedError, Middleware, NextFn, Request } from './utils'
13
+
14
+ import type {
15
+ AsyncHandlerResult,
16
+ FormattedError,
17
+ HandlerResult,
18
+ Middleware,
19
+ NextFn,
20
+ Request,
21
+ } from './utils'
14
22
 
15
23
  export type FormatterField = {
16
24
  type: string
@@ -28,14 +36,6 @@ export type ValidationSchemas = {
28
36
 
29
37
  export type SchemaKey = keyof ValidationSchemas
30
38
 
31
- function requireNext (next: NextFn | null): NextFn {
32
- if (!next) {
33
- throw new TypeError('Middleware cannot be the last entry in a chain')
34
- }
35
-
36
- return next
37
- }
38
-
39
39
  let _schemasCompiled = false
40
40
  let _customFormats: Record<string, Format> | null = null
41
41
 
@@ -121,12 +121,12 @@ export function parseJsonBody (): Middleware {
121
121
  return async (
122
122
  req: Request,
123
123
  res: unknown,
124
- next: NextFn | null,
125
- ): Promise<unknown> => {
124
+ next: NextFn,
125
+ ): AsyncHandlerResult => {
126
126
  const contentType = req.headers.get('content-type')
127
127
 
128
128
  if (!contentType) {
129
- return requireNext(next)(res)
129
+ return next(res)
130
130
  }
131
131
 
132
132
  if (!contentType.startsWith('application/json')) {
@@ -135,13 +135,11 @@ export function parseJsonBody (): Middleware {
135
135
 
136
136
  const body = await parseBody(req)
137
137
 
138
- return requireNext(next)(body)
138
+ return next(body)
139
139
  }
140
140
  }
141
141
 
142
- export function setValidationFormats (
143
- formats: Record<string, Format>,
144
- ): void {
142
+ export function setValidationFormats (formats: Record<string, Format>): void {
145
143
  if (_customFormats) {
146
144
  console.warn('setValidationFormats() - already initialized')
147
145
  }
@@ -172,9 +170,7 @@ function buildValidationSource (
172
170
  }
173
171
  }
174
172
 
175
- export function validateSchemas (
176
- schemas: ValidationSchemas,
177
- ): Middleware {
173
+ export function validateSchemas (schemas: ValidationSchemas): Middleware {
178
174
  const entries = compileSchemas(schemas)
179
175
 
180
176
  _schemasCompiled = true
@@ -182,14 +178,14 @@ export function validateSchemas (
182
178
  return (
183
179
  req: Request,
184
180
  res: unknown,
185
- next: NextFn | null,
186
- ): unknown => {
181
+ next: NextFn,
182
+ ): HandlerResult => {
187
183
  const source = buildValidationSource(req, res)
188
184
 
189
- const errors = entries.reduce((accum: FormattedError[], [
190
- key,
191
- validator,
192
- ]) => {
185
+ const errors = entries.reduce((
186
+ accum: FormattedError[],
187
+ [key, validator]: [SchemaKey, ValidateFunction],
188
+ ) => {
193
189
  const valid = validator(source[key])
194
190
 
195
191
  return !valid
@@ -204,6 +200,6 @@ export function validateSchemas (
204
200
  throw new UnprocessableContentError(errors)
205
201
  }
206
202
 
207
- return requireNext(next)(res)
203
+ return next(res)
208
204
  }
209
205
  }
package/src/socket.ts CHANGED
@@ -2,10 +2,16 @@ import Ajv from 'ajv'
2
2
  import addFormats from 'ajv-formats'
3
3
  import crypto from 'node:crypto'
4
4
 
5
- import { MessageType, createMessage, validateMessage } from './messages'
5
+ import {
6
+ MessageType,
7
+ createMessage,
8
+ validateMessage,
9
+ } from './messages'
6
10
 
7
11
  import {
8
12
  StatusCode,
13
+ CloseCode,
14
+ CloseReason,
9
15
  toSegments,
10
16
  formatError,
11
17
  executeMiddlewareChain,
@@ -13,6 +19,7 @@ import {
13
19
 
14
20
  import {
15
21
  RequestError,
22
+ BadRequestError,
16
23
  NotFoundError,
17
24
  UnauthorizedError,
18
25
  MethodNotAllowedError,
@@ -21,15 +28,21 @@ import {
21
28
  ServiceUnavailableError,
22
29
  } from './errors'
23
30
 
24
- import type { WebSocketHandler } from 'bun'
25
31
  import type { ValidateFunction } from 'ajv'
32
+ import type { WebSocketHandler } from 'bun'
26
33
 
27
34
  import type {
35
+ AsyncHandlerResult,
28
36
  HttpMethod,
29
37
  Request,
38
+ MiddlewareChain,
30
39
  WebSocketRequest,
31
- Middleware,
32
40
  SocketData,
41
+ SocketConnection,
42
+ ActiveSession,
43
+ ActiveSessions,
44
+ InactiveSession,
45
+ Session,
33
46
  AppOptions,
34
47
  } from './utils'
35
48
 
@@ -40,57 +53,8 @@ import type {
40
53
  ResponseMessage,
41
54
  } from './messages'
42
55
 
43
- export type Ticket = {
44
- clientId: string
45
- expiresAt: number
46
- }
47
-
48
- export type SocketConnection = {
49
- data: SocketData
50
- send: (data: string) => unknown
51
- close: () => void
52
- }
53
-
54
- export type ActiveSession = {
55
- token: string
56
- ws: SocketConnection
57
- }
58
-
59
- export type InactiveSession = {
60
- token: string
61
- expiresAt: number
62
- }
63
-
64
- export type Session = ActiveSession | InactiveSession
65
-
66
- export type SocketState = {
67
- disconnectThreshold: number
68
- heartbeatInterval: number
69
- maxTickets: number
70
- reclaimTtl: number
71
- ticketTtl: number
72
- tickets: Map<string, Ticket>
73
- activeSessions: Map<string, ActiveSession>
74
- inactiveSessions: Map<string, InactiveSession>
75
- }
76
-
77
- export type SocketRoute = {
78
- method: HttpMethod
79
- path: string
80
- segments: string[]
81
- chain: Middleware[]
82
- }
83
-
84
- export type SocketEndpoint = {
85
- method: HttpMethod
86
- path: string
87
- handler: (req: Request, res: unknown) => Response
88
- }
89
-
90
- export type SocketCommands = {
91
- send: (clientId: string, event: string, body: unknown) => void
92
- broadcast: (event: string, body: unknown) => void
93
- }
56
+ type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult
57
+ type FilterFn = (clientId: string, data: unknown) => boolean
94
58
 
95
59
  type UpgradeData = {
96
60
  clientId?: string
@@ -102,6 +66,12 @@ type UpgradeContext = {
102
66
  [key: string]: unknown
103
67
  }
104
68
 
69
+ type SocketEndpoint = {
70
+ method: HttpMethod
71
+ path: string
72
+ handler: SocketHandler
73
+ }
74
+
105
75
  type CreateSocketRequest = {
106
76
  query: {
107
77
  ticket: string
@@ -121,6 +91,39 @@ type UpdateTicketRequest = {
121
91
  }
122
92
  }
123
93
 
94
+ export type Ticket = {
95
+ clientId: string
96
+ expiresAt: number
97
+ data: unknown
98
+ }
99
+
100
+ export type SocketState = {
101
+ dropThreshold: number
102
+ heartbeatInterval: number
103
+ maxTickets: number
104
+ reclaimTtl: number
105
+ ticketTtl: number
106
+ tickets: Map<string, Ticket>
107
+ activeSessions: Map<string, ActiveSession>
108
+ inactiveSessions: Map<string, InactiveSession>
109
+ onOpen: ((clientId: string) => void) | null
110
+ onClose: ((clientId: string, reason: CloseReason) => void) | null
111
+ }
112
+
113
+ export type SocketRoute = {
114
+ method: HttpMethod
115
+ path: string
116
+ segments: string[]
117
+ chain: MiddlewareChain
118
+ }
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
+
124
127
  const ajv = new Ajv({
125
128
  allErrors: true,
126
129
  })
@@ -226,7 +229,7 @@ function isSessionActive (session: InactiveSession): boolean {
226
229
  return !session.expiresAt || session.expiresAt > Date.now()
227
230
  }
228
231
 
229
- function randomTicket (): string {
232
+ function randomTicketHash (): string {
230
233
  return crypto.randomBytes(24).toString('base64url')
231
234
  }
232
235
 
@@ -244,6 +247,30 @@ function parseMessage (raw: string | Buffer): RawMessage | undefined {
244
247
  }
245
248
  }
246
249
 
250
+ async function parseJsonBody (req: Request): Promise<unknown> {
251
+ try {
252
+ const body = await req.json()
253
+
254
+ return body
255
+ } catch {
256
+ throw new BadRequestError('Invalid JSON')
257
+ }
258
+ }
259
+
260
+ async function parseJsonBodyAppData (req: Request): Promise<unknown> {
261
+ const contentType = req.headers.get('content-type')
262
+ const usingJsonBody = contentType?.startsWith('application/json')
263
+
264
+ if (!usingJsonBody) {
265
+ return null
266
+ }
267
+
268
+ const rawBody = await parseJsonBody(req)
269
+ const appData = (rawBody as Record<string, unknown> | null)?.data ?? null
270
+
271
+ return appData
272
+ }
273
+
247
274
  function sweepInactiveSessions (state: SocketState): void {
248
275
  for (const [key, session] of state.inactiveSessions) {
249
276
  if (!isSessionActive(session)) {
@@ -334,6 +361,7 @@ function buildParams (
334
361
  function buildRequest (
335
362
  params: Record<string, string>,
336
363
  message: RequestMessage,
364
+ activeSessions: ActiveSessions,
337
365
  ): WebSocketRequest {
338
366
  const { id, clientId, method, route } = message
339
367
  const headers = new Headers(message.headers ?? {})
@@ -349,6 +377,9 @@ function buildRequest (
349
377
  params,
350
378
  query,
351
379
  json,
380
+ ws: {
381
+ active: activeSessions,
382
+ },
352
383
  }
353
384
  }
354
385
 
@@ -360,13 +391,12 @@ async function buildOutgoingMessage (
360
391
  const text = await response.text()
361
392
  const contentType = response.headers.get('content-type') ?? ''
362
393
  const usingJson = contentType.includes('application/json')
363
- const body = usingJson ? JSON.parse(text) : text
364
394
 
365
395
  return createMessage(clientId, MessageType.Response, {
366
396
  id,
367
397
  status: response.status,
368
398
  headers: response.headers,
369
- body,
399
+ body: usingJson ? JSON.parse(text) : text,
370
400
  })
371
401
  }
372
402
 
@@ -375,31 +405,33 @@ function buildErrorMessage (
375
405
  err: unknown,
376
406
  ): ResponseMessage {
377
407
  const { id, clientId } = message as Pick<BaseMessage, 'id' | 'clientId'>
378
-
379
- if (err instanceof RequestError) {
380
- const ctor = err.constructor as typeof RequestError
381
-
382
- return createMessage(clientId, MessageType.Response, {
383
- id,
384
- status: ctor.status,
385
- headers: new Headers({
386
- 'content-type': 'application/json;charset=utf-8',
387
- }),
388
- body: err.output,
389
- })
390
- }
408
+ const isRequestError = err instanceof RequestError
409
+ const httpError = isRequestError ? err : new InternalServerError()
410
+ const { status } = httpError.constructor as typeof RequestError
391
411
 
392
412
  return createMessage(clientId, MessageType.Response, {
393
413
  id,
394
- status: InternalServerError.status,
395
- headers: new Headers(),
396
- body: err instanceof Error ? err.message : undefined,
414
+ status,
415
+ headers: new Headers({
416
+ 'content-type': 'application/json;charset=utf-8',
417
+ }),
418
+ body: httpError.output,
397
419
  })
398
420
  }
399
421
 
422
+ function getCloseReason (ws: SocketConnection, code: number): CloseReason {
423
+ if (ws.data.reaped) {
424
+ return CloseReason.Reaped
425
+ } else if (code === CloseCode.Ok) {
426
+ return CloseReason.Ok
427
+ }
428
+
429
+ return CloseReason.Dropped
430
+ }
431
+
400
432
  export function buildSocketState (opts: AppOptions = {}): SocketState {
401
433
  return {
402
- disconnectThreshold: opts.ws?.disconnectThreshold ?? 120_000,
434
+ dropThreshold: opts.ws?.dropThreshold ?? 120_000,
403
435
  heartbeatInterval: opts.ws?.heartbeatInterval ?? 30_000,
404
436
  maxTickets: opts.ws?.maxTickets ?? 100_000,
405
437
  reclaimTtl: opts.ws?.reclaimTtl ?? 300_000,
@@ -407,6 +439,8 @@ export function buildSocketState (opts: AppOptions = {}): SocketState {
407
439
  tickets: new Map(),
408
440
  activeSessions: new Map(),
409
441
  inactiveSessions: new Map(),
442
+ onOpen: opts.ws?.onOpen ?? null,
443
+ onClose: opts.ws?.onClose ?? null,
410
444
  }
411
445
  }
412
446
 
@@ -415,14 +449,16 @@ export function buildSocketServer (
415
449
  state: SocketState,
416
450
  ): WebSocketHandler<SocketData> {
417
451
  const {
418
- disconnectThreshold,
452
+ dropThreshold,
419
453
  heartbeatInterval,
420
454
  reclaimTtl,
421
455
  activeSessions,
422
456
  inactiveSessions,
457
+ onOpen,
458
+ onClose,
423
459
  } = state
424
460
 
425
- function armReaper (ws: SocketConnection): void {
461
+ function armReaper (ws: SocketConnection) {
426
462
  if (ws.data.reaperHandle) {
427
463
  clearTimeout(ws.data.reaperHandle)
428
464
  }
@@ -430,8 +466,28 @@ export function buildSocketServer (
430
466
  ws.data.reaperHandle = setTimeout(() => {
431
467
  ws.data.reaped = true
432
468
 
433
- ws.close()
434
- }, disconnectThreshold)
469
+ ws.close(CloseCode.Reaped)
470
+ }, dropThreshold)
471
+ }
472
+
473
+ function invokeOpen (ws: SocketConnection) {
474
+ if (onOpen) {
475
+ try {
476
+ onOpen(ws.data.clientId)
477
+ } catch (err) {
478
+ console.error(err)
479
+ }
480
+ }
481
+ }
482
+
483
+ function invokeClose (ws: SocketConnection, reason: CloseReason) {
484
+ if (onClose) {
485
+ try {
486
+ onClose(ws.data.clientId, reason)
487
+ } catch (err) {
488
+ console.error(err)
489
+ }
490
+ }
435
491
  }
436
492
 
437
493
  return {
@@ -469,6 +525,7 @@ export function buildSocketServer (
469
525
  )
470
526
 
471
527
  ws.send(JSON.stringify(welcomeMessage))
528
+ invokeOpen(ws)
472
529
  },
473
530
  close (ws: SocketConnection, code: number): void {
474
531
  if (ws.data.reaperHandle) {
@@ -476,23 +533,29 @@ export function buildSocketServer (
476
533
  }
477
534
 
478
535
  if (ws.data.superseded) {
536
+ invokeClose(ws, CloseReason.Superseded)
537
+
479
538
  return
480
539
  }
481
540
 
482
- const existingSession = activeSessions.get(ws.data.clientId)
541
+ const reason = getCloseReason(ws, code)
542
+ const exists = activeSessions.get(ws.data.clientId)
483
543
 
484
- if (!existingSession || existingSession.ws !== ws) {
544
+ if (!exists || exists.ws !== ws) {
485
545
  return
486
546
  }
487
547
 
488
548
  activeSessions.delete(ws.data.clientId)
489
549
 
490
- if (code !== 1000 || ws.data.reaped) {
550
+ if (code !== CloseCode.Ok || ws.data.reaped) {
491
551
  inactiveSessions.set(ws.data.clientId, {
492
- token: existingSession.token,
552
+ token: exists.token,
493
553
  expiresAt: Date.now() + reclaimTtl,
554
+ app: ws.data.app,
494
555
  })
495
556
  }
557
+
558
+ invokeClose(ws, reason)
496
559
  },
497
560
  async message (ws: SocketConnection, raw: string | Buffer): Promise<void> {
498
561
  const incomingMsg = parseMessage(raw)
@@ -518,7 +581,7 @@ export function buildSocketServer (
518
581
  const { id, clientId } = message
519
582
  const route = matchRoute(routes, message)
520
583
  const params = buildParams(route, message)
521
- const req = buildRequest(params, message)
584
+ const req = buildRequest(params, message, activeSessions)
522
585
  const res = await executeMiddlewareChain(req, route.chain)
523
586
  const outgoingMsg = await buildOutgoingMessage(id, clientId, res)
524
587
 
@@ -543,7 +606,7 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
543
606
  inactiveSessions,
544
607
  } = state
545
608
 
546
- function bindTicket (clientId: string): string {
609
+ function issueTicket (clientId: string, data: unknown): string {
547
610
  for (const [key, entry] of tickets) {
548
611
  if (entry.expiresAt > Date.now()) {
549
612
  break
@@ -556,38 +619,36 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
556
619
  throw new ServiceUnavailableError('Unable to issue ticket')
557
620
  }
558
621
 
559
- const ticket = randomTicket()
622
+ const hash = randomTicketHash()
560
623
  const expiresAt = Date.now() + ticketTtl
561
624
 
562
- tickets.set(ticket, {
625
+ tickets.set(hash, {
563
626
  clientId,
564
627
  expiresAt,
628
+ data,
565
629
  })
566
630
 
567
- return ticket
631
+ return hash
568
632
  }
569
633
 
570
- function redeemTicket (ticket: string): string | undefined {
571
- const entry = ticket ? tickets.get(ticket) : undefined
634
+ function redeemTicket (hash: string): Ticket | undefined {
635
+ const entry = hash ? tickets.get(hash) : undefined
572
636
 
573
637
  if (!entry) {
574
638
  return undefined
575
639
  }
576
640
 
577
- tickets.delete(ticket)
641
+ tickets.delete(hash)
578
642
 
579
- if (entry.expiresAt <= Date.now()) {
580
- return undefined
581
- }
643
+ return entry.expiresAt > Date.now() ? entry : undefined
582
644
 
583
- return entry.clientId
584
645
  }
585
646
 
586
647
  return [
587
648
  {
588
649
  method: 'GET',
589
650
  path: '/ws',
590
- handler (req: Record<string, unknown>, res: unknown): Response {
651
+ handler (req: Request, res: unknown): AsyncHandlerResult {
591
652
  const validReq = validateSchema(req, createSocketValidator)
592
653
 
593
654
  if (typeof res !== 'object') {
@@ -595,16 +656,18 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
595
656
  }
596
657
 
597
658
  const ctx: UpgradeContext = res ? { ...res } : {}
659
+ const ticket = redeemTicket(validReq.query.ticket)
660
+
661
+ if (!ticket) {
662
+ throw new NotFoundError()
663
+ }
598
664
 
599
665
  ctx.data = ctx.data ?? {}
600
- ctx.data.clientId = redeemTicket(validReq.query.ticket)
666
+ ctx.data.clientId = ticket.clientId
601
667
  ctx.data.superseded = false
602
668
  ctx.data.reaped = false
603
669
  ctx.data.reaperHandle = null
604
-
605
- if (!ctx.data.clientId) {
606
- throw new NotFoundError()
607
- }
670
+ ctx.data.app = ticket.data
608
671
 
609
672
  const useSocket = validReq.server.upgrade(validReq.raw, ctx)
610
673
 
@@ -612,20 +675,22 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
612
675
  throw new NotFoundError()
613
676
  }
614
677
 
615
- return new Response()
678
+ return Promise.resolve(new Response())
616
679
  },
617
680
  },
618
681
  {
619
682
  method: 'POST',
620
683
  path: '/ws',
621
- handler (req: Record<string, unknown>, res: unknown): Response {
684
+ async handler (req: Request, res: unknown): AsyncHandlerResult {
622
685
  validateSchema(req, createTicketValidator)
623
686
 
624
687
  const clientId = crypto.randomUUID()
688
+ const appData = await parseJsonBodyAppData(req)
689
+ const ticket = issueTicket(clientId, appData)
625
690
 
626
691
  return Response.json({
627
692
  clientId,
628
- ticket: bindTicket(clientId),
693
+ ticket,
629
694
  data: res,
630
695
  }, { status: StatusCode.Created })
631
696
  },
@@ -633,10 +698,10 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
633
698
  {
634
699
  method: 'PUT',
635
700
  path: '/ws/:clientId',
636
- handler (req: Record<string, unknown>, res: unknown): Response {
701
+ async handler (req: Request, res: unknown): AsyncHandlerResult {
637
702
  const validReq = validateSchema(req, updateTicketValidator)
638
- const authHeader = validReq.headers.get('authorization')
639
- const token = authHeader!.slice('Bearer '.length)
703
+ const authHeader = validReq.headers.get('authorization')!
704
+ const token = authHeader.slice('Bearer '.length)
640
705
 
641
706
  let session: Session | undefined =
642
707
  activeSessions.get(validReq.params.clientId)
@@ -659,9 +724,11 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
659
724
  throw new UnauthorizedError('Invalid token')
660
725
  }
661
726
 
727
+ const appData = 'ws' in session ? session.ws.data.app : session.app
728
+
662
729
  return Response.json({
663
730
  clientId: validReq.params.clientId,
664
- ticket: bindTicket(validReq.params.clientId),
731
+ ticket: issueTicket(validReq.params.clientId, appData),
665
732
  data: res,
666
733
  })
667
734
  },
@@ -670,15 +737,11 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
670
737
  }
671
738
 
672
739
  export function buildSocketCommands (state: SocketState): SocketCommands {
673
- function sendToClient (
674
- clientId: string,
675
- event: string,
676
- body: unknown,
677
- ): void {
740
+ function sendToClient (clientId: string, event: string, body: unknown) {
678
741
  const session = state.activeSessions.get(clientId)
679
742
 
680
743
  if (!session) {
681
- throw new ReferenceError(`No live socket for client: ${clientId}`)
744
+ throw new ReferenceError(`No active socket for client: ${clientId}`)
682
745
  }
683
746
 
684
747
  const message = createMessage(clientId, MessageType.Notification, {
@@ -694,10 +757,28 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
694
757
  send (clientId, event, body) {
695
758
  sendToClient(clientId, event, body)
696
759
  },
760
+ sendToGroup (fn, event, body) {
761
+ for (const [clientId, session] of state.activeSessions) {
762
+ const allow = fn(clientId, session.ws.data)
763
+
764
+ if (allow) {
765
+ sendToClient(clientId, event, body)
766
+ }
767
+ }
768
+ },
697
769
  broadcast (event, body) {
698
770
  for (const clientId of state.activeSessions.keys()) {
699
771
  sendToClient(clientId, event, body)
700
772
  }
701
773
  },
774
+ drop (clientId, code, reason) {
775
+ const session = state.activeSessions.get(clientId)
776
+
777
+ if (!session) {
778
+ throw new ReferenceError(`No active socket for client: ${clientId}`)
779
+ }
780
+
781
+ session.ws.close(code, reason)
782
+ },
702
783
  }
703
784
  }
package/src/utils.ts CHANGED
@@ -90,7 +90,36 @@ export type FormattedError = {
90
90
  message: string
91
91
  }
92
92
 
93
- export type NextFn = (data?: unknown) => Response | Promise<Response>
93
+ export type AsyncHandlerResult = Promise<Response>
94
+ export type HandlerResult = Response | AsyncHandlerResult
95
+ export type NextFn = (data?: unknown) => HandlerResult
96
+
97
+ export type Middleware = (
98
+ req: Request,
99
+ res: unknown,
100
+ next: NextFn,
101
+ ) => HandlerResult
102
+
103
+ export type Handler = (
104
+ req: Request,
105
+ res: unknown,
106
+ ) => HandlerResult
107
+
108
+ export type MiddlewareChain = (Middleware | Handler)[]
109
+
110
+ export type ActiveSession = {
111
+ token: string
112
+ ws: SocketConnection
113
+ }
114
+
115
+ export type InactiveSession = {
116
+ token: string
117
+ expiresAt: number
118
+ app: unknown
119
+ }
120
+
121
+ export type ActiveSessions = ReadonlyMap<string, ActiveSession>
122
+ export type Session = ActiveSession | InactiveSession
94
123
 
95
124
  export type BaseRequest = {
96
125
  method: HttpMethod
@@ -99,6 +128,9 @@ export type BaseRequest = {
99
128
  params: Record<string, string>
100
129
  query: Record<string, unknown>
101
130
  json: () => Promise<unknown>
131
+ ws: {
132
+ active: ActiveSessions
133
+ }
102
134
  }
103
135
 
104
136
  export type EndpointRequest = BaseRequest & {
@@ -113,18 +145,31 @@ export type WebSocketRequest = BaseRequest & {
113
145
 
114
146
  export type Request = EndpointRequest | WebSocketRequest
115
147
 
116
- export type Middleware = (
117
- req: Request,
118
- res: unknown,
119
- next: NextFn | null,
120
- ) => unknown
148
+ export const CloseCode = {
149
+ Ok: 1000,
150
+ Abnormal: 1006,
151
+ Reaped: 4999,
152
+ } as const
153
+
154
+ export type CloseCode = typeof CloseCode[keyof typeof CloseCode]
155
+
156
+ export const CloseReason = {
157
+ Ok: 'ok',
158
+ Dropped: 'dropped',
159
+ Reaped: 'reaped',
160
+ Superseded: 'superseded',
161
+ } as const
162
+
163
+ export type CloseReason = typeof CloseReason[keyof typeof CloseReason]
121
164
 
122
165
  export type SocketOptions = {
123
- disconnectThreshold?: number
166
+ dropThreshold?: number
124
167
  heartbeatInterval?: number
125
168
  maxTickets?: number
126
169
  reclaimTtl?: number
127
170
  ticketTtl?: number
171
+ onOpen?: (clientId: string) => void
172
+ onClose?: (clientId: string, reason: CloseReason) => void
128
173
  }
129
174
 
130
175
  export type SocketData = {
@@ -132,6 +177,13 @@ export type SocketData = {
132
177
  superseded: boolean
133
178
  reaped: boolean
134
179
  reaperHandle: ReturnType<typeof setTimeout> | null
180
+ app: unknown
181
+ }
182
+
183
+ export type SocketConnection = {
184
+ data: SocketData
185
+ send: (data: string) => unknown
186
+ close: (code?: number, reason?: string) => void
135
187
  }
136
188
 
137
189
  export type Server = BunServer<SocketData>
@@ -174,8 +226,8 @@ export function formatError (
174
226
 
175
227
  export async function executeMiddlewareChain (
176
228
  req: Request,
177
- chain: Middleware[],
178
- ): Promise<Response> {
229
+ chain: MiddlewareChain,
230
+ ): AsyncHandlerResult {
179
231
  if (!chain.length) {
180
232
  throw new RangeError('Middleware chain is empty')
181
233
  }
@@ -183,15 +235,14 @@ export async function executeMiddlewareChain (
183
235
  const executeMiddleware = async (
184
236
  index: number,
185
237
  res: unknown,
186
- ): Promise<Response> => {
187
- const currentMiddleware = chain[index]
188
- const isLastMiddleware = index === chain.length - 1
189
-
190
- const next = !isLastMiddleware ?
191
- (data?: unknown) => executeMiddleware(index + 1, data)
192
- : null
193
-
194
- const result = await currentMiddleware(req, res, next)
238
+ ): AsyncHandlerResult => {
239
+ const isLast = index === chain.length - 1
240
+ const fn = chain[index]
241
+ const next = (data?: unknown) => executeMiddleware(index + 1, data)
242
+
243
+ const result = isLast
244
+ ? await (fn as Handler)(req, res)
245
+ : await (fn as Middleware)(req, res, next)
195
246
 
196
247
  if (result instanceof Response) {
197
248
  return result