@rapidrest/service-core 1.8.0 → 2.1.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.
Files changed (87) hide show
  1. package/dist/lib/ApiErrors.js +10 -0
  2. package/dist/lib/ApiErrors.js.map +1 -1
  3. package/dist/lib/BackgroundServiceManager.js +51 -9
  4. package/dist/lib/BackgroundServiceManager.js.map +1 -1
  5. package/dist/lib/EventListenerManager.js +35 -2
  6. package/dist/lib/EventListenerManager.js.map +1 -1
  7. package/dist/lib/NetUtils.js +215 -21
  8. package/dist/lib/NetUtils.js.map +1 -1
  9. package/dist/lib/ObjectFactory.js +7 -2
  10. package/dist/lib/ObjectFactory.js.map +1 -1
  11. package/dist/lib/RateLimiter.js.map +1 -1
  12. package/dist/lib/Server.js +121 -80
  13. package/dist/lib/Server.js.map +1 -1
  14. package/dist/lib/auth/AuthMiddleware.js +160 -100
  15. package/dist/lib/auth/AuthMiddleware.js.map +1 -1
  16. package/dist/lib/auth/JWTStrategy.js +6 -2
  17. package/dist/lib/auth/JWTStrategy.js.map +1 -1
  18. package/dist/lib/database/ConnectionManager.js +47 -1
  19. package/dist/lib/database/ConnectionManager.js.map +1 -1
  20. package/dist/lib/database/DatabaseErrors.js +88 -0
  21. package/dist/lib/database/DatabaseErrors.js.map +1 -0
  22. package/dist/lib/database/MongoRepository.js +31 -3
  23. package/dist/lib/database/MongoRepository.js.map +1 -1
  24. package/dist/lib/database/MongoSchemaSync.js +7 -1
  25. package/dist/lib/database/MongoSchemaSync.js.map +1 -1
  26. package/dist/lib/database/TypeOrmSupport.js +56 -6
  27. package/dist/lib/database/TypeOrmSupport.js.map +1 -1
  28. package/dist/lib/database/index.js +1 -0
  29. package/dist/lib/database/index.js.map +1 -1
  30. package/dist/lib/decorators/PersistenceDecorators.js +23 -0
  31. package/dist/lib/decorators/PersistenceDecorators.js.map +1 -1
  32. package/dist/lib/decorators/RouteDecorators.js +4 -2
  33. package/dist/lib/decorators/RouteDecorators.js.map +1 -1
  34. package/dist/lib/http/bun/BunRouter.js +119 -9
  35. package/dist/lib/http/bun/BunRouter.js.map +1 -1
  36. package/dist/lib/http/index.js +1 -0
  37. package/dist/lib/http/index.js.map +1 -1
  38. package/dist/lib/http/session/sessionMiddleware.js +62 -14
  39. package/dist/lib/http/session/sessionMiddleware.js.map +1 -1
  40. package/dist/lib/http/types.js +10 -1
  41. package/dist/lib/http/types.js.map +1 -1
  42. package/dist/lib/http/uWS/Adapters.js +31 -13
  43. package/dist/lib/http/uWS/Adapters.js.map +1 -1
  44. package/dist/lib/http/uWS/Router.js +80 -16
  45. package/dist/lib/http/uWS/Router.js.map +1 -1
  46. package/dist/lib/http/uWS/WebSocket.js +4 -2
  47. package/dist/lib/http/uWS/WebSocket.js.map +1 -1
  48. package/dist/lib/models/ModelUtils.js +910 -227
  49. package/dist/lib/models/ModelUtils.js.map +1 -1
  50. package/dist/lib/models/RepoUtils.js +863 -266
  51. package/dist/lib/models/RepoUtils.js.map +1 -1
  52. package/dist/lib/routes/BaseAdminRoute.js +5 -4
  53. package/dist/lib/routes/BaseAdminRoute.js.map +1 -1
  54. package/dist/lib/routes/BasePushRoute.js +104 -40
  55. package/dist/lib/routes/BasePushRoute.js.map +1 -1
  56. package/dist/lib/routes/CRUDRoute.js +31 -17
  57. package/dist/lib/routes/CRUDRoute.js.map +1 -1
  58. package/dist/lib/routes/RouteUtils.js +120 -29
  59. package/dist/lib/routes/RouteUtils.js.map +1 -1
  60. package/dist/lib/security/ACLUtils.js +170 -34
  61. package/dist/lib/security/ACLUtils.js.map +1 -1
  62. package/dist/types/ApiErrors.d.ts +10 -0
  63. package/dist/types/BackgroundServiceManager.d.ts +6 -0
  64. package/dist/types/EventListenerManager.d.ts +4 -0
  65. package/dist/types/NetUtils.d.ts +65 -6
  66. package/dist/types/RateLimiter.d.ts +4 -3
  67. package/dist/types/Server.d.ts +33 -2
  68. package/dist/types/auth/AuthMiddleware.d.ts +35 -4
  69. package/dist/types/database/ConnectionManager.d.ts +18 -0
  70. package/dist/types/database/DatabaseErrors.d.ts +26 -0
  71. package/dist/types/database/MongoRepository.d.ts +21 -2
  72. package/dist/types/database/index.d.ts +1 -0
  73. package/dist/types/decorators/PersistenceDecorators.d.ts +31 -0
  74. package/dist/types/decorators/RouteDecorators.d.ts +4 -2
  75. package/dist/types/http/bun/BunRouter.d.ts +23 -2
  76. package/dist/types/http/index.d.ts +2 -1
  77. package/dist/types/http/session/sessionMiddleware.d.ts +14 -4
  78. package/dist/types/http/types.d.ts +40 -0
  79. package/dist/types/http/uWS/Adapters.d.ts +10 -1
  80. package/dist/types/http/uWS/Router.d.ts +15 -2
  81. package/dist/types/models/ModelUtils.d.ts +301 -47
  82. package/dist/types/models/RepoUtils.d.ts +240 -4
  83. package/dist/types/routes/BasePushRoute.d.ts +5 -0
  84. package/dist/types/routes/CRUDRoute.d.ts +10 -0
  85. package/dist/types/routes/RouteUtils.d.ts +37 -1
  86. package/dist/types/security/ACLUtils.d.ts +68 -7
  87. package/package.json +1 -1
@@ -16,6 +16,12 @@ export declare class AuthMiddleware {
16
16
  /**
17
17
  * Performs authentication of the given request using one of the provided strategies.
18
18
  *
19
+ * Each strategy is tried in order until one succeeds. A strategy that throws doesn't stop the others from
20
+ * being tried, since several strategies can claim the same credential (e.g. `jwt` and `oauth_bearer` both
21
+ * read an `Authorization: Bearer` header). If no strategy succeeds and at least one threw, the first error
22
+ * thrown is rethrown, whether or not `required` is set, so callers can still tell a bad credential from a
23
+ * missing one.
24
+ *
19
25
  * @param strategies The list of strategy names to attempt authentication with.
20
26
  * @param req The request containing data to perform authenticate with.
21
27
  * @param res The response to use when writing back directly to the client.
@@ -25,7 +31,8 @@ export declare class AuthMiddleware {
25
31
  /**
26
32
  * Performs authentication of the given request using one of the provided strategies.
27
33
  *
28
- * This is the synchronous version of `authenticate` that performs blocking based authentication.
34
+ * This is the synchronous version of `authenticate` that performs blocking based authentication. It follows
35
+ * the same rules for strategies that throw.
29
36
  *
30
37
  * @param strategies The list of strategy names to attempt authentication with.
31
38
  * @param req The request containing data to perform authenticate with.
@@ -33,18 +40,42 @@ export declare class AuthMiddleware {
33
40
  * @param required Set to `true` to if authentication is required to pass, otherwise set to `false`.
34
41
  */
35
42
  authenticateSync(strategies: string[], req: HttpRequest, res?: HttpResponse, required?: boolean): AuthResult | undefined;
43
+ /**
44
+ * Returns the registered strategy with the given name, throwing if there is none. A missing strategy is a
45
+ * configuration error, so it is never treated as an ordinary authentication failure.
46
+ */
47
+ private getStrategy;
48
+ /**
49
+ * Handles the case where no strategy authenticated the request: rethrows the first strategy error if there was
50
+ * one, throws if authentication is required, and otherwise returns `undefined`.
51
+ */
52
+ private authFailed;
53
+ /**
54
+ * Builds a copy of `req` that carries only the given token, as an `Authorization: Bearer` header. Used to run
55
+ * a WebSocket `LOGIN` message's token through the route's strategies. Query parameters and cookies from the
56
+ * upgrade request are removed, so a strategy can't authenticate from a credential other than the one supplied.
57
+ */
58
+ private createTokenRequest;
36
59
  /**
37
60
  * Returns a request handler function that will perform authentication of a websocket connection. Authentication
38
61
  * can be handled in two ways:
39
62
  *
40
- * 1. Authorization header
63
+ * 1. Authorization header (or any other credential a strategy reads from the upgrade request)
41
64
  * 2. Negotiation via handshake
42
65
  *
43
- * This middleware function primarily provides the implementation for item 2 above.
66
+ * Pre-upgrade auth (see `RouteUtils.registerRoute()`) can only run strategies synchronously. When it didn't
67
+ * authenticate the connection, this handler first runs the strategies asynchronously against the upgrade
68
+ * request, which covers async-only strategies such as `oauth_bearer`. If that doesn't authenticate either, it
69
+ * waits for a `LOGIN` message whose `data` token is also verified through `strategies`, sent to them as an
70
+ * `Authorization: Bearer` header.
71
+ *
72
+ * When `required` is `false`, a failed or missing credential never closes the connection; the handler proceeds
73
+ * anonymously.
44
74
  *
45
75
  * @param required Set to `true` to indicate that auth is required, otherwise `false`.
76
+ * @param strategies The strategy names to authenticate with. Defaults to `["jwt"]`, matching `RouteUtils`.
46
77
  */
47
- authWebSocket(required: boolean): RequestHandler;
78
+ authWebSocket(required: boolean, strategies?: string[]): RequestHandler;
48
79
  /**
49
80
  * Registers the provided authentication strategy to be used
50
81
  * @param name The name of the authentication type to associate the given strategy with
@@ -1,6 +1,24 @@
1
1
  import type { DataSource } from "typeorm";
2
2
  import { MongoConnection } from "./MongoConnection.js";
3
3
  import type { RedisClientType } from "redis";
4
+ /**
5
+ * Attaches `error`, `reconnecting` and `ready` listeners that log a redis client's connection problems.
6
+ *
7
+ * A node-redis client emits `error` whenever its socket fails (e.g. the server restarts or the network drops). An
8
+ * `EventEmitter` with no `error` listener throws on that event, which crashes the whole process. With a listener,
9
+ * the client's own reconnect strategy (exponential backoff, retried indefinitely by default) takes over and pending
10
+ * subscriptions are restored once it reconnects.
11
+ *
12
+ * To avoid flooding the log while a server is unreachable, the first error of an outage is logged as an error and
13
+ * the following ones as debug messages, until the client is `ready` again. Every client this library creates, and
14
+ * every `duplicate()` of one, must be passed through this function. Calling it again on the same client does nothing.
15
+ *
16
+ * @param client The redis client to attach the listeners to. Clients without an `on()` method are ignored.
17
+ * @param logger The logger to report errors to.
18
+ * @param name A name for the client, used in log messages.
19
+ * @returns The given client.
20
+ */
21
+ export declare function attachRedisErrorHandler<T>(client: T, logger: any, name: string): T;
4
22
  /**
5
23
  * Provides database connection management.
6
24
  *
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Determines whether `err` is a unique/primary key violation reported by any of the supported database drivers:
3
+ * - MongoDB: code `11000` (or `11001`);
4
+ * - PostgreSQL: SQLSTATE `23505`;
5
+ * - MySQL/MariaDB: `ER_DUP_ENTRY` (errno `1062`);
6
+ * - SQLite (`better-sqlite3`/`sqlite3`): `SQLITE_CONSTRAINT_UNIQUE`/`SQLITE_CONSTRAINT_PRIMARYKEY`, or a generic
7
+ * `SQLITE_CONSTRAINT` whose message is a `UNIQUE constraint failed` error.
8
+ *
9
+ * @param err The error to inspect.
10
+ */
11
+ export declare function isDuplicateKeyError(err: any): boolean;
12
+ /**
13
+ * Best-effort extraction of the field names involved in a duplicate key error, for telling a primary key/version
14
+ * clash apart from a clash on some other unique column. Returns `undefined` when the driver's error doesn't say.
15
+ * The special value `"PRIMARY"` is returned when the driver only names the primary key (MySQL, a PostgreSQL `PK_`
16
+ * constraint).
17
+ */
18
+ export declare function duplicateKeyFields(err: any): string[] | undefined;
19
+ /**
20
+ * Determines whether a duplicate key error is a clash on a record's identity - its `_id`/primary key or its
21
+ * `(uid, version)` pair - rather than on some other unique column.
22
+ *
23
+ * @param err The duplicate key error.
24
+ * @param fallback The answer to give when the driver's error doesn't name the fields involved.
25
+ */
26
+ export declare function isIdentityDuplicate(err: any, fallback: boolean): boolean;
@@ -1,4 +1,4 @@
1
- import type { Abortable, AggregateOptions, AggregationCursor, Collection, CountDocumentsOptions, Db, DeleteOptions, DeleteResult, DistinctOptions, Document, Filter, FindCursor, FindOneAndDeleteOptions, FindOptions, InsertOneOptions, ReplaceOptions, UpdateOptions, UpdateResult } from "mongodb";
1
+ import type { Abortable, AggregateOptions, AggregationCursor, Collection, CountDocumentsOptions, Db, DeleteOptions, DeleteResult, DistinctOptions, Document, Filter, FindCursor, FindOneAndDeleteOptions, FindOneAndUpdateOptions, FindOptions, InsertOneOptions, ReplaceOptions, UpdateOptions, UpdateResult } from "mongodb";
2
2
  /**
3
3
  * Provides a lightweight repository for performing common operations against a single MongoDB collection using the
4
4
  * native `mongodb` driver. Instances of this class are obtained via `MongoConnection.getRepository`.
@@ -93,13 +93,32 @@ export declare class MongoRepository<T extends Document = any> {
93
93
  * a plain object) without preserving `_id`, where the default `_id`-only behavior would insert a *second*,
94
94
  * duplicate document instead of updating the existing one (see `ACLUtils.saveACL()`).
95
95
  *
96
+ * Pass `insertOnly: true` to always insert, even when `doc` carries an `_id`: the document is inserted with
97
+ * that `_id`, and the driver's duplicate-key error (code `11000`) is thrown if a document with it already
98
+ * exists. Use it for any create-style write whose `_id` may have come from untrusted input, where the default
99
+ * `_id`-present behavior would silently replace whichever existing document owns that `_id` (see
100
+ * `RepoUtils.create()`). Takes precedence over `mergeByUid`.
101
+ *
96
102
  * @param doc The document to save.
97
- * @param options Driver options, plus optionally `mergeByUid` (see above).
103
+ * @param options Driver options, plus optionally `mergeByUid` or `insertOnly` (see above).
98
104
  * @returns The saved document.
99
105
  */
100
106
  save(doc: any, options?: (InsertOneOptions | ReplaceOptions) & {
101
107
  mergeByUid?: boolean;
108
+ insertOnly?: boolean;
102
109
  }): Promise<T>;
110
+ /**
111
+ * Atomically finds the first document matching the given filter, applies the given update operations to it and
112
+ * returns the resulting document (or `null` if none matched). By default the document is returned as it was
113
+ * *after* the update; pass `returnDocument: "before"` to get the original. A single round trip, so a caller that
114
+ * needs the exact document its own update produced isn't racing a separate follow-up read against a concurrent
115
+ * write to the same document (see `RepoUtils.update()`).
116
+ *
117
+ * @param filter The query filter to match documents against.
118
+ * @param update The update operations (e.g. `$set`) to apply.
119
+ * @param options - Optional settings for the command
120
+ */
121
+ findOneAndUpdate(filter: any, update: any, options?: FindOneAndUpdateOptions): Promise<T | null>;
103
122
  /**
104
123
  * Updates all documents matching the given filter with the provided update operations.
105
124
  *
@@ -1,5 +1,6 @@
1
1
  export * from "./ConnectionKinds.js";
2
2
  export * from "./ConnectionManager.js";
3
+ export * from "./DatabaseErrors.js";
3
4
  export * from "./MongoConnection.js";
4
5
  export * from "./MongoRepository.js";
5
6
  export * from "./MongoSchemaSync.js";
@@ -33,7 +33,38 @@ export interface ColumnOptions {
33
33
  * a complex/nested structure that has no direct SQL column equivalent (e.g. `"simple-json"`).
34
34
  */
35
35
  type?: string | Function;
36
+ /**
37
+ * The column's database default, used for rows inserted without a value and, importantly, to fill existing
38
+ * rows when `synchronize` adds a new `NOT NULL` column to a table that already has data (without a default that
39
+ * fails with a `NOT NULL constraint failed` error). A function returns a raw SQL expression, e.g.
40
+ * `() => "CURRENT_TIMESTAMP"`. (SQL only; on MongoDB use a property initializer instead.)
41
+ */
42
+ default?: string | number | boolean | null | (() => string);
43
+ /** The maximum length of a string column, e.g. `varchar(255)`. (SQL only) */
44
+ length?: number | string;
45
+ /**
46
+ * Set to `true` to enforce that every value of the column is unique. Equivalent to also applying `@Unique()` to
47
+ * the property, so it creates a unique index on both SQL and MongoDB.
48
+ */
49
+ unique?: boolean;
50
+ /** The total number of digits stored by a `decimal`/`numeric` column. (SQL only) */
51
+ precision?: number;
52
+ /** The number of digits after the decimal point stored by a `decimal`/`numeric` column. (SQL only) */
53
+ scale?: number;
54
+ /** Set to `true` to declare an array column. Only supported by PostgreSQL and CockroachDB. (SQL only) */
55
+ array?: boolean;
56
+ /** The allowed values of an `enum`/`simple-enum` column. (SQL only) */
57
+ enum?: (string | number)[] | object;
58
+ /** Set to `true` to declare a numeric column `UNSIGNED`. Only supported by MySQL/MariaDB. (SQL only) */
59
+ unsigned?: boolean;
60
+ /** A comment stored with the column definition, where the database supports it. (SQL only) */
61
+ comment?: string;
36
62
  }
63
+ /**
64
+ * The `ColumnOptions` keys that are forwarded to TypeORM verbatim when set. `unique` is not among them: it is
65
+ * registered as a unique index instead (see `Column`), so both backends enforce it the same way.
66
+ */
67
+ export declare const FORWARDED_SQL_COLUMN_OPTIONS: readonly ["name", "nullable", "default", "length", "precision", "scale", "array", "enum", "unsigned", "comment"];
37
68
  /**
38
69
  * The set of options to use when declaring a persistent class type via the `@Entity` decorator.
39
70
  */
@@ -146,8 +146,10 @@ export interface RateLimitOptions extends RateLimitConfig {
146
146
  * endpoints in the class will have rate limiting applied.
147
147
  *
148
148
  * Optionally pass in options to control the identifier of the rate limit. Set `options.id` to set an explicit identifier.
149
- * By default, the method and path of the request (without the query portion) is used as the identifier. Set
150
- * the `options.perUser` option to `true` to have the limit scoped to each user individually.
149
+ * By default, the method and the percent-decoded, normalized path of the request (without the query portion) is used
150
+ * as the identifier. Set the `options.perUser` option to `true` to have the limit scoped to each user individually;
151
+ * anonymous callers are then scoped by client IP address (see `trusted_proxies`). Calling `@RateLimit()` with no
152
+ * options enables `perUser`. See `RouteUtils.checkRateLimiter()`.
151
153
  */
152
154
  export declare function RateLimit(options?: RateLimitOptions): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => void;
153
155
  /**
@@ -1,4 +1,4 @@
1
- import type { IHttpRouter, RequestHandler } from "../types.js";
1
+ import { type IHttpRouter, type RequestHandler, type WebSocketOptions } from "../types.js";
2
2
  import { type WsUpgradeAuth } from "../MiddlewareChain.js";
3
3
  /**
4
4
  * Bun-native HTTP/WebSocket router built on `Bun.serve()`. Exposes the same public surface as the
@@ -21,6 +21,8 @@ export declare class BunRouter implements IHttpRouter {
21
21
  * global CORS middleware so a real app-defined `OPTIONS` handler gets a chance to run). */
22
22
  private readonly explicitOptionsPaths;
23
23
  private readonly maxBodySize;
24
+ /** The HTTP requests currently being handled. */
25
+ private inFlight;
24
26
  private readonly sslConfig;
25
27
  private _bunServer;
26
28
  /** The port the server is currently listening on (set after a successful `listen()` call). */
@@ -59,8 +61,18 @@ export declare class BunRouter implements IHttpRouter {
59
61
  * is aborted. If it returns `{ user, ... }`, those credentials are attached to the request so
60
62
  * downstream middleware sees an authenticated user. If it returns `{}`, auth falls through to
61
63
  * the post-upgrade message-based LOGIN flow.
64
+ *
65
+ * `wsOptions` uses the uWS option names (`maxPayloadLength`, `idleTimeout`, `maxBackpressure`) over
66
+ * `DEFAULT_WS_OPTIONS`. `Bun.serve()` only accepts one WebSocket configuration for the whole server, so
67
+ * `maxPayloadLength` is additionally enforced per route (a larger message closes the socket with 1009), while
68
+ * `idleTimeout` and `maxBackpressure` are applied server-wide using the largest value any route registered.
69
+ */
70
+ ws(routePath: string, handlers: RequestHandler[], wsOptions?: WebSocketOptions, upgradeAuth?: WsUpgradeAuth): this;
71
+ /**
72
+ * Builds the server-wide `Bun.serve()` WebSocket limits from every registered WebSocket route: the largest value
73
+ * of each option, or `DEFAULT_WS_OPTIONS` when no route is registered.
62
74
  */
63
- ws(routePath: string, handlers: RequestHandler[], _wsOptions?: any, upgradeAuth?: WsUpgradeAuth): this;
75
+ private buildWebSocketLimits;
64
76
  /**
65
77
  * Matches a request against the route table. Exact-length static/param routes always outrank
66
78
  * wildcard-suffixed routes, matching uWS's `best ?? wildcard` fallback behavior. Among wildcard
@@ -84,5 +96,14 @@ export declare class BunRouter implements IHttpRouter {
84
96
  listen(host: string, port: number): Promise<void>;
85
97
  /** Stops the server, closing the listen socket. */
86
98
  close(): void;
99
+ /** The number of HTTP requests currently being handled. */
100
+ get inFlightRequests(): number;
101
+ /**
102
+ * Stops accepting new connections, waits up to `timeoutMs` for in-flight HTTP requests to finish, then closes
103
+ * every remaining connection (keep-alive and WebSocket connections included).
104
+ *
105
+ * @param timeoutMs The maximum time to wait for in-flight requests, in milliseconds.
106
+ */
107
+ shutdown(timeoutMs?: number): Promise<void>;
87
108
  [key: string]: any;
88
109
  }
@@ -1,4 +1,5 @@
1
- export type { ErrorHandler, HttpRequest, HttpResponse, IHttpRouter, NextFunction, RequestHandler } from "./types.js";
1
+ export type { ErrorHandler, HttpRequest, HttpResponse, IHttpRouter, NextFunction, RequestHandler, WebSocketOptions, } from "./types.js";
2
+ export { DEFAULT_WS_OPTIONS } from "./types.js";
2
3
  export { isBunRuntime } from "./RuntimeDetect.js";
3
4
  export type { IWebSocketShim } from "./IWebSocketShim.js";
4
5
  export { extractParamNames, makeWsStubResponse, runChain } from "./MiddlewareChain.js";
@@ -2,9 +2,19 @@ import type { RequestHandler } from "../types.js";
2
2
  import type { SessionManager } from "./SessionManager.js";
3
3
  /**
4
4
  * Builds a global, framework-agnostic session middleware backed by the given `SessionManager`.
5
- * Lazily loads-or-creates `req.session` and persists any mutations back to the store once the
6
- * response finishes. Cheap for requests that never touch sessions: no store round-trip unless a
7
- * valid session cookie was actually sent, and no `Set-Cookie`/store write for sessions that end up
8
- * empty.
5
+ *
6
+ * `req.session` is always an object, and `req.sessionIsNew` tells whether it is backed by a stored session:
7
+ *
8
+ * - When the request carries a validly signed session cookie whose session still exists, `req.session` holds that
9
+ * session's data (`req.sessionIsNew === false`) and any non-empty data is saved back once the response finishes.
10
+ * - Otherwise `req.session` starts out as an empty, unsaved object (`req.sessionIsNew === true`). Nothing is stored
11
+ * and no cookie is sent unless a handler explicitly asks for a session by writing to it (setting, defining or
12
+ * deleting a property, or assigning a new object to `req.session`). The first such write issues a fresh session ID
13
+ * via `Set-Cookie`, and the data is saved once the response finishes. A write after the response headers were sent
14
+ * can no longer deliver the cookie, so it isn't saved.
15
+ *
16
+ * This keeps clients that never send cookies (e.g. API clients authenticating with a bearer token) from creating a
17
+ * new stored session on every request. Middleware that only records bookkeeping about the caller (last access time,
18
+ * IP address, ...) should skip `req.session` when `req.sessionIsNew` is `true`.
9
19
  */
10
20
  export declare function createSessionMiddleware(mgr: SessionManager): RequestHandler;
@@ -20,6 +20,12 @@ export interface HttpRequest {
20
20
  * session middleware is registered (i.e. the `session` config block is absent).
21
21
  */
22
22
  session?: Record<string, any>;
23
+ /**
24
+ * Set by the session middleware. `true` when `session` is not backed by a stored session yet (the request carried
25
+ * no valid session cookie): nothing is stored and no cookie is sent unless a handler writes to `session`.
26
+ * Bookkeeping-only writes (last access time, IP address, ...) should be skipped while this is `true`.
27
+ */
28
+ sessionIsNew?: boolean;
23
29
  /** Minimal socket interface; populated with remote address for IP extraction. */
24
30
  socket: {
25
31
  remoteAddress?: string;
@@ -30,6 +36,13 @@ export interface HttpRequest {
30
36
  authPayload?: any;
31
37
  /** Raw JWT token string, set by JWT auth middleware. */
32
38
  authToken?: string;
39
+ /**
40
+ * The registered route pattern that matched this request (e.g. `/items/:id`), set by the router before any
41
+ * middleware runs. `undefined` when no application route matched, i.e. the request is handled by the router's
42
+ * own not-found or CORS preflight fallback. Unlike `path`, the set of possible values is bounded by the
43
+ * application's routes, so it is safe to use as a metrics label.
44
+ */
45
+ routePattern?: string;
33
46
  /** Allow arbitrary per-request properties (e.g. req.websocket, req.wsHandled). */
34
47
  [key: string]: any;
35
48
  }
@@ -92,8 +105,35 @@ export interface IHttpRouter {
92
105
  hasExplicitOptionsRoute(path: string): boolean;
93
106
  ws(path: string, handlers: RequestHandler[], wsOptions?: any, upgradeAuth?: any): this;
94
107
  listen(host: string, port: number): Promise<void>;
108
+ /** Stops accepting new connections. Connections that are already open (including keep-alive and WebSocket
109
+ * connections) are left alone. */
95
110
  close(): void;
111
+ /**
112
+ * Gracefully shuts the server down: stops accepting new connections, waits up to `timeoutMs` milliseconds for
113
+ * HTTP requests that are still being handled to finish, then force-closes every remaining connection
114
+ * (keep-alive and WebSocket connections included). Optional, so custom routers without it keep working;
115
+ * `Server.stop()` falls back to `close()` for those.
116
+ */
117
+ shutdown?(timeoutMs: number): Promise<void>;
96
118
  readonly isListening: boolean;
97
119
  listenPort: number;
98
120
  [key: string]: any;
99
121
  }
122
+ /**
123
+ * WebSocket options shared by every router implementation. Names follow uWebSockets.js' `WebSocketBehavior`.
124
+ */
125
+ export interface WebSocketOptions {
126
+ /** Maximum size of a single incoming message, in bytes. A larger message closes the connection. */
127
+ maxPayloadLength?: number;
128
+ /** Seconds without any received data after which the connection is closed. `0` disables the timeout. */
129
+ idleTimeout?: number;
130
+ /** Maximum number of bytes that may be queued for sending before further messages are dropped. */
131
+ maxBackpressure?: number;
132
+ [key: string]: any;
133
+ }
134
+ /**
135
+ * The WebSocket defaults used by both the uWS and Bun routers when a route doesn't override them. These match
136
+ * uWebSockets.js' own defaults, so a route behaves the same on both runtimes (Bun's own defaults are far larger,
137
+ * e.g. a 16 MiB payload limit).
138
+ */
139
+ export declare const DEFAULT_WS_OPTIONS: Readonly<Required<Pick<WebSocketOptions, "maxPayloadLength" | "idleTimeout" | "maxBackpressure">>>;
@@ -2,7 +2,16 @@ import type { HttpRequest, HttpResponse } from "../types.js";
2
2
  import type { HttpRequest as UWSHttpRequest, HttpResponse as UWSHttpResponse } from "uWebSockets.js";
3
3
  /** Parses a `cookie` header string into a key/value map. */
4
4
  export declare function parseCookies(cookieHeader: string): Record<string, string>;
5
- /** Parses a URL query string (without leading `?`) into a key/value map. */
5
+ /**
6
+ * Parses a URL query string (without leading `?`) into a key/value map. A key given more than once becomes an array
7
+ * of its values.
8
+ *
9
+ * The result is a plain object, so spreading it and calling `Object.prototype` methods through it behave as usual.
10
+ * Only own properties are considered when detecting a repeated key, so a key like `hasOwnProperty` or `constructor`
11
+ * is stored as a normal value instead of being merged with the inherited function. `__proto__` keys are dropped:
12
+ * assigning one would replace the object's prototype, and even as an own property it would do so again when the
13
+ * object is later copied with `Object.assign()` or a deep merge.
14
+ */
6
15
  export declare function parseQueryString(qs: string): Record<string, string | string[]>;
7
16
  /**
8
17
  * Parses a raw request body Buffer according to its content-type header. Shared by both the
@@ -1,5 +1,5 @@
1
1
  import uWS from "uWebSockets.js";
2
- import type { IHttpRouter, RequestHandler } from "../types.js";
2
+ import { type IHttpRouter, type RequestHandler, type WebSocketOptions } from "../types.js";
3
3
  import { type WsUpgradeAuth } from "../MiddlewareChain.js";
4
4
  export { runChain, extractParamNames } from "../MiddlewareChain.js";
5
5
  export type { WsUpgradeAuth, WsUpgradeAuthResult } from "../MiddlewareChain.js";
@@ -40,6 +40,8 @@ export declare class HttpRouter implements IHttpRouter {
40
40
  private readonly explicitOptionsPaths;
41
41
  /** Maximum accepted request body size, in bytes. */
42
42
  private readonly maxBodySize;
43
+ /** The HTTP requests currently being handled. */
44
+ private readonly inFlight;
43
45
  constructor(uwsApp: uWS.TemplatedApp, maxBodySize?: number);
44
46
  /** Returns `true` if the server is currently listening. */
45
47
  get isListening(): boolean;
@@ -51,6 +53,8 @@ export declare class HttpRouter implements IHttpRouter {
51
53
  * everything added after is "post-route" (error handlers, metrics, etc.).
52
54
  */
53
55
  private capturePreRouteCount;
56
+ /** Builds the uWS handler for a route registered at `routePattern` (`undefined` for the router's own fallbacks). */
57
+ private makeHandler;
54
58
  get(routePath: string, ...handlers: RequestHandler[]): this;
55
59
  post(routePath: string, ...handlers: RequestHandler[]): this;
56
60
  put(routePath: string, ...handlers: RequestHandler[]): this;
@@ -77,7 +81,7 @@ export declare class HttpRouter implements IHttpRouter {
77
81
  *
78
82
  * Both `path` and `path + "/"` are registered to avoid trailing-slash mismatch.
79
83
  */
80
- ws(routePath: string, handlers: RequestHandler[], wsOptions?: Partial<uWS.WebSocketBehavior<any>>, upgradeAuth?: WsUpgradeAuth): this;
84
+ ws(routePath: string, handlers: RequestHandler[], wsOptions?: Partial<uWS.WebSocketBehavior<any>> | WebSocketOptions, upgradeAuth?: WsUpgradeAuth): this;
81
85
  /**
82
86
  * Starts listening on the given host and port.
83
87
  * Resolves when the server is ready; rejects if the port cannot be bound.
@@ -85,5 +89,14 @@ export declare class HttpRouter implements IHttpRouter {
85
89
  listen(host: string, port: number): Promise<void>;
86
90
  /** Closes the listen socket, stopping the server from accepting new connections. */
87
91
  close(): void;
92
+ /** The number of HTTP requests currently being handled. */
93
+ get inFlightRequests(): number;
94
+ /**
95
+ * Stops accepting new connections, waits up to `timeoutMs` for in-flight HTTP requests to finish, then closes
96
+ * every remaining connection (keep-alive and WebSocket connections included).
97
+ *
98
+ * @param timeoutMs The maximum time to wait for in-flight requests, in milliseconds.
99
+ */
100
+ shutdown(timeoutMs?: number): Promise<void>;
88
101
  [key: string]: any;
89
102
  }