@rapidrest/service-core 1.0.0-rc.21 → 1.0.0-rc.23

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.
Files changed (90) hide show
  1. package/dist/lib/ApiErrors.js +2 -0
  2. package/dist/lib/ApiErrors.js.map +1 -1
  3. package/dist/lib/OpenApiSpec.js.map +1 -1
  4. package/dist/lib/Server.js +25 -15
  5. package/dist/lib/Server.js.map +1 -1
  6. package/dist/lib/auth/AuthMiddleware.js +2 -1
  7. package/dist/lib/auth/AuthMiddleware.js.map +1 -1
  8. package/dist/lib/auth/JWTStrategy.js +21 -1
  9. package/dist/lib/auth/JWTStrategy.js.map +1 -1
  10. package/dist/lib/database/ConnectionManager.js +7 -1
  11. package/dist/lib/database/ConnectionManager.js.map +1 -1
  12. package/dist/lib/database/MongoSchemaSync.js.map +1 -1
  13. package/dist/lib/decorators/ModelDecorators.js +17 -0
  14. package/dist/lib/decorators/ModelDecorators.js.map +1 -1
  15. package/dist/lib/http/IWebSocketShim.js +2 -0
  16. package/dist/lib/http/IWebSocketShim.js.map +1 -0
  17. package/dist/lib/http/MiddlewareChain.js +129 -0
  18. package/dist/lib/http/MiddlewareChain.js.map +1 -0
  19. package/dist/lib/http/RuntimeDetect.js +8 -0
  20. package/dist/lib/http/RuntimeDetect.js.map +1 -0
  21. package/dist/lib/http/bun/BunAdapters.js +270 -0
  22. package/dist/lib/http/bun/BunAdapters.js.map +1 -0
  23. package/dist/lib/http/bun/BunRouter.js +377 -0
  24. package/dist/lib/http/bun/BunRouter.js.map +1 -0
  25. package/dist/lib/http/bun/BunWebSocket.js +48 -0
  26. package/dist/lib/http/bun/BunWebSocket.js.map +1 -0
  27. package/dist/lib/http/index.js +13 -3
  28. package/dist/lib/http/index.js.map +1 -1
  29. package/dist/lib/http/{Adapters.js → uWS/Adapters.js} +65 -27
  30. package/dist/lib/http/uWS/Adapters.js.map +1 -0
  31. package/dist/lib/http/{Router.js → uWS/Router.js} +24 -133
  32. package/dist/lib/http/uWS/Router.js.map +1 -0
  33. package/dist/lib/http/uWS/WebSocket.js.map +1 -0
  34. package/dist/lib/models/ModelUtils.js +104 -9
  35. package/dist/lib/models/ModelUtils.js.map +1 -1
  36. package/dist/lib/models/RepoUtils.js +89 -6
  37. package/dist/lib/models/RepoUtils.js.map +1 -1
  38. package/dist/lib/routes/BaseAdminRoute.js +7 -5
  39. package/dist/lib/routes/BaseAdminRoute.js.map +1 -1
  40. package/dist/lib/routes/BaseMetricsRoute.js.map +1 -1
  41. package/dist/lib/routes/BasePushRoute.js +32 -5
  42. package/dist/lib/routes/BasePushRoute.js.map +1 -1
  43. package/dist/lib/routes/CRUDRoute.js.map +1 -1
  44. package/dist/lib/routes/ModelRoute.js.map +1 -1
  45. package/dist/lib/routes/RouteUtils.js +6 -2
  46. package/dist/lib/routes/RouteUtils.js.map +1 -1
  47. package/dist/lib/security/ACLUtils.js +10 -5
  48. package/dist/lib/security/ACLUtils.js.map +1 -1
  49. package/dist/lib/security/AccessControlListMongo.js +29 -29
  50. package/dist/lib/security/AccessControlListSQL.js +29 -29
  51. package/dist/lib/security/BaseACLRoute.js.map +1 -1
  52. package/dist/lib/test/requestws.js +28 -3
  53. package/dist/lib/test/requestws.js.map +1 -1
  54. package/dist/types/ApiErrors.d.ts +2 -0
  55. package/dist/types/OpenApiSpec.d.ts +1 -1
  56. package/dist/types/Server.d.ts +4 -4
  57. package/dist/types/auth/AuthMiddleware.d.ts +2 -2
  58. package/dist/types/auth/AuthStrategy.d.ts +2 -2
  59. package/dist/types/auth/BasicStrategy.d.ts +3 -3
  60. package/dist/types/auth/JWTStrategy.d.ts +3 -2
  61. package/dist/types/decorators/ModelDecorators.d.ts +8 -1
  62. package/dist/types/decorators/RouteDecorators.d.ts +1 -1
  63. package/dist/types/http/IWebSocketShim.d.ts +11 -0
  64. package/dist/types/http/MiddlewareChain.d.ts +40 -0
  65. package/dist/types/http/RuntimeDetect.d.ts +2 -0
  66. package/dist/types/http/bun/BunAdapters.d.ts +128 -0
  67. package/dist/types/http/bun/BunRouter.d.ts +79 -0
  68. package/dist/types/http/bun/BunWebSocket.d.ts +35 -0
  69. package/dist/types/http/index.d.ts +12 -6
  70. package/dist/types/http/types.d.ts +21 -0
  71. package/dist/types/http/{Adapters.d.ts → uWS/Adapters.d.ts} +24 -2
  72. package/dist/types/http/{Router.d.ts → uWS/Router.d.ts} +8 -32
  73. package/dist/types/http/{WebSocket.d.ts → uWS/WebSocket.d.ts} +6 -5
  74. package/dist/types/models/ModelUtils.d.ts +30 -0
  75. package/dist/types/models/RepoUtils.d.ts +2 -2
  76. package/dist/types/routes/BaseAdminRoute.d.ts +3 -3
  77. package/dist/types/routes/BaseMetricsRoute.d.ts +1 -1
  78. package/dist/types/routes/BasePushRoute.d.ts +4 -0
  79. package/dist/types/routes/BaseStaticRoute.d.ts +1 -1
  80. package/dist/types/routes/CRUDRoute.d.ts +2 -2
  81. package/dist/types/routes/ModelRoute.d.ts +2 -2
  82. package/dist/types/security/ACLUtils.d.ts +6 -3
  83. package/dist/types/security/AccessControlListMongo.d.ts +1 -1
  84. package/dist/types/security/AccessControlListSQL.d.ts +1 -1
  85. package/dist/types/security/BaseACLRoute.d.ts +3 -3
  86. package/package.json +138 -132
  87. package/dist/lib/http/Adapters.js.map +0 -1
  88. package/dist/lib/http/Router.js.map +0 -1
  89. package/dist/lib/http/WebSocket.js.map +0 -1
  90. /package/dist/lib/http/{WebSocket.js → uWS/WebSocket.js} +0 -0
@@ -0,0 +1,128 @@
1
+ import type { HttpRequest, HttpResponse } from "../types.js";
2
+ /**
3
+ * Minimal structural slice of Bun's `Server` used by `BunRequest`. Deliberately NOT typed as
4
+ * `Bun.Server` — a public constructor parameter typed against the ambient `Bun` namespace would
5
+ * leak into this package's emitted `.d.ts`, breaking `tsc` for downstream consumers who don't have
6
+ * `@types/bun` installed even though they never touch the Bun adapter (confirmed: without this,
7
+ * the emitted declaration file contains a bare unresolvable `Bun.Server<any>` reference). Bun's
8
+ * real `Server` structurally satisfies this interface, so `bun/BunRouter.ts` (which has its own
9
+ * `/// <reference types="bun" />`) can pass a real instance unchanged.
10
+ */
11
+ export interface RequestIPSource {
12
+ requestIP(req: Request): {
13
+ address?: string;
14
+ } | null;
15
+ }
16
+ /**
17
+ * Adapts a Bun/Fetch API `Request` to the framework-agnostic `HttpRequest` interface.
18
+ *
19
+ * Unlike uWS's stack-allocated request, a Fetch API `Request` stays valid across `await`, so there
20
+ * is no urgency to capture everything synchronously in the constructor the way `UWSRequest` must —
21
+ * this is done here purely for parity/simplicity, not correctness.
22
+ */
23
+ export declare class BunRequest implements HttpRequest {
24
+ readonly method: string;
25
+ readonly url: string;
26
+ readonly path: string;
27
+ readonly headers: Record<string, string>;
28
+ params: Record<string, string>;
29
+ readonly query: Record<string, string | string[]>;
30
+ body: any;
31
+ rawBody?: Buffer;
32
+ readonly cookies: Record<string, string>;
33
+ readonly signedCookies: Record<string, string>;
34
+ readonly socket: {
35
+ remoteAddress?: string;
36
+ };
37
+ user?: any;
38
+ authPayload?: any;
39
+ authToken?: string;
40
+ /** Allow arbitrary per-request properties (websocket, wsHandled, etc.) */
41
+ [key: string]: any;
42
+ constructor(req: Request, server: RequestIPSource);
43
+ }
44
+ /**
45
+ * Adapts a buffered/streamed set of response calls to the framework-agnostic `HttpResponse`
46
+ * interface, bridging them into a Fetch API `Response` that `Bun.serve()`'s `fetch()` handler
47
+ * resolves and returns.
48
+ *
49
+ * Two modes:
50
+ * - Buffered (default): `end()`/`json()`/`send()` finalize the body and resolve `responseReady`
51
+ * with a single complete `Response`.
52
+ * - Streaming: `flushHeaders()`/`write()` construct a `ReadableStream`-backed `Response` and
53
+ * resolve `responseReady` immediately (before the body is fully known) so the client starts
54
+ * receiving bytes right away; further `write()` calls enqueue chunks, `end()` closes the stream.
55
+ */
56
+ export declare class BunResponse implements HttpResponse {
57
+ private _statusCode;
58
+ private _headers;
59
+ private _headersSent;
60
+ private _writableEnded;
61
+ private _streaming;
62
+ private _aborted;
63
+ private _abortHandlers;
64
+ /** Set to true for HEAD requests — body bytes must not be sent. */
65
+ isHead: boolean;
66
+ /** Intermediate result passed between middleware. */
67
+ result?: any;
68
+ /** Allow arbitrary per-response properties. */
69
+ [key: string]: any;
70
+ private _body;
71
+ private _controller;
72
+ private _stream;
73
+ private _resolveReady;
74
+ /** Resolves once headers are known — either the full buffered Response, or a streaming one. */
75
+ readonly responseReady: Promise<Response>;
76
+ constructor(rawRequest: Request);
77
+ get statusCode(): number;
78
+ get headersSent(): boolean;
79
+ get writableEnded(): boolean;
80
+ status(code: number): this;
81
+ setHeader(key: string, value: string | number): this;
82
+ getHeader(key: string): string | undefined;
83
+ json(data: any): void;
84
+ send(data?: any): void;
85
+ end(data?: any): void;
86
+ /**
87
+ * Flushes status and headers to the wire immediately without ending the response, via a
88
+ * `ReadableStream`, and resolves `responseReady` right away so the client starts receiving
89
+ * bytes before the rest of the middleware chain finishes. Required before streaming data
90
+ * (e.g. SSE). Safe to call multiple times — only acts on the first call.
91
+ */
92
+ flushHeaders(): void;
93
+ /**
94
+ * Writes a chunk to the response without ending it (streaming / SSE).
95
+ * Flushes headers on the first call if they haven't been sent yet.
96
+ */
97
+ write(data: string | Buffer): void;
98
+ /**
99
+ * Registers a callback to run when the client aborts the connection — either before any
100
+ * response was sent (via the request's `AbortSignal`) or mid-stream (via the `ReadableStream`'s
101
+ * `cancel` callback).
102
+ */
103
+ onAbort(callback: () => void): void;
104
+ /**
105
+ * Errors the underlying stream if it's still open. Used when a middleware chain throws after
106
+ * streaming has already started and the `Response` has already been handed back to the client.
107
+ */
108
+ abortStream(err: any): void;
109
+ private _enqueue;
110
+ private _buildHeaders;
111
+ private _buildResponse;
112
+ private _buildStreamingResponse;
113
+ }
114
+ export type ReadBunBodyResult = {
115
+ ok: true;
116
+ } | {
117
+ ok: false;
118
+ response: Response;
119
+ };
120
+ /**
121
+ * Reads the full request body from a Bun `Request` into a Buffer, mirroring `readBody()`'s uWS
122
+ * behavior exactly: body parsing (JSON / URL-encoded) is applied based on content-type and the
123
+ * result is cached on `req.body` / `req.rawBody`. Rejects with a 413 JSON `Response` if the body
124
+ * exceeds `maxBodySize` — checked both against the declared `Content-Length` up front (fast path)
125
+ * and against the actual accumulated byte count as chunks arrive, so chunked/unknown-length bodies
126
+ * that lie about or omit `Content-Length` are still caught, matching uWS's guarantee.
127
+ */
128
+ export declare function readBunBody(req: BunRequest, rawRequest: Request, maxBodySize?: number): Promise<ReadBunBodyResult>;
@@ -0,0 +1,79 @@
1
+ import type { IHttpRouter, RequestHandler } from "../types.js";
2
+ import { type WsUpgradeAuth } from "../MiddlewareChain.js";
3
+ /**
4
+ * Bun-native HTTP/WebSocket router built on `Bun.serve()`. Exposes the same public surface as the
5
+ * uWS-backed `HttpRouter` (`use/get/post/put/delete/patch/head/options/ws/listen/close/isListening/
6
+ * listenPort`) so `RouteUtils.registerRoute()` and `Server.ts` work against either implementation
7
+ * unchanged.
8
+ *
9
+ * Unlike uWS, `Bun.serve()` has no built-in dynamic router, so this class maintains its own route
10
+ * table matched inside a single `fetch()` callback, with static-segment > `:param` > `/*` wildcard
11
+ * precedence — replicating uWS's specificity-based matching.
12
+ */
13
+ export declare class BunRouter implements IHttpRouter {
14
+ private readonly routesByMethod;
15
+ private readonly wsRoutes;
16
+ private readonly globalMiddleware;
17
+ private preRouteCount;
18
+ private readonly rootWildcardVerbs;
19
+ private readonly maxBodySize;
20
+ private readonly sslConfig;
21
+ private _bunServer;
22
+ /** The port the server is currently listening on (set after a successful `listen()` call). */
23
+ listenPort: number;
24
+ constructor(maxBodySize?: number, sslConfig?: any);
25
+ /** Returns `true` if the server is currently listening. */
26
+ get isListening(): boolean;
27
+ /** Register global middleware that runs before every route handler. */
28
+ use(...handlers: RequestHandler[]): this;
29
+ /**
30
+ * Freezes the pre-route middleware count the first time a route is registered.
31
+ * All global middleware registered BEFORE this call is "pre-route" middleware;
32
+ * everything added after is "post-route" (error handlers, metrics, etc.).
33
+ */
34
+ private capturePreRouteCount;
35
+ private pushRoute;
36
+ private register;
37
+ get(routePath: string, ...handlers: RequestHandler[]): this;
38
+ post(routePath: string, ...handlers: RequestHandler[]): this;
39
+ put(routePath: string, ...handlers: RequestHandler[]): this;
40
+ delete(routePath: string, ...handlers: RequestHandler[]): this;
41
+ patch(routePath: string, ...handlers: RequestHandler[]): this;
42
+ head(routePath: string, ...handlers: RequestHandler[]): this;
43
+ options(routePath: string, ...handlers: RequestHandler[]): this;
44
+ /**
45
+ * Registers a WebSocket route. Handlers follow the same `(req, res, next)` pattern as HTTP
46
+ * routes; they receive `req.websocket` containing the Bun WebSocket shim.
47
+ *
48
+ * `upgradeAuth` is an optional pre-upgrade auth function, run synchronously inside `fetch()`
49
+ * before the handshake. If it returns `{ reject: true }`, an HTTP 401 is sent and the upgrade
50
+ * is aborted. If it returns `{ user, ... }`, those credentials are attached to the request so
51
+ * downstream middleware sees an authenticated user. If it returns `{}`, auth falls through to
52
+ * the post-upgrade message-based LOGIN flow.
53
+ */
54
+ ws(routePath: string, handlers: RequestHandler[], _wsOptions?: any, upgradeAuth?: WsUpgradeAuth): this;
55
+ /**
56
+ * Matches a request against the route table. Exact-length static/param routes always outrank
57
+ * wildcard-suffixed routes, matching uWS's `best ?? wildcard` fallback behavior. Among wildcard
58
+ * candidates, the one with the longest (most specific) matching prefix wins — e.g. `/static/*`
59
+ * beats a bare `/*` for requests under `/static`.
60
+ *
61
+ * `reqHasTrailingSlash` is required to replicate uWS's own wildcard boundary rule, verified
62
+ * empirically against real uWS: a `<prefix>/*` route matches `<prefix>/` and anything nested
63
+ * under it, but NOT the bare `<prefix>` with no trailing slash — a distinction that's lost if
64
+ * matching were done on segment arrays alone, since `/static` and `/static/` produce the same
65
+ * segment list once empty segments are filtered out.
66
+ */
67
+ private matchRoute;
68
+ private matchWsRoute;
69
+ private readonly fetchHandler;
70
+ private readonly websocketConfig;
71
+ /**
72
+ * Starts listening on the given host and port.
73
+ * Resolves when the server is ready; rejects if the port cannot be bound.
74
+ */
75
+ listen(host: string, port: number): Promise<void>;
76
+ /** Stops the server, closing the listen socket. */
77
+ close(): void;
78
+ [key: string]: any;
79
+ }
@@ -0,0 +1,35 @@
1
+ import { EventEmitter } from "events";
2
+ import type { IWebSocketShim } from "../IWebSocketShim.js";
3
+ /**
4
+ * Minimal structural slice of Bun's `ServerWebSocket` used by this shim. Deliberately NOT typed as
5
+ * `Bun.ServerWebSocket` — a public constructor parameter typed against the ambient `Bun` namespace
6
+ * would leak into this package's emitted `.d.ts`, breaking `tsc` for downstream consumers who don't
7
+ * have `@types/bun` installed even though they never touch the Bun adapter. Bun's real
8
+ * `ServerWebSocket` structurally satisfies this interface, so `bun/BunRouter.ts` (which has its own
9
+ * `/// <reference types="bun" />`) can pass a real instance unchanged.
10
+ */
11
+ export interface WebSocketSendTarget {
12
+ send(data: string | Uint8Array): number;
13
+ close(code?: number, reason?: string): void;
14
+ }
15
+ /**
16
+ * Wraps a Bun `ServerWebSocket` handle and exposes an EventEmitter-based API compatible with
17
+ * `IWebSocketShim`, mirroring `UWSWebSocketShim`'s behavior for the uWS-backed router so that
18
+ * route handlers (e.g. `BaseAdminRoute`) and `AuthMiddleware.authWebSocket()` work unchanged
19
+ * regardless of which runtime is active.
20
+ */
21
+ export declare class BunWebSocketShim extends EventEmitter implements IWebSocketShim {
22
+ private readonly _ws;
23
+ readyState: number;
24
+ constructor(ws: WebSocketSendTarget);
25
+ /**
26
+ * Sends data over the WebSocket connection.
27
+ * @param data The data to send.
28
+ * @param cb Optional callback invoked on completion or error.
29
+ */
30
+ send(data: any, cb?: (err?: Error) => void): void;
31
+ /**
32
+ * Closes the WebSocket connection with an optional code and reason.
33
+ */
34
+ close(code?: number, reason?: string): void;
35
+ }
@@ -1,6 +1,12 @@
1
- export type { HttpRequest, HttpResponse, NextFunction, RequestHandler, ErrorHandler } from "./types.js";
2
- export { UWSRequest, UWSResponse, readBody } from "./Adapters.js";
3
- export { HttpRouter, runChain } from "./Router.js";
4
- export type { WsUpgradeAuth, WsUpgradeAuthResult } from "./Router.js";
5
- export type { RequestWS } from "./WebSocket.js";
6
- export { UWSWebSocketShim, createWebSocketStream } from "./WebSocket.js";
1
+ export type { ErrorHandler, HttpRequest, HttpResponse, IHttpRouter, NextFunction, RequestHandler } from "./types.js";
2
+ export { isBunRuntime } from "./RuntimeDetect.js";
3
+ export type { IWebSocketShim } from "./IWebSocketShim.js";
4
+ export { extractParamNames, makeWsStubResponse, runChain } from "./MiddlewareChain.js";
5
+ export type { WsUpgradeAuth, WsUpgradeAuthResult } from "./MiddlewareChain.js";
6
+ export { UWSRequest, UWSResponse, readBody } from "./uWS/Adapters.js";
7
+ export type { HttpRouter } from "./uWS/Router.js";
8
+ export type { RequestWS } from "./uWS/WebSocket.js";
9
+ export { UWSWebSocketShim, createWebSocketStream } from "./uWS/WebSocket.js";
10
+ export { BunRequest, BunResponse, readBunBody } from "./bun/BunAdapters.js";
11
+ export { BunRouter } from "./bun/BunRouter.js";
12
+ export { BunWebSocketShim } from "./bun/BunWebSocket.js";
@@ -52,3 +52,24 @@ export type NextFunction = (err?: any) => void;
52
52
  export type RequestHandler = (req: HttpRequest, res: HttpResponse, next: NextFunction) => void | Promise<void>;
53
53
  /** Standard 4-param error-handling middleware function. */
54
54
  export type ErrorHandler = (err: any, req: HttpRequest, res: HttpResponse, next: NextFunction) => void | Promise<void>;
55
+ /**
56
+ * Public surface shared by every HTTP router implementation (uWS-backed, Bun-backed, ...).
57
+ * `Server.ts` depends only on this interface, never on a concrete router class, so the
58
+ * underlying HTTP server can be swapped per-runtime without touching route registration.
59
+ */
60
+ export interface IHttpRouter {
61
+ use(...handlers: RequestHandler[]): this;
62
+ get(path: string, ...handlers: RequestHandler[]): this;
63
+ post(path: string, ...handlers: RequestHandler[]): this;
64
+ put(path: string, ...handlers: RequestHandler[]): this;
65
+ delete(path: string, ...handlers: RequestHandler[]): this;
66
+ patch(path: string, ...handlers: RequestHandler[]): this;
67
+ head(path: string, ...handlers: RequestHandler[]): this;
68
+ options(path: string, ...handlers: RequestHandler[]): this;
69
+ ws(path: string, handlers: RequestHandler[], wsOptions?: any, upgradeAuth?: any): this;
70
+ listen(host: string, port: number): Promise<void>;
71
+ close(): void;
72
+ readonly isListening: boolean;
73
+ listenPort: number;
74
+ [key: string]: any;
75
+ }
@@ -1,5 +1,17 @@
1
- import type { HttpRequest, HttpResponse } from "./types.js";
1
+ import type { HttpRequest, HttpResponse } from "../types.js";
2
2
  import type { HttpRequest as UWSHttpRequest, HttpResponse as UWSHttpResponse } from "uWebSockets.js";
3
+ /** Parses a `cookie` header string into a key/value map. */
4
+ export declare function parseCookies(cookieHeader: string): Record<string, string>;
5
+ /** Parses a URL query string (without leading `?`) into a key/value map. */
6
+ export declare function parseQueryString(qs: string): Record<string, string | string[]>;
7
+ /**
8
+ * Parses a raw request body Buffer according to its content-type header. Shared by both the
9
+ * uWS-backed and Bun-backed HTTP adapters so body-parsing rules stay in one place:
10
+ * `application/json` is parsed (falling back to the raw string on parse failure),
11
+ * `application/x-www-form-urlencoded` is parsed into a plain object, and anything else is
12
+ * returned as the raw Buffer. Returns `undefined` for an empty body.
13
+ */
14
+ export declare function parseBodyByContentType(raw: Buffer, contentType: string): any;
3
15
  /**
4
16
  * Adapts a uWS `HttpRequest` to the framework-agnostic `HttpRequest` interface.
5
17
  *
@@ -79,9 +91,19 @@ export declare class UWSResponse implements HttpResponse {
79
91
  /** Converts a numeric status code to the "200 OK" string format uWS expects. */
80
92
  private _statusToString;
81
93
  }
94
+ /** Default maximum accepted request body size (10 MiB) when no explicit limit is configured. */
95
+ export declare const DEFAULT_MAX_BODY_SIZE: number;
82
96
  /**
83
97
  * Reads the full request body from a uWS response object as a Buffer.
84
98
  * Body parsing (JSON / URL-encoded) is applied based on content-type and the result
85
99
  * is cached on `req.body` / `req.rawBody`.
100
+ *
101
+ * If the accumulated body exceeds `maxBodySize`, a `413 Payload Too Large` response is written
102
+ * directly and the connection is ended — the caller must check the resolved value and skip running
103
+ * any further middleware/routing for this request when it's `false`, since a response has already
104
+ * been sent.
105
+ *
106
+ * @returns `true` if the body was read normally (or there was none to read), `false` if the request
107
+ * was rejected for exceeding `maxBodySize`.
86
108
  */
87
- export declare function readBody(uwsRes: UWSHttpResponse, req: UWSRequest): Promise<void>;
109
+ export declare function readBody(uwsRes: UWSHttpResponse, req: UWSRequest, maxBodySize?: number): Promise<boolean>;
@@ -1,34 +1,8 @@
1
1
  import uWS from "uWebSockets.js";
2
- import type { HttpRequest, HttpResponse, RequestHandler } from "./types.js";
3
- /**
4
- * Runs an ordered array of middleware handlers sequentially, Express-style.
5
- * Supports both 3-param `(req, res, next)` handlers and 4-param `(err, req, res, next)`
6
- * error handlers. On error, execution skips to the next error handler.
7
- *
8
- * Each handler is awaited via a Promise that resolves when next() is called, not when
9
- * the handler's return value resolves. This correctly handles: (1) sync handlers that
10
- * call next() synchronously, (2) async handlers that return next() or await before
11
- * calling it, and (3) sync handlers that schedule next() via callbacks such as
12
- * setTimeout or socket.once. The chain terminates early when a handler ends the
13
- * response without calling next().
14
- */
15
- export declare function runChain(handlers: RequestHandler[], req: HttpRequest, res: HttpResponse): Promise<void>;
16
- /** Result returned by a pre-upgrade WebSocket auth function. */
17
- export type WsUpgradeAuthResult = {
18
- user?: any;
19
- authPayload?: any;
20
- authToken?: string;
21
- /** Set to `true` to reject the connection with HTTP 401 before the WebSocket handshake. */
22
- reject?: boolean;
23
- };
24
- /**
25
- * Optional pre-upgrade auth function for WebSocket routes. Called synchronously inside the uWS
26
- * `upgrade` callback — before the WebSocket handshake completes. Returning `{ reject: true }`
27
- * sends an HTTP 401 and skips the upgrade entirely. Returning `{}` falls through to the
28
- * post-upgrade message-based LOGIN flow. Returning `{ user, ... }` pre-authenticates the
29
- * connection so clients that can send an Authorization header skip the LOGIN step.
30
- */
31
- export type WsUpgradeAuth = (req: HttpRequest) => WsUpgradeAuthResult;
2
+ import type { IHttpRouter, RequestHandler } from "../types.js";
3
+ import { type WsUpgradeAuth } from "../MiddlewareChain.js";
4
+ export { runChain, extractParamNames } from "../MiddlewareChain.js";
5
+ export type { WsUpgradeAuth, WsUpgradeAuthResult } from "../MiddlewareChain.js";
32
6
  /**
33
7
  * Thin Express-compatible wrapper over `uWS.TemplatedApp`.
34
8
  *
@@ -40,7 +14,7 @@ export type WsUpgradeAuth = (req: HttpRequest) => WsUpgradeAuthResult;
40
14
  * - `ws(path, handlers)` — native uWS WebSocket routing
41
15
  * - `listen(host, port)` / `close()` — server lifecycle
42
16
  */
43
- export declare class HttpRouter {
17
+ export declare class HttpRouter implements IHttpRouter {
44
18
  private readonly uwsApp;
45
19
  private readonly globalMiddleware;
46
20
  private listenSocket;
@@ -57,7 +31,9 @@ export declare class HttpRouter {
57
31
  * avoid clobbering an app-defined root catch-all with the default JSON 404 fallback.
58
32
  */
59
33
  private readonly rootWildcardVerbs;
60
- constructor(uwsApp: uWS.TemplatedApp);
34
+ /** Maximum accepted request body size, in bytes. */
35
+ private readonly maxBodySize;
36
+ constructor(uwsApp: uWS.TemplatedApp, maxBodySize?: number);
61
37
  /** Returns `true` if the server is currently listening. */
62
38
  get isListening(): boolean;
63
39
  /** Register global middleware that runs before every route handler. */
@@ -1,17 +1,18 @@
1
1
  import { EventEmitter } from "events";
2
2
  import { Duplex } from "stream";
3
3
  import type { WebSocket } from "uWebSockets.js";
4
- import type { HttpRequest } from "./types.js";
4
+ import type { HttpRequest } from "../types.js";
5
+ import type { IWebSocketShim } from "../IWebSocketShim.js";
5
6
  /**
6
7
  * HTTP request type for handling WebSocket upgrade requests.
7
8
  * Extends `HttpRequest` with WebSocket-specific properties set by the router
8
- * after the uWS WebSocket connection is opened.
9
+ * after the WebSocket connection is opened.
9
10
  */
10
11
  export interface RequestWS extends HttpRequest {
11
12
  /**
12
- * The uWS WebSocket shim for this connection. Set on `open` after the upgrade.
13
+ * The WebSocket shim for this connection. Set on `open` after the upgrade.
13
14
  */
14
- websocket: UWSWebSocketShim | undefined;
15
+ websocket: IWebSocketShim | undefined;
15
16
  /**
16
17
  * Indicates that the WebSocket handler has processed this connection and the
17
18
  * connection should remain open. Set to `true` by `wrapMiddleware` when a
@@ -31,7 +32,7 @@ export interface RequestWS extends HttpRequest {
31
32
  * The Router's `ws()` implementation stores the shim in `ws.getUserData()` so that
32
33
  * behavior callbacks can call `shim.emit(...)`.
33
34
  */
34
- export declare class UWSWebSocketShim extends EventEmitter {
35
+ export declare class UWSWebSocketShim extends EventEmitter implements IWebSocketShim {
35
36
  private readonly _ws;
36
37
  readyState: number;
37
38
  constructor(ws: WebSocket<any>);
@@ -10,6 +10,7 @@ export declare class ModelUtils {
10
10
  /** The `typeorm` module containing the query operators used to build SQL queries. */
11
11
  private static typeOrm;
12
12
  private static idPropertyCache;
13
+ private static readOnlyPropertyCache;
13
14
  /**
14
15
  * Provides the `typeorm` module to use when building SQL queries. This is called automatically when a SQL
15
16
  * datastore connection is established.
@@ -28,6 +29,13 @@ export declare class ModelUtils {
28
29
  * @returns The list of all property names that have the @Identifier decorator applied.
29
30
  */
30
31
  static getIdPropertyNames(modelClass: any): string[];
32
+ /**
33
+ * Retrieves a list of all of the specified class's properties that have the @ReadOnly decorator applied.
34
+ *
35
+ * @param modelClass The class definition to search for read-only properties from.
36
+ * @returns The list of all property names that have the @ReadOnly decorator applied.
37
+ */
38
+ static getReadOnlyPropertyNames(modelClass: any): string[];
31
39
  /**
32
40
  * Builds a query object for use with `find` functions of the given repository for retrieving objects matching the
33
41
  * specified unique identifier.
@@ -68,6 +76,28 @@ export declare class ModelUtils {
68
76
  * @param param
69
77
  */
70
78
  private static getQueryParamValue;
79
+ /**
80
+ * Recursively verifies that no key in the given value (at any depth, including keys of objects nested inside
81
+ * arrays) is a MongoDB operator (starts with `$`) or uses dot-notation field addressing (contains `.`). Client
82
+ * input is only ever meant to supply plain field values/comparison operands — never raw Mongo query operators —
83
+ * so any such key indicates an attempt to inject arbitrary query behavior (e.g. `$where`, `$expr`, or reaching
84
+ * into a field the API doesn't expose via dot-notation).
85
+ *
86
+ * @param value The value to check, typically a parsed query parameter.
87
+ * @throws {ApiError} If an operator-like or dotted key is found anywhere in `value`.
88
+ */
89
+ private static assertNoOperatorInjection;
90
+ /** Maximum accepted length of a client-supplied `like()` search pattern. */
91
+ private static readonly MAX_LIKE_PATTERN_LENGTH;
92
+ /**
93
+ * Best-effort check for regex patterns vulnerable to catastrophic backtracking (ReDoS). Rejects patterns that
94
+ * are unreasonably long, or that contain a quantified group whose contents are themselves quantified (e.g.
95
+ * `(a+)+`, `(a*)*`) — the classic shape that causes exponential backtracking in JS's regex engine. This is not
96
+ * an exhaustive defense; it catches the common cases a client would realistically send.
97
+ *
98
+ * @param pattern The user-supplied `like()` pattern.
99
+ */
100
+ private static isUnsafeRegexPattern;
71
101
  /**
72
102
  * Given a string containing a parameter value and/or a comparison operation return a MongoDB compatible find value.
73
103
  * e.g.
@@ -2,11 +2,11 @@ import type { Repository } from "typeorm";
2
2
  import { MongoRepository } from "../database/MongoRepository.js";
3
3
  import { BaseEntity } from "../models/BaseEntity.js";
4
4
  import { SimpleEntity } from "../models/SimpleEntity.js";
5
- import { JWTUser } from "@rapidrest/core";
5
+ import { type JWTUser } from "@rapidrest/core";
6
6
  import { Redis } from "ioredis";
7
7
  import { ObjectFactory } from "../ObjectFactory.js";
8
8
  import { NotificationUtils } from "../NotificationUtils.js";
9
- import { AccessControlList } from "../security/index.js";
9
+ import { type AccessControlList } from "../security/index.js";
10
10
  import type { ACLUtils } from "../security/ACLUtils.js";
11
11
  import { ConnectionManager } from "../database/index.js";
12
12
  /**
@@ -1,7 +1,7 @@
1
- import { JWTUser } from "@rapidrest/core";
1
+ import { type JWTUser } from "@rapidrest/core";
2
2
  import { Redis } from "ioredis";
3
3
  import Transport from "winston-transport";
4
- import { type UWSWebSocketShim } from "../http/WebSocket.js";
4
+ import { type IWebSocketShim } from "../http/IWebSocketShim.js";
5
5
  /**
6
6
  * Implements a Winston transport that pipes incoming log messages to a configured redis pubsub channel.
7
7
  */
@@ -53,7 +53,7 @@ export declare class BaseAdminRoute {
53
53
  protected trustedRoles: string[];
54
54
  protected init(): Promise<void>;
55
55
  clearCache(user?: JWTUser): Promise<void>;
56
- logs(socket: UWSWebSocketShim, user: JWTUser): Promise<void>;
56
+ logs(socket: IWebSocketShim, user: JWTUser): Promise<void>;
57
57
  getReleaseNotes(user?: JWTUser): string | undefined;
58
58
  restart(user?: JWTUser): void;
59
59
  }
@@ -1,5 +1,5 @@
1
1
  import * as prom from "prom-client";
2
- import { JWTUser } from "@rapidrest/core";
2
+ import { type JWTUser } from "@rapidrest/core";
3
3
  /**
4
4
  * The `BaseMetricsRoute` class provides a base set of endpoints for exposing Prometheus metrics.
5
5
  *
@@ -31,6 +31,10 @@ export declare class BasePushRoute {
31
31
  private activeSubs;
32
32
  private logger;
33
33
  private redisConfig;
34
+ /** The maximum number of concurrent sockets a single user may hold open at once. */
35
+ private maxSocketsPerUser;
36
+ /** The maximum number of channels a single user may be subscribed to at once. */
37
+ private maxSubscriptionsPerUser;
34
38
  /** A persistent redis client used to publish outgoing push messages. */
35
39
  private redisPub?;
36
40
  private init;
@@ -1,4 +1,4 @@
1
- import { HttpRequest, HttpResponse } from "../http/types.js";
1
+ import type { HttpRequest, HttpResponse } from "../http/types.js";
2
2
  /**
3
3
  * The `BaseStaticRoute` class provides a single endpoint for serving static files from a designated path on the filesystem (e.g. `./public`).
4
4
  * Uses the path specified in the project configuration key `static_files`. Default is `public`.
@@ -1,8 +1,8 @@
1
1
  import { BaseEntity } from "../models/BaseEntity.js";
2
2
  import type { HttpRequest, HttpResponse } from "../http/index.js";
3
3
  import { SimpleEntity } from "../models/SimpleEntity.js";
4
- import { JWTUser } from "@rapidrest/core";
5
- import { ModelRoute, UpdateObject } from "./ModelRoute.js";
4
+ import type { JWTUser } from "@rapidrest/core";
5
+ import { ModelRoute, type UpdateObject } from "./ModelRoute.js";
6
6
  /**
7
7
  * The `CRUDRoute` provides a base implementation of all CRUD endpoint behaviors that `ModelRoute` offers for a given
8
8
  * data model class. This class provides the most common default settings for each route handler. If you desire additional
@@ -1,9 +1,9 @@
1
- import { RepoOperationOptions, RepoUtils } from "../models/RepoUtils.js";
1
+ import { RepoUtils, type RepoOperationOptions } from "../models/RepoUtils.js";
2
2
  import { BaseEntity } from "../models/BaseEntity.js";
3
3
  import { Redis } from "ioredis";
4
4
  import type { HttpRequest, HttpResponse } from "../http/index.js";
5
5
  import { SimpleEntity } from "../models/SimpleEntity.js";
6
- import { AccessControlList } from "../security/AccessControlList.js";
6
+ import { type AccessControlList } from "../security/AccessControlList.js";
7
7
  import { ACLUtils } from "../security/ACLUtils.js";
8
8
  import { NotificationUtils } from "../NotificationUtils.js";
9
9
  import { ObjectFactory } from "../ObjectFactory.js";
@@ -1,6 +1,6 @@
1
- import { JWTUser } from "@rapidrest/core";
1
+ import { type JWTUser } from "@rapidrest/core";
2
2
  import type { HttpRequest as Request } from "../http/index.js";
3
- import { AccessControlList, ACLAction, ACLRecord } from "./AccessControlList.js";
3
+ import { ACLAction, type AccessControlList, type ACLRecord } from "./AccessControlList.js";
4
4
  /**
5
5
  * Common utility functions for working with `AccessControlList` objects and validating user permissions.
6
6
  */
@@ -36,9 +36,12 @@ export declare class ACLUtils {
36
36
  * @param user The user to validate permissions of.
37
37
  * @param acl The ACL or uid of an ACL to validate permissions against.
38
38
  * @param action The action that the user desires permission for.
39
+ * @param reqCache Optional request-scoped cache (see `findACL`) to avoid redundant Redis/DB round trips
40
+ * when the same ACL uid — typically a shared parent — is checked repeatedly within one request, such as
41
+ * once per record when filtering a page of search results.
39
42
  * @returns `true` if the user has at least one of the permissions granted for the given entity, otherwise `false`.
40
43
  */
41
- hasPermission(user: JWTUser | undefined, acl: AccessControlList | string, action: ACLAction): Promise<boolean>;
44
+ hasPermission(user: JWTUser | undefined, acl: AccessControlList | string, action: ACLAction, reqCache?: Map<string, AccessControlList | undefined>): Promise<boolean>;
42
45
  /**
43
46
  * Retrieves the access control list with the associated identifier and populates the parent(s).
44
47
  *
@@ -1,5 +1,5 @@
1
1
  import { BaseMongoEntity } from "../models/index.js";
2
- import { AccessControlList, ACLRecord } from "./AccessControlList.js";
2
+ import type { AccessControlList, ACLRecord } from "./AccessControlList.js";
3
3
  /**
4
4
  * Implementation of the `ACLRecord` interface for use with MongoDB databases.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  import { BaseEntity } from "../models/BaseEntity.js";
2
- import { AccessControlList, ACLRecord } from "./AccessControlList.js";
2
+ import type { AccessControlList, ACLRecord } from "./AccessControlList.js";
3
3
  /**
4
4
  * Implementation of the `ACLRecord` interface for use with SQL databases.
5
5
  */
@@ -1,7 +1,7 @@
1
1
  import type { HttpRequest, HttpResponse } from "../http/index.js";
2
- import { AccessControlList } from "./AccessControlList.js";
3
- import { JWTUser } from "@rapidrest/core";
4
- import { UpdateObject } from "../routes/ModelRoute.js";
2
+ import { type AccessControlList } from "./AccessControlList.js";
3
+ import { type JWTUser } from "@rapidrest/core";
4
+ import type { UpdateObject } from "../routes/ModelRoute.js";
5
5
  import { CRUDRoute } from "../routes/CRUDRoute.js";
6
6
  /**
7
7
  * The `BaseACLRoute` class provides a base set of endpoints for managing Access Control List records.