@web-ts-toolkit/express-json-router 0.32.0 → 0.33.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
@@ -6,14 +6,16 @@ Express router wrapper that routes handler return values through `@web-ts-toolki
6
6
 
7
7
  ```sh
8
8
  pnpm add @web-ts-toolkit/express-json-router express
9
+ pnpm add -D @types/express
9
10
  ```
10
11
 
11
12
  ## Highlights
12
13
 
13
14
  - return plain values from route handlers
14
15
  - throw typed HTTP errors
15
- - use custom response-handler instances when needed
16
+ - use custom response-handler instances when you need isolated behavior
16
17
  - inspect registered endpoints with `getEndpoints()`
18
+ - review supported Express route methods with `JsonRouter.supportedMethods`
17
19
 
18
20
  ## Quick Start
19
21
 
@@ -35,12 +37,42 @@ app.use(router.original);
35
37
 
36
38
  ## Main Exports
37
39
 
38
- - `JsonRouter`
40
+ - default-only `JsonRouter` class export
39
41
  - `JsonRouter.HttpResponse`
40
42
  - `JsonRouter.clientErrors`
41
43
  - `JsonRouter.success`
42
44
  - `JsonRouter.createHandler(...)`
43
45
  - `JsonRouter.ErrorFormats`
46
+ - type imports: `JsonRouterCallback`, `JsonRouterEndpoint`, `JsonRouterHandlerInput`, `JsonRouterMethod`, `JsonRouterMiddlewares`, `JsonRouterRouteRegistrar`, `JsonRouteBuilder`
47
+
48
+ ```ts
49
+ import JsonRouter, { type JsonRouterCallback } from '@web-ts-toolkit/express-json-router';
50
+
51
+ type UserParams = { id: string };
52
+
53
+ const getUser: JsonRouterCallback<UserParams> = (req) => ({
54
+ id: req.params.id,
55
+ });
56
+
57
+ new JsonRouter('/api').get('/users/:id', getUser);
58
+ ```
59
+
60
+ ## Supported Route Methods
61
+
62
+ `JsonRouter.supportedMethods` is the reviewed method contract used for runtime registration, route builder types, endpoint metadata, and emitted declarations. The list tracks the stable route methods exposed by the supported Express 5 runtime.
63
+
64
+ Route paths intentionally use a narrower contract than Express: `basePath`, route method paths, and `router.route(path)` must be strings. Express also accepts `RegExp` and path pattern arrays, but `JsonRouter` keeps string-only paths so `getEndpoints()` can continue returning unambiguous `{ method, path }` metadata. JavaScript callers that pass a non-string path receive `TypeError: JsonRouter route path must be a string path` or `TypeError: JsonRouter basePath must be a string path` before any endpoint is recorded.
65
+
66
+ Every listed method is available on both the router and route builders:
67
+
68
+ ```ts
69
+ import JsonRouter from '@web-ts-toolkit/express-json-router';
70
+
71
+ const router = new JsonRouter('/api');
72
+
73
+ router.propfind('/documents/:id', () => ({ ok: true }));
74
+ router.route('/documents/:id').proppatch(() => ({ ok: true }));
75
+ ```
44
76
 
45
77
  ## Handler Defaults
46
78
 
@@ -52,20 +84,27 @@ app.use(router.original);
52
84
  - `JsonRouter.preError`
53
85
  - `JsonRouter.postError`
54
86
 
55
- These now behave as defaults for future `new JsonRouter(...)` instances.
87
+ These behave as defaults for future `new JsonRouter(...)` instances.
56
88
 
57
89
  - Updating a static property affects routers created after that change.
58
90
  - Existing routers keep the response-handler instance they were constructed with.
91
+ - `JsonRouter.defaultHandler` returns a newly configured handler each time it is read.
59
92
  - For fully isolated behavior, pass an explicit handler instance as the third constructor argument.
60
93
 
61
94
  ```ts
95
+ import JsonRouter from '@web-ts-toolkit/express-json-router';
96
+
97
+ JsonRouter.errorMessageProvider = () => 'default-error';
98
+
99
+ const routerUsingDefaults = new JsonRouter('/api');
100
+
62
101
  const handler = JsonRouter.createHandler({
63
102
  errorFormat: JsonRouter.ErrorFormats.rfc9457,
64
103
  });
65
104
 
66
105
  handler.errorMessageProvider = () => 'custom-error';
67
106
 
68
- const router = new JsonRouter('/api', undefined, handler);
107
+ const routerUsingCustomHandler = new JsonRouter('/admin', undefined, handler);
69
108
  ```
70
109
 
71
110
  ## Documentation
package/index.d.mts CHANGED
@@ -1,22 +1,41 @@
1
1
  import * as _web_ts_toolkit_express_response_handler from '@web-ts-toolkit/express-response-handler';
2
- import { ExpressResponseHandler, OK, Created, Accepted, NonAuthoritativeInfo, NoContent, ResetContent, PartialContent, MultiStatus, AlreadyReported, IMUsed, createHandler } from '@web-ts-toolkit/express-response-handler';
2
+ import { MaybePromise, ExpressResponseHandler, OK, Created, Accepted, NonAuthoritativeInfo, NoContent, ResetContent, PartialContent, MultiStatus, AlreadyReported, IMUsed, createHandler } from '@web-ts-toolkit/express-response-handler';
3
+ import * as _web_ts_toolkit_express_response_handler_responses_csv from '@web-ts-toolkit/express-response-handler/responses/csv';
3
4
  import * as clientErrors from '@web-ts-toolkit/http-errors';
4
- import express, { Request, Response, NextFunction } from 'express';
5
+ import express, { Request, Response, NextFunction, RequestHandler } from 'express';
5
6
 
6
7
  declare const DEFAULT_RESPONSE_HANDLER: ExpressResponseHandler;
7
- declare const METHODS: readonly ["all", "checkout", "copy", "delete", "get", "head", "lock", "merge", "mkactivity", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "purge", "put", "report", "search", "subscribe", "trace", "unlock", "unsubscribe"];
8
- type RouteMethod = (typeof METHODS)[number];
9
- type JsonRouterCallback = (req: Request, res: Response, next: NextFunction) => unknown | Promise<unknown>;
10
- type RouteRegistrar = (path: string, ...callbacks: JsonRouterCallback[]) => JsonRouter;
8
+ declare const SUPPORTED_ROUTE_METHODS: readonly ["acl", "all", "bind", "checkout", "connect", "copy", "delete", "get", "head", "link", "lock", "merge", "mkactivity", "mkcalendar", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "query", "rebind", "report", "search", "source", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"];
9
+ /** HTTP methods supported by `JsonRouter` registration and endpoint metadata. */
10
+ type JsonRouterMethod = (typeof SUPPORTED_ROUTE_METHODS)[number];
11
+ /** Snapshot entry returned by `JsonRouter#getEndpoints()`. */
12
+ type JsonRouterEndpoint = {
13
+ method: Uppercase<JsonRouterMethod>;
14
+ path: string;
15
+ };
16
+ type JsonRouterParams = Record<string, string>;
17
+ type JsonRouterQuery = Record<string, string | string[] | undefined>;
18
+ /** Route handler callback accepted by `JsonRouter` methods and route builders. */
19
+ type JsonRouterCallback<Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown> = (req: Request<Params, ResBody, ReqBody, ReqQuery, Locals>, res: Response<ResBody, Locals>, next: NextFunction) => MaybePromise<Return>;
20
+ /** Recursive callback input accepted by router-level middleware and route registrations. */
21
+ type JsonRouterHandlerInput<Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown> = JsonRouterCallback<Params, ResBody, ReqBody, ReqQuery, Locals, Return> | readonly JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[];
22
+ type JsonRouterMiddlewareInput = JsonRouterCallback | RequestHandler | readonly JsonRouterMiddlewareInput[];
23
+ /** Middleware callback or nested callback array accepted by the constructor. */
24
+ type JsonRouterMiddlewares = JsonRouterMiddlewareInput | readonly JsonRouterMiddlewareInput[];
25
+ /** Registrar function exposed for each supported HTTP method on a `JsonRouter`. */
26
+ type JsonRouterRouteRegistrar = <Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(path: string, ...callbacks: JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]) => JsonRouter;
27
+ /** Fluent builder returned by `JsonRouter#route(path)`. */
11
28
  type JsonRouteBuilder = {
12
- [Method in RouteMethod]: (...callbacks: JsonRouterCallback[]) => JsonRouteBuilder;
29
+ [Method in JsonRouterMethod]: <Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...callbacks: JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]) => JsonRouteBuilder;
13
30
  };
14
- type Endpoint = {
15
- method: Uppercase<RouteMethod>;
16
- path: string;
31
+ type JsonRouterRouteRegistrars = {
32
+ readonly [Method in JsonRouterMethod]: JsonRouterRouteRegistrar;
33
+ };
34
+ type JsonRouterConstructor = Omit<typeof JsonRouterBase, 'prototype'> & {
35
+ new (basePath?: string, middlewares?: JsonRouterMiddlewares, responseHandler?: ExpressResponseHandler): JsonRouter;
36
+ readonly prototype: JsonRouter;
17
37
  };
18
38
  type ExpressRouter = ReturnType<typeof express.Router>;
19
- type JsonRouterMiddlewares = JsonRouterCallback | JsonRouterCallback[];
20
39
  /**
21
40
  * Express router that serializes route handler return values as JSON and
22
41
  * converts thrown `HttpError`s into structured error responses.
@@ -26,36 +45,14 @@ type JsonRouterMiddlewares = JsonRouterCallback | JsonRouterCallback[];
26
45
  * const router = new JsonRouter('/api');
27
46
  * router.get('/health', () => ({ ok: true }));
28
47
  */
29
- declare class JsonRouter {
30
- readonly methods: RouteMethod[];
31
- readonly endpoints: Endpoint[];
32
- readonly middlewares: JsonRouterCallback[];
48
+ declare class JsonRouterBase {
49
+ private readonly _methods;
50
+ private readonly _endpoints;
51
+ private readonly _middlewares;
52
+ /** Normalized base path prepended to every registered route. */
33
53
  readonly basePath: string;
54
+ /** Response handler instance captured when this router is constructed. */
34
55
  readonly responseHandler: ExpressResponseHandler;
35
- all: RouteRegistrar;
36
- checkout: RouteRegistrar;
37
- copy: RouteRegistrar;
38
- delete: RouteRegistrar;
39
- get: RouteRegistrar;
40
- head: RouteRegistrar;
41
- lock: RouteRegistrar;
42
- merge: RouteRegistrar;
43
- mkactivity: RouteRegistrar;
44
- mkcol: RouteRegistrar;
45
- move: RouteRegistrar;
46
- ['m-search']: RouteRegistrar;
47
- notify: RouteRegistrar;
48
- options: RouteRegistrar;
49
- patch: RouteRegistrar;
50
- post: RouteRegistrar;
51
- purge: RouteRegistrar;
52
- put: RouteRegistrar;
53
- report: RouteRegistrar;
54
- search: RouteRegistrar;
55
- subscribe: RouteRegistrar;
56
- trace: RouteRegistrar;
57
- unlock: RouteRegistrar;
58
- unsubscribe: RouteRegistrar;
59
56
  private readonly _router;
60
57
  private static defaultHandlerDefaults;
61
58
  private static getSharedHandlerProperty;
@@ -112,11 +109,7 @@ declare class JsonRouter {
112
109
  requestHeaderFieldsTooLarge: (message?: string | undefined, options?: clientErrors.HttpErrorOptions | undefined) => clientErrors.RequestHeaderFieldsTooLargeError;
113
110
  unavailableForLegalReasons: (message?: string | undefined, options?: clientErrors.HttpErrorOptions | undefined) => clientErrors.UnavailableForLegalReasonsError;
114
111
  json: (data: unknown) => OK<unknown>;
115
- csv: (dataset?: unknown, options?: {
116
- filename?: string;
117
- headers?: boolean;
118
- processor?: (value: unknown) => unknown;
119
- } | undefined) => _web_ts_toolkit_express_response_handler.CSVResponse;
112
+ csv: (dataset?: unknown, options?: _web_ts_toolkit_express_response_handler_responses_csv.CsvResponseOptions | undefined) => _web_ts_toolkit_express_response_handler.CSVResponse;
120
113
  };
121
114
  static readonly ErrorFormats: {
122
115
  readonly simple: "simple";
@@ -124,6 +117,11 @@ declare class JsonRouter {
124
117
  readonly rfc9457: "rfc9457";
125
118
  };
126
119
  static readonly createHandler: typeof createHandler;
120
+ static readonly supportedMethods: readonly ["acl", "all", "bind", "checkout", "connect", "copy", "delete", "get", "head", "link", "lock", "merge", "mkactivity", "mkcalendar", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "query", "rebind", "report", "search", "source", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"];
121
+ /**
122
+ * Creates a fresh response handler from the current static defaults.
123
+ * Existing routers keep the handler instance captured during construction.
124
+ */
127
125
  static get defaultHandler(): ExpressResponseHandler;
128
126
  static get errorMessageProvider(): typeof DEFAULT_RESPONSE_HANDLER.errorMessageProvider;
129
127
  static set errorMessageProvider(customErrorMessageProvider: typeof DEFAULT_RESPONSE_HANDLER.errorMessageProvider);
@@ -135,14 +133,29 @@ declare class JsonRouter {
135
133
  static set preError(preErrorHookFn: typeof DEFAULT_RESPONSE_HANDLER.preError);
136
134
  static get postError(): typeof DEFAULT_RESPONSE_HANDLER.postError;
137
135
  static set postError(postErrorHookFn: typeof DEFAULT_RESPONSE_HANDLER.postError);
136
+ /**
137
+ * Creates a JSON router with a normalized base path, optional shared middleware,
138
+ * and a snapshot of the current static response-handler defaults.
139
+ */
138
140
  constructor(basePath?: string, middlewares?: JsonRouterMiddlewares, responseHandler?: ExpressResponseHandler);
141
+ /** Middleware callbacks captured during construction. */
142
+ get middlewares(): JsonRouterCallback[];
143
+ /** Underlying Express router to mount with `app.use(router.original)`. */
139
144
  get original(): ExpressRouter;
140
145
  param(...args: Parameters<ExpressRouter['param']>): ReturnType<ExpressRouter['param']>;
141
146
  use(...args: Parameters<ExpressRouter['use']>): ReturnType<ExpressRouter['use']>;
147
+ /**
148
+ * Starts a fluent route builder for one path.
149
+ * Registered handlers still pass through this router's response handler.
150
+ */
142
151
  route(path: string): JsonRouteBuilder;
143
- addEndpoint(method: RouteMethod, path: string): void;
144
- getEndpoints(): Endpoint[];
145
- normalizePath(path: string): string;
152
+ private addEndpoint;
153
+ /** Returns a defensive copy of registered endpoint method/path metadata. */
154
+ getEndpoints(): JsonRouterEndpoint[];
155
+ private normalizePath;
156
+ }
157
+ interface JsonRouter extends JsonRouterBase, JsonRouterRouteRegistrars {
146
158
  }
159
+ declare const JsonRouter: JsonRouterConstructor;
147
160
 
148
- export { JsonRouter as default };
161
+ export { type JsonRouteBuilder, type JsonRouterCallback, type JsonRouterEndpoint, type JsonRouterHandlerInput, type JsonRouterMethod, type JsonRouterMiddlewares, type JsonRouterRouteRegistrar, JsonRouter as default };
package/index.d.ts CHANGED
@@ -1,22 +1,41 @@
1
1
  import * as _web_ts_toolkit_express_response_handler from '@web-ts-toolkit/express-response-handler';
2
- import { ExpressResponseHandler, OK, Created, Accepted, NonAuthoritativeInfo, NoContent, ResetContent, PartialContent, MultiStatus, AlreadyReported, IMUsed, createHandler } from '@web-ts-toolkit/express-response-handler';
2
+ import { MaybePromise, ExpressResponseHandler, OK, Created, Accepted, NonAuthoritativeInfo, NoContent, ResetContent, PartialContent, MultiStatus, AlreadyReported, IMUsed, createHandler } from '@web-ts-toolkit/express-response-handler';
3
+ import * as _web_ts_toolkit_express_response_handler_responses_csv from '@web-ts-toolkit/express-response-handler/responses/csv';
3
4
  import * as clientErrors from '@web-ts-toolkit/http-errors';
4
- import express, { Request, Response, NextFunction } from 'express';
5
+ import express, { Request, Response, NextFunction, RequestHandler } from 'express';
5
6
 
6
7
  declare const DEFAULT_RESPONSE_HANDLER: ExpressResponseHandler;
7
- declare const METHODS: readonly ["all", "checkout", "copy", "delete", "get", "head", "lock", "merge", "mkactivity", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "purge", "put", "report", "search", "subscribe", "trace", "unlock", "unsubscribe"];
8
- type RouteMethod = (typeof METHODS)[number];
9
- type JsonRouterCallback = (req: Request, res: Response, next: NextFunction) => unknown | Promise<unknown>;
10
- type RouteRegistrar = (path: string, ...callbacks: JsonRouterCallback[]) => JsonRouter;
8
+ declare const SUPPORTED_ROUTE_METHODS: readonly ["acl", "all", "bind", "checkout", "connect", "copy", "delete", "get", "head", "link", "lock", "merge", "mkactivity", "mkcalendar", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "query", "rebind", "report", "search", "source", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"];
9
+ /** HTTP methods supported by `JsonRouter` registration and endpoint metadata. */
10
+ type JsonRouterMethod = (typeof SUPPORTED_ROUTE_METHODS)[number];
11
+ /** Snapshot entry returned by `JsonRouter#getEndpoints()`. */
12
+ type JsonRouterEndpoint = {
13
+ method: Uppercase<JsonRouterMethod>;
14
+ path: string;
15
+ };
16
+ type JsonRouterParams = Record<string, string>;
17
+ type JsonRouterQuery = Record<string, string | string[] | undefined>;
18
+ /** Route handler callback accepted by `JsonRouter` methods and route builders. */
19
+ type JsonRouterCallback<Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown> = (req: Request<Params, ResBody, ReqBody, ReqQuery, Locals>, res: Response<ResBody, Locals>, next: NextFunction) => MaybePromise<Return>;
20
+ /** Recursive callback input accepted by router-level middleware and route registrations. */
21
+ type JsonRouterHandlerInput<Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown> = JsonRouterCallback<Params, ResBody, ReqBody, ReqQuery, Locals, Return> | readonly JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[];
22
+ type JsonRouterMiddlewareInput = JsonRouterCallback | RequestHandler | readonly JsonRouterMiddlewareInput[];
23
+ /** Middleware callback or nested callback array accepted by the constructor. */
24
+ type JsonRouterMiddlewares = JsonRouterMiddlewareInput | readonly JsonRouterMiddlewareInput[];
25
+ /** Registrar function exposed for each supported HTTP method on a `JsonRouter`. */
26
+ type JsonRouterRouteRegistrar = <Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(path: string, ...callbacks: JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]) => JsonRouter;
27
+ /** Fluent builder returned by `JsonRouter#route(path)`. */
11
28
  type JsonRouteBuilder = {
12
- [Method in RouteMethod]: (...callbacks: JsonRouterCallback[]) => JsonRouteBuilder;
29
+ [Method in JsonRouterMethod]: <Params = JsonRouterParams, ResBody = unknown, ReqBody = unknown, ReqQuery = JsonRouterQuery, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...callbacks: JsonRouterHandlerInput<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]) => JsonRouteBuilder;
13
30
  };
14
- type Endpoint = {
15
- method: Uppercase<RouteMethod>;
16
- path: string;
31
+ type JsonRouterRouteRegistrars = {
32
+ readonly [Method in JsonRouterMethod]: JsonRouterRouteRegistrar;
33
+ };
34
+ type JsonRouterConstructor = Omit<typeof JsonRouterBase, 'prototype'> & {
35
+ new (basePath?: string, middlewares?: JsonRouterMiddlewares, responseHandler?: ExpressResponseHandler): JsonRouter;
36
+ readonly prototype: JsonRouter;
17
37
  };
18
38
  type ExpressRouter = ReturnType<typeof express.Router>;
19
- type JsonRouterMiddlewares = JsonRouterCallback | JsonRouterCallback[];
20
39
  /**
21
40
  * Express router that serializes route handler return values as JSON and
22
41
  * converts thrown `HttpError`s into structured error responses.
@@ -26,36 +45,14 @@ type JsonRouterMiddlewares = JsonRouterCallback | JsonRouterCallback[];
26
45
  * const router = new JsonRouter('/api');
27
46
  * router.get('/health', () => ({ ok: true }));
28
47
  */
29
- declare class JsonRouter {
30
- readonly methods: RouteMethod[];
31
- readonly endpoints: Endpoint[];
32
- readonly middlewares: JsonRouterCallback[];
48
+ declare class JsonRouterBase {
49
+ private readonly _methods;
50
+ private readonly _endpoints;
51
+ private readonly _middlewares;
52
+ /** Normalized base path prepended to every registered route. */
33
53
  readonly basePath: string;
54
+ /** Response handler instance captured when this router is constructed. */
34
55
  readonly responseHandler: ExpressResponseHandler;
35
- all: RouteRegistrar;
36
- checkout: RouteRegistrar;
37
- copy: RouteRegistrar;
38
- delete: RouteRegistrar;
39
- get: RouteRegistrar;
40
- head: RouteRegistrar;
41
- lock: RouteRegistrar;
42
- merge: RouteRegistrar;
43
- mkactivity: RouteRegistrar;
44
- mkcol: RouteRegistrar;
45
- move: RouteRegistrar;
46
- ['m-search']: RouteRegistrar;
47
- notify: RouteRegistrar;
48
- options: RouteRegistrar;
49
- patch: RouteRegistrar;
50
- post: RouteRegistrar;
51
- purge: RouteRegistrar;
52
- put: RouteRegistrar;
53
- report: RouteRegistrar;
54
- search: RouteRegistrar;
55
- subscribe: RouteRegistrar;
56
- trace: RouteRegistrar;
57
- unlock: RouteRegistrar;
58
- unsubscribe: RouteRegistrar;
59
56
  private readonly _router;
60
57
  private static defaultHandlerDefaults;
61
58
  private static getSharedHandlerProperty;
@@ -112,11 +109,7 @@ declare class JsonRouter {
112
109
  requestHeaderFieldsTooLarge: (message?: string | undefined, options?: clientErrors.HttpErrorOptions | undefined) => clientErrors.RequestHeaderFieldsTooLargeError;
113
110
  unavailableForLegalReasons: (message?: string | undefined, options?: clientErrors.HttpErrorOptions | undefined) => clientErrors.UnavailableForLegalReasonsError;
114
111
  json: (data: unknown) => OK<unknown>;
115
- csv: (dataset?: unknown, options?: {
116
- filename?: string;
117
- headers?: boolean;
118
- processor?: (value: unknown) => unknown;
119
- } | undefined) => _web_ts_toolkit_express_response_handler.CSVResponse;
112
+ csv: (dataset?: unknown, options?: _web_ts_toolkit_express_response_handler_responses_csv.CsvResponseOptions | undefined) => _web_ts_toolkit_express_response_handler.CSVResponse;
120
113
  };
121
114
  static readonly ErrorFormats: {
122
115
  readonly simple: "simple";
@@ -124,6 +117,11 @@ declare class JsonRouter {
124
117
  readonly rfc9457: "rfc9457";
125
118
  };
126
119
  static readonly createHandler: typeof createHandler;
120
+ static readonly supportedMethods: readonly ["acl", "all", "bind", "checkout", "connect", "copy", "delete", "get", "head", "link", "lock", "merge", "mkactivity", "mkcalendar", "mkcol", "move", "m-search", "notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "query", "rebind", "report", "search", "source", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"];
121
+ /**
122
+ * Creates a fresh response handler from the current static defaults.
123
+ * Existing routers keep the handler instance captured during construction.
124
+ */
127
125
  static get defaultHandler(): ExpressResponseHandler;
128
126
  static get errorMessageProvider(): typeof DEFAULT_RESPONSE_HANDLER.errorMessageProvider;
129
127
  static set errorMessageProvider(customErrorMessageProvider: typeof DEFAULT_RESPONSE_HANDLER.errorMessageProvider);
@@ -135,14 +133,29 @@ declare class JsonRouter {
135
133
  static set preError(preErrorHookFn: typeof DEFAULT_RESPONSE_HANDLER.preError);
136
134
  static get postError(): typeof DEFAULT_RESPONSE_HANDLER.postError;
137
135
  static set postError(postErrorHookFn: typeof DEFAULT_RESPONSE_HANDLER.postError);
136
+ /**
137
+ * Creates a JSON router with a normalized base path, optional shared middleware,
138
+ * and a snapshot of the current static response-handler defaults.
139
+ */
138
140
  constructor(basePath?: string, middlewares?: JsonRouterMiddlewares, responseHandler?: ExpressResponseHandler);
141
+ /** Middleware callbacks captured during construction. */
142
+ get middlewares(): JsonRouterCallback[];
143
+ /** Underlying Express router to mount with `app.use(router.original)`. */
139
144
  get original(): ExpressRouter;
140
145
  param(...args: Parameters<ExpressRouter['param']>): ReturnType<ExpressRouter['param']>;
141
146
  use(...args: Parameters<ExpressRouter['use']>): ReturnType<ExpressRouter['use']>;
147
+ /**
148
+ * Starts a fluent route builder for one path.
149
+ * Registered handlers still pass through this router's response handler.
150
+ */
142
151
  route(path: string): JsonRouteBuilder;
143
- addEndpoint(method: RouteMethod, path: string): void;
144
- getEndpoints(): Endpoint[];
145
- normalizePath(path: string): string;
152
+ private addEndpoint;
153
+ /** Returns a defensive copy of registered endpoint method/path metadata. */
154
+ getEndpoints(): JsonRouterEndpoint[];
155
+ private normalizePath;
156
+ }
157
+ interface JsonRouter extends JsonRouterBase, JsonRouterRouteRegistrars {
146
158
  }
159
+ declare const JsonRouter: JsonRouterConstructor;
147
160
 
148
- export { JsonRouter as default };
161
+ export { type JsonRouteBuilder, type JsonRouterCallback, type JsonRouterEndpoint, type JsonRouterHandlerInput, type JsonRouterMethod, type JsonRouterMiddlewares, type JsonRouterRouteRegistrar, JsonRouter as default };
package/index.js CHANGED
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
8
12
  var __copyProps = (to, from, except, desc) => {
9
13
  if (from && typeof from === "object" || typeof from === "function") {
10
14
  for (let key of __getOwnPropNames(from))
@@ -21,24 +25,35 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
26
  mod
23
27
  ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
29
 
25
30
  // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ default: () => index_default
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
26
36
  var import_express_response_handler = require("@web-ts-toolkit/express-response-handler");
27
37
  var import_express_response_handler2 = __toESM(require("@web-ts-toolkit/express-response-handler"));
28
38
  var clientErrors = __toESM(require("@web-ts-toolkit/http-errors"));
29
39
  var import_utils = require("@web-ts-toolkit/utils");
30
40
  var import_express = __toESM(require("express"));
31
41
  var DEFAULT_RESPONSE_HANDLER = import_express_response_handler2.default;
32
- var METHODS = [
42
+ var SUPPORTED_ROUTE_METHODS = Object.freeze([
43
+ "acl",
33
44
  "all",
45
+ "bind",
34
46
  "checkout",
47
+ "connect",
35
48
  "copy",
36
49
  "delete",
37
50
  "get",
38
51
  "head",
52
+ "link",
39
53
  "lock",
40
54
  "merge",
41
55
  "mkactivity",
56
+ "mkcalendar",
42
57
  "mkcol",
43
58
  "move",
44
59
  "m-search",
@@ -46,15 +61,27 @@ var METHODS = [
46
61
  "options",
47
62
  "patch",
48
63
  "post",
64
+ "propfind",
65
+ "proppatch",
49
66
  "purge",
50
67
  "put",
68
+ "query",
69
+ "rebind",
51
70
  "report",
52
71
  "search",
72
+ "source",
53
73
  "subscribe",
54
74
  "trace",
75
+ "unbind",
76
+ "unlink",
55
77
  "unlock",
56
78
  "unsubscribe"
57
- ];
79
+ ]);
80
+ var assertStringPath = (value, label) => {
81
+ if (typeof value !== "string") {
82
+ throw new TypeError(`JsonRouter ${label} must be a string path`);
83
+ }
84
+ };
58
85
  var success = {
59
86
  OK: import_express_response_handler.OK,
60
87
  Created: import_express_response_handler.Created,
@@ -68,17 +95,46 @@ var success = {
68
95
  IMUsed: import_express_response_handler.IMUsed
69
96
  };
70
97
  var normalizeBasePath = (value) => {
98
+ assertStringPath(value, "basePath");
71
99
  if (!value || value === "/") {
72
100
  return "";
73
101
  }
74
102
  return (0, import_utils.addLeadingSlash)(value).replace(/\/+$/, "");
75
103
  };
76
- var joinRoutePath = (basePath, path) => `${basePath}${(0, import_utils.addLeadingSlash)(path)}`;
104
+ var joinRoutePath = (basePath, path) => {
105
+ assertStringPath(path, "route path");
106
+ return `${basePath}${(0, import_utils.addLeadingSlash)(path)}`;
107
+ };
108
+ var assertJsonRouterCallback = (handler) => {
109
+ if (typeof handler !== "function") {
110
+ throw new TypeError("middleware handler must be a function");
111
+ }
112
+ if (handler.length >= 4) {
113
+ throw new TypeError("route-local error middleware must be mounted with use()");
114
+ }
115
+ };
116
+ var flattenHandlerInputs = (handlers) => {
117
+ const flattened = [];
118
+ for (const handler of handlers) {
119
+ if (Array.isArray(handler)) {
120
+ flattened.push(...flattenHandlerInputs(handler));
121
+ continue;
122
+ }
123
+ assertJsonRouterCallback(handler);
124
+ flattened.push(handler);
125
+ }
126
+ return flattened;
127
+ };
128
+ var assertHasMiddleware = (handlers) => {
129
+ if (handlers.length === 0) {
130
+ throw new TypeError("at least one middleware handler is required");
131
+ }
132
+ };
77
133
  var toMiddlewareList = (middlewares) => {
78
134
  if (!middlewares) {
79
135
  return [];
80
136
  }
81
- return Array.isArray(middlewares) ? middlewares : [middlewares];
137
+ return flattenHandlerInputs(Array.isArray(middlewares) ? middlewares : [middlewares]);
82
138
  };
83
139
  var createResponseHandlerFromDefaults = (defaults) => {
84
140
  const handler = (0, import_express_response_handler.createHandler)();
@@ -89,24 +145,30 @@ var createResponseHandlerFromDefaults = (defaults) => {
89
145
  handler.postError = defaults.postError;
90
146
  return handler;
91
147
  };
92
- var JsonRouter = class _JsonRouter {
93
- constructor(basePath = "", middlewares, responseHandler = _JsonRouter.defaultHandler) {
94
- this.methods = [];
95
- this.endpoints = [];
148
+ var JsonRouterBase = class _JsonRouterBase {
149
+ /**
150
+ * Creates a JSON router with a normalized base path, optional shared middleware,
151
+ * and a snapshot of the current static response-handler defaults.
152
+ */
153
+ constructor(basePath = "", middlewares, responseHandler = _JsonRouterBase.defaultHandler) {
154
+ this._methods = [];
155
+ this._endpoints = [];
96
156
  this.basePath = normalizeBasePath(basePath);
97
- this.middlewares = toMiddlewareList(middlewares);
157
+ this._middlewares = toMiddlewareList(middlewares);
98
158
  this.responseHandler = responseHandler;
99
159
  this._router = import_express.default.Router();
100
- for (const method of METHODS) {
160
+ for (const method of SUPPORTED_ROUTE_METHODS) {
101
161
  const routerMethod = this._router[method];
102
162
  if (typeof routerMethod !== "function") {
103
- continue;
163
+ throw new Error(`Express Router does not expose the supported ${method.toUpperCase()} route method`);
104
164
  }
105
- this.methods.push(method);
165
+ this._methods.push(method);
106
166
  Object.defineProperty(this, method, {
107
167
  value: (path, ...callbacks) => {
168
+ const routeCallbacks = flattenHandlerInputs(callbacks);
169
+ assertHasMiddleware(routeCallbacks);
108
170
  const fullPath = joinRoutePath(this.basePath, path);
109
- const handlers = this.responseHandler.handleResponse([...this.middlewares, ...callbacks]);
171
+ const handlers = this.responseHandler.handleResponse([...this._middlewares, ...routeCallbacks]);
110
172
  routerMethod.call(this._router, fullPath, handlers);
111
173
  this.addEndpoint(method, fullPath);
112
174
  return this;
@@ -127,10 +189,10 @@ var JsonRouter = class _JsonRouter {
127
189
  };
128
190
  }
129
191
  static getSharedHandlerProperty(name) {
130
- return _JsonRouter.defaultHandlerDefaults[name];
192
+ return _JsonRouterBase.defaultHandlerDefaults[name];
131
193
  }
132
194
  static setSharedHandlerProperty(name, value) {
133
- _JsonRouter.defaultHandlerDefaults[name] = value;
195
+ _JsonRouterBase.defaultHandlerDefaults[name] = value;
134
196
  }
135
197
  static {
136
198
  this.clientErrors = clientErrors;
@@ -147,39 +209,51 @@ var JsonRouter = class _JsonRouter {
147
209
  static {
148
210
  this.createHandler = import_express_response_handler.createHandler;
149
211
  }
212
+ static {
213
+ this.supportedMethods = SUPPORTED_ROUTE_METHODS;
214
+ }
215
+ /**
216
+ * Creates a fresh response handler from the current static defaults.
217
+ * Existing routers keep the handler instance captured during construction.
218
+ */
150
219
  static get defaultHandler() {
151
- return createResponseHandlerFromDefaults(_JsonRouter.defaultHandlerDefaults);
220
+ return createResponseHandlerFromDefaults(_JsonRouterBase.defaultHandlerDefaults);
152
221
  }
153
222
  static get errorMessageProvider() {
154
- return _JsonRouter.getSharedHandlerProperty("errorMessageProvider");
223
+ return _JsonRouterBase.getSharedHandlerProperty("errorMessageProvider");
155
224
  }
156
225
  static set errorMessageProvider(customErrorMessageProvider) {
157
- _JsonRouter.setSharedHandlerProperty("errorMessageProvider", customErrorMessageProvider);
226
+ _JsonRouterBase.setSharedHandlerProperty("errorMessageProvider", customErrorMessageProvider);
158
227
  }
159
228
  static get preJson() {
160
- return _JsonRouter.getSharedHandlerProperty("preJson");
229
+ return _JsonRouterBase.getSharedHandlerProperty("preJson");
161
230
  }
162
231
  static set preJson(preJsonHookFn) {
163
- _JsonRouter.setSharedHandlerProperty("preJson", preJsonHookFn);
232
+ _JsonRouterBase.setSharedHandlerProperty("preJson", preJsonHookFn);
164
233
  }
165
234
  static get postJson() {
166
- return _JsonRouter.getSharedHandlerProperty("postJson");
235
+ return _JsonRouterBase.getSharedHandlerProperty("postJson");
167
236
  }
168
237
  static set postJson(postJsonHookFn) {
169
- _JsonRouter.setSharedHandlerProperty("postJson", postJsonHookFn);
238
+ _JsonRouterBase.setSharedHandlerProperty("postJson", postJsonHookFn);
170
239
  }
171
240
  static get preError() {
172
- return _JsonRouter.getSharedHandlerProperty("preError");
241
+ return _JsonRouterBase.getSharedHandlerProperty("preError");
173
242
  }
174
243
  static set preError(preErrorHookFn) {
175
- _JsonRouter.setSharedHandlerProperty("preError", preErrorHookFn);
244
+ _JsonRouterBase.setSharedHandlerProperty("preError", preErrorHookFn);
176
245
  }
177
246
  static get postError() {
178
- return _JsonRouter.getSharedHandlerProperty("postError");
247
+ return _JsonRouterBase.getSharedHandlerProperty("postError");
179
248
  }
180
249
  static set postError(postErrorHookFn) {
181
- _JsonRouter.setSharedHandlerProperty("postError", postErrorHookFn);
250
+ _JsonRouterBase.setSharedHandlerProperty("postError", postErrorHookFn);
251
+ }
252
+ /** Middleware callbacks captured during construction. */
253
+ get middlewares() {
254
+ return this._middlewares.slice();
182
255
  }
256
+ /** Underlying Express router to mount with `app.use(router.original)`. */
183
257
  get original() {
184
258
  return this._router;
185
259
  }
@@ -189,9 +263,14 @@ var JsonRouter = class _JsonRouter {
189
263
  use(...args) {
190
264
  return this._router.use(...args);
191
265
  }
266
+ /**
267
+ * Starts a fluent route builder for one path.
268
+ * Registered handlers still pass through this router's response handler.
269
+ */
192
270
  route(path) {
271
+ assertStringPath(path, "route path");
193
272
  const definition = {};
194
- for (const method of this.methods) {
273
+ for (const method of this._methods) {
195
274
  Object.defineProperty(definition, method, {
196
275
  value: (...callbacks) => {
197
276
  this[method](path, ...callbacks);
@@ -205,16 +284,18 @@ var JsonRouter = class _JsonRouter {
205
284
  return definition;
206
285
  }
207
286
  addEndpoint(method, path) {
208
- this.endpoints.push({
287
+ this._endpoints.push({
209
288
  method: method.toUpperCase(),
210
289
  path: this.normalizePath(path)
211
290
  });
212
291
  }
292
+ /** Returns a defensive copy of registered endpoint method/path metadata. */
213
293
  getEndpoints() {
214
- return this.endpoints.map((endpoint) => ({ ...endpoint }));
294
+ return this._endpoints.map((endpoint) => ({ ...endpoint }));
215
295
  }
216
296
  normalizePath(path) {
217
297
  return (0, import_utils.addLeadingSlash)(path);
218
298
  }
219
299
  };
220
- module.exports = JsonRouter;
300
+ var JsonRouter = JsonRouterBase;
301
+ var index_default = JsonRouter;
package/index.mjs CHANGED
@@ -1,8 +1,3 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __commonJS = (cb, mod) => function __require() {
3
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4
- };
5
-
6
1
  // src/index.ts
7
2
  import {
8
3
  Accepted,
@@ -23,198 +18,267 @@ import apiHandler from "@web-ts-toolkit/express-response-handler";
23
18
  import * as clientErrors from "@web-ts-toolkit/http-errors";
24
19
  import { addLeadingSlash } from "@web-ts-toolkit/utils";
25
20
  import express from "express";
26
- var require_index = __commonJS({
27
- "src/index.ts"(exports, module) {
28
- var DEFAULT_RESPONSE_HANDLER = apiHandler;
29
- var METHODS = [
30
- "all",
31
- "checkout",
32
- "copy",
33
- "delete",
34
- "get",
35
- "head",
36
- "lock",
37
- "merge",
38
- "mkactivity",
39
- "mkcol",
40
- "move",
41
- "m-search",
42
- "notify",
43
- "options",
44
- "patch",
45
- "post",
46
- "purge",
47
- "put",
48
- "report",
49
- "search",
50
- "subscribe",
51
- "trace",
52
- "unlock",
53
- "unsubscribe"
54
- ];
55
- var success = {
56
- OK,
57
- Created,
58
- Accepted,
59
- NonAuthoritativeInfo,
60
- NoContent,
61
- ResetContent,
62
- PartialContent,
63
- MultiStatus,
64
- AlreadyReported,
65
- IMUsed
66
- };
67
- var normalizeBasePath = (value) => {
68
- if (!value || value === "/") {
69
- return "";
70
- }
71
- return addLeadingSlash(value).replace(/\/+$/, "");
72
- };
73
- var joinRoutePath = (basePath, path) => `${basePath}${addLeadingSlash(path)}`;
74
- var toMiddlewareList = (middlewares) => {
75
- if (!middlewares) {
76
- return [];
77
- }
78
- return Array.isArray(middlewares) ? middlewares : [middlewares];
79
- };
80
- var createResponseHandlerFromDefaults = (defaults) => {
81
- const handler = createHandler();
82
- handler.errorMessageProvider = defaults.errorMessageProvider;
83
- handler.preJson = defaults.preJson;
84
- handler.postJson = defaults.postJson;
85
- handler.preError = defaults.preError;
86
- handler.postError = defaults.postError;
87
- return handler;
88
- };
89
- var JsonRouter = class _JsonRouter {
90
- constructor(basePath = "", middlewares, responseHandler = _JsonRouter.defaultHandler) {
91
- this.methods = [];
92
- this.endpoints = [];
93
- this.basePath = normalizeBasePath(basePath);
94
- this.middlewares = toMiddlewareList(middlewares);
95
- this.responseHandler = responseHandler;
96
- this._router = express.Router();
97
- for (const method of METHODS) {
98
- const routerMethod = this._router[method];
99
- if (typeof routerMethod !== "function") {
100
- continue;
101
- }
102
- this.methods.push(method);
103
- Object.defineProperty(this, method, {
104
- value: (path, ...callbacks) => {
105
- const fullPath = joinRoutePath(this.basePath, path);
106
- const handlers = this.responseHandler.handleResponse([...this.middlewares, ...callbacks]);
107
- routerMethod.call(this._router, fullPath, handlers);
108
- this.addEndpoint(method, fullPath);
109
- return this;
110
- },
111
- enumerable: false,
112
- writable: false,
113
- configurable: false
114
- });
115
- }
116
- }
117
- static {
118
- this.defaultHandlerDefaults = {
119
- errorMessageProvider: DEFAULT_RESPONSE_HANDLER.errorMessageProvider,
120
- preJson: DEFAULT_RESPONSE_HANDLER.preJson,
121
- postJson: DEFAULT_RESPONSE_HANDLER.postJson,
122
- preError: DEFAULT_RESPONSE_HANDLER.preError,
123
- postError: DEFAULT_RESPONSE_HANDLER.postError
124
- };
125
- }
126
- static getSharedHandlerProperty(name) {
127
- return _JsonRouter.defaultHandlerDefaults[name];
128
- }
129
- static setSharedHandlerProperty(name, value) {
130
- _JsonRouter.defaultHandlerDefaults[name] = value;
131
- }
132
- static {
133
- this.clientErrors = clientErrors;
134
- }
135
- static {
136
- this.success = success;
137
- }
138
- static {
139
- this.HttpResponse = HttpResponse;
140
- }
141
- static {
142
- this.ErrorFormats = ErrorFormats;
143
- }
144
- static {
145
- this.createHandler = createHandler;
146
- }
147
- static get defaultHandler() {
148
- return createResponseHandlerFromDefaults(_JsonRouter.defaultHandlerDefaults);
149
- }
150
- static get errorMessageProvider() {
151
- return _JsonRouter.getSharedHandlerProperty("errorMessageProvider");
152
- }
153
- static set errorMessageProvider(customErrorMessageProvider) {
154
- _JsonRouter.setSharedHandlerProperty("errorMessageProvider", customErrorMessageProvider);
155
- }
156
- static get preJson() {
157
- return _JsonRouter.getSharedHandlerProperty("preJson");
158
- }
159
- static set preJson(preJsonHookFn) {
160
- _JsonRouter.setSharedHandlerProperty("preJson", preJsonHookFn);
161
- }
162
- static get postJson() {
163
- return _JsonRouter.getSharedHandlerProperty("postJson");
164
- }
165
- static set postJson(postJsonHookFn) {
166
- _JsonRouter.setSharedHandlerProperty("postJson", postJsonHookFn);
167
- }
168
- static get preError() {
169
- return _JsonRouter.getSharedHandlerProperty("preError");
170
- }
171
- static set preError(preErrorHookFn) {
172
- _JsonRouter.setSharedHandlerProperty("preError", preErrorHookFn);
173
- }
174
- static get postError() {
175
- return _JsonRouter.getSharedHandlerProperty("postError");
176
- }
177
- static set postError(postErrorHookFn) {
178
- _JsonRouter.setSharedHandlerProperty("postError", postErrorHookFn);
179
- }
180
- get original() {
181
- return this._router;
182
- }
183
- param(...args) {
184
- return this._router.param(...args);
185
- }
186
- use(...args) {
187
- return this._router.use(...args);
188
- }
189
- route(path) {
190
- const definition = {};
191
- for (const method of this.methods) {
192
- Object.defineProperty(definition, method, {
193
- value: (...callbacks) => {
194
- this[method](path, ...callbacks);
195
- return definition;
196
- },
197
- enumerable: false,
198
- writable: false,
199
- configurable: false
200
- });
201
- }
202
- return definition;
203
- }
204
- addEndpoint(method, path) {
205
- this.endpoints.push({
206
- method: method.toUpperCase(),
207
- path: this.normalizePath(path)
208
- });
209
- }
210
- getEndpoints() {
211
- return this.endpoints.map((endpoint) => ({ ...endpoint }));
212
- }
213
- normalizePath(path) {
214
- return addLeadingSlash(path);
21
+ var DEFAULT_RESPONSE_HANDLER = apiHandler;
22
+ var SUPPORTED_ROUTE_METHODS = Object.freeze([
23
+ "acl",
24
+ "all",
25
+ "bind",
26
+ "checkout",
27
+ "connect",
28
+ "copy",
29
+ "delete",
30
+ "get",
31
+ "head",
32
+ "link",
33
+ "lock",
34
+ "merge",
35
+ "mkactivity",
36
+ "mkcalendar",
37
+ "mkcol",
38
+ "move",
39
+ "m-search",
40
+ "notify",
41
+ "options",
42
+ "patch",
43
+ "post",
44
+ "propfind",
45
+ "proppatch",
46
+ "purge",
47
+ "put",
48
+ "query",
49
+ "rebind",
50
+ "report",
51
+ "search",
52
+ "source",
53
+ "subscribe",
54
+ "trace",
55
+ "unbind",
56
+ "unlink",
57
+ "unlock",
58
+ "unsubscribe"
59
+ ]);
60
+ var assertStringPath = (value, label) => {
61
+ if (typeof value !== "string") {
62
+ throw new TypeError(`JsonRouter ${label} must be a string path`);
63
+ }
64
+ };
65
+ var success = {
66
+ OK,
67
+ Created,
68
+ Accepted,
69
+ NonAuthoritativeInfo,
70
+ NoContent,
71
+ ResetContent,
72
+ PartialContent,
73
+ MultiStatus,
74
+ AlreadyReported,
75
+ IMUsed
76
+ };
77
+ var normalizeBasePath = (value) => {
78
+ assertStringPath(value, "basePath");
79
+ if (!value || value === "/") {
80
+ return "";
81
+ }
82
+ return addLeadingSlash(value).replace(/\/+$/, "");
83
+ };
84
+ var joinRoutePath = (basePath, path) => {
85
+ assertStringPath(path, "route path");
86
+ return `${basePath}${addLeadingSlash(path)}`;
87
+ };
88
+ var assertJsonRouterCallback = (handler) => {
89
+ if (typeof handler !== "function") {
90
+ throw new TypeError("middleware handler must be a function");
91
+ }
92
+ if (handler.length >= 4) {
93
+ throw new TypeError("route-local error middleware must be mounted with use()");
94
+ }
95
+ };
96
+ var flattenHandlerInputs = (handlers) => {
97
+ const flattened = [];
98
+ for (const handler of handlers) {
99
+ if (Array.isArray(handler)) {
100
+ flattened.push(...flattenHandlerInputs(handler));
101
+ continue;
102
+ }
103
+ assertJsonRouterCallback(handler);
104
+ flattened.push(handler);
105
+ }
106
+ return flattened;
107
+ };
108
+ var assertHasMiddleware = (handlers) => {
109
+ if (handlers.length === 0) {
110
+ throw new TypeError("at least one middleware handler is required");
111
+ }
112
+ };
113
+ var toMiddlewareList = (middlewares) => {
114
+ if (!middlewares) {
115
+ return [];
116
+ }
117
+ return flattenHandlerInputs(Array.isArray(middlewares) ? middlewares : [middlewares]);
118
+ };
119
+ var createResponseHandlerFromDefaults = (defaults) => {
120
+ const handler = createHandler();
121
+ handler.errorMessageProvider = defaults.errorMessageProvider;
122
+ handler.preJson = defaults.preJson;
123
+ handler.postJson = defaults.postJson;
124
+ handler.preError = defaults.preError;
125
+ handler.postError = defaults.postError;
126
+ return handler;
127
+ };
128
+ var JsonRouterBase = class _JsonRouterBase {
129
+ /**
130
+ * Creates a JSON router with a normalized base path, optional shared middleware,
131
+ * and a snapshot of the current static response-handler defaults.
132
+ */
133
+ constructor(basePath = "", middlewares, responseHandler = _JsonRouterBase.defaultHandler) {
134
+ this._methods = [];
135
+ this._endpoints = [];
136
+ this.basePath = normalizeBasePath(basePath);
137
+ this._middlewares = toMiddlewareList(middlewares);
138
+ this.responseHandler = responseHandler;
139
+ this._router = express.Router();
140
+ for (const method of SUPPORTED_ROUTE_METHODS) {
141
+ const routerMethod = this._router[method];
142
+ if (typeof routerMethod !== "function") {
143
+ throw new Error(`Express Router does not expose the supported ${method.toUpperCase()} route method`);
215
144
  }
145
+ this._methods.push(method);
146
+ Object.defineProperty(this, method, {
147
+ value: (path, ...callbacks) => {
148
+ const routeCallbacks = flattenHandlerInputs(callbacks);
149
+ assertHasMiddleware(routeCallbacks);
150
+ const fullPath = joinRoutePath(this.basePath, path);
151
+ const handlers = this.responseHandler.handleResponse([...this._middlewares, ...routeCallbacks]);
152
+ routerMethod.call(this._router, fullPath, handlers);
153
+ this.addEndpoint(method, fullPath);
154
+ return this;
155
+ },
156
+ enumerable: false,
157
+ writable: false,
158
+ configurable: false
159
+ });
160
+ }
161
+ }
162
+ static {
163
+ this.defaultHandlerDefaults = {
164
+ errorMessageProvider: DEFAULT_RESPONSE_HANDLER.errorMessageProvider,
165
+ preJson: DEFAULT_RESPONSE_HANDLER.preJson,
166
+ postJson: DEFAULT_RESPONSE_HANDLER.postJson,
167
+ preError: DEFAULT_RESPONSE_HANDLER.preError,
168
+ postError: DEFAULT_RESPONSE_HANDLER.postError
216
169
  };
217
- module.exports = JsonRouter;
218
170
  }
219
- });
220
- export default require_index();
171
+ static getSharedHandlerProperty(name) {
172
+ return _JsonRouterBase.defaultHandlerDefaults[name];
173
+ }
174
+ static setSharedHandlerProperty(name, value) {
175
+ _JsonRouterBase.defaultHandlerDefaults[name] = value;
176
+ }
177
+ static {
178
+ this.clientErrors = clientErrors;
179
+ }
180
+ static {
181
+ this.success = success;
182
+ }
183
+ static {
184
+ this.HttpResponse = HttpResponse;
185
+ }
186
+ static {
187
+ this.ErrorFormats = ErrorFormats;
188
+ }
189
+ static {
190
+ this.createHandler = createHandler;
191
+ }
192
+ static {
193
+ this.supportedMethods = SUPPORTED_ROUTE_METHODS;
194
+ }
195
+ /**
196
+ * Creates a fresh response handler from the current static defaults.
197
+ * Existing routers keep the handler instance captured during construction.
198
+ */
199
+ static get defaultHandler() {
200
+ return createResponseHandlerFromDefaults(_JsonRouterBase.defaultHandlerDefaults);
201
+ }
202
+ static get errorMessageProvider() {
203
+ return _JsonRouterBase.getSharedHandlerProperty("errorMessageProvider");
204
+ }
205
+ static set errorMessageProvider(customErrorMessageProvider) {
206
+ _JsonRouterBase.setSharedHandlerProperty("errorMessageProvider", customErrorMessageProvider);
207
+ }
208
+ static get preJson() {
209
+ return _JsonRouterBase.getSharedHandlerProperty("preJson");
210
+ }
211
+ static set preJson(preJsonHookFn) {
212
+ _JsonRouterBase.setSharedHandlerProperty("preJson", preJsonHookFn);
213
+ }
214
+ static get postJson() {
215
+ return _JsonRouterBase.getSharedHandlerProperty("postJson");
216
+ }
217
+ static set postJson(postJsonHookFn) {
218
+ _JsonRouterBase.setSharedHandlerProperty("postJson", postJsonHookFn);
219
+ }
220
+ static get preError() {
221
+ return _JsonRouterBase.getSharedHandlerProperty("preError");
222
+ }
223
+ static set preError(preErrorHookFn) {
224
+ _JsonRouterBase.setSharedHandlerProperty("preError", preErrorHookFn);
225
+ }
226
+ static get postError() {
227
+ return _JsonRouterBase.getSharedHandlerProperty("postError");
228
+ }
229
+ static set postError(postErrorHookFn) {
230
+ _JsonRouterBase.setSharedHandlerProperty("postError", postErrorHookFn);
231
+ }
232
+ /** Middleware callbacks captured during construction. */
233
+ get middlewares() {
234
+ return this._middlewares.slice();
235
+ }
236
+ /** Underlying Express router to mount with `app.use(router.original)`. */
237
+ get original() {
238
+ return this._router;
239
+ }
240
+ param(...args) {
241
+ return this._router.param(...args);
242
+ }
243
+ use(...args) {
244
+ return this._router.use(...args);
245
+ }
246
+ /**
247
+ * Starts a fluent route builder for one path.
248
+ * Registered handlers still pass through this router's response handler.
249
+ */
250
+ route(path) {
251
+ assertStringPath(path, "route path");
252
+ const definition = {};
253
+ for (const method of this._methods) {
254
+ Object.defineProperty(definition, method, {
255
+ value: (...callbacks) => {
256
+ this[method](path, ...callbacks);
257
+ return definition;
258
+ },
259
+ enumerable: false,
260
+ writable: false,
261
+ configurable: false
262
+ });
263
+ }
264
+ return definition;
265
+ }
266
+ addEndpoint(method, path) {
267
+ this._endpoints.push({
268
+ method: method.toUpperCase(),
269
+ path: this.normalizePath(path)
270
+ });
271
+ }
272
+ /** Returns a defensive copy of registered endpoint method/path metadata. */
273
+ getEndpoints() {
274
+ return this._endpoints.map((endpoint) => ({ ...endpoint }));
275
+ }
276
+ normalizePath(path) {
277
+ return addLeadingSlash(path);
278
+ }
279
+ };
280
+ var JsonRouter = JsonRouterBase;
281
+ var index_default = JsonRouter;
282
+ export {
283
+ index_default as default
284
+ };
package/llms.txt ADDED
@@ -0,0 +1,68 @@
1
+ # @web-ts-toolkit/express-json-router
2
+
3
+ Express router wrapper that sends route handler return values through `@web-ts-toolkit/express-response-handler` and records registered JSON-aware endpoints.
4
+
5
+ ## Main Patterns
6
+
7
+ ```ts
8
+ import express from 'express';
9
+ import JsonRouter from '@web-ts-toolkit/express-json-router';
10
+
11
+ const app = express();
12
+ const router = new JsonRouter('/api');
13
+
14
+ router.get('/health', () => ({ ok: true }));
15
+
16
+ router.get('/users/:id', (req) => {
17
+ throw new JsonRouter.clientErrors.NotFoundError(`User ${req.params.id} not found`);
18
+ });
19
+
20
+ app.use(router.original);
21
+ ```
22
+
23
+ Shared middleware and route builders:
24
+
25
+ ```ts
26
+ import type { RequestHandler } from 'express';
27
+ import JsonRouter from '@web-ts-toolkit/express-json-router';
28
+
29
+ const requireAuth: RequestHandler = (_req, _res, next) => next();
30
+ const router = new JsonRouter('/api', [requireAuth]);
31
+
32
+ router
33
+ .route('/documents/:id')
34
+ .propfind((req) => ({ id: req.params.id }))
35
+ .proppatch((req) => ({ id: req.params.id, updated: true }));
36
+ ```
37
+
38
+ Typed callback and isolated response handler:
39
+
40
+ ```ts
41
+ import JsonRouter, { type JsonRouterCallback } from '@web-ts-toolkit/express-json-router';
42
+
43
+ type Params = { id: string };
44
+
45
+ const readUser: JsonRouterCallback<Params> = (req) => ({ id: req.params.id });
46
+ const handler = JsonRouter.createHandler({ errorFormat: JsonRouter.ErrorFormats.rfc9457 });
47
+ const router = new JsonRouter('/admin', undefined, handler);
48
+
49
+ router.get('/users/:id', readUser);
50
+ ```
51
+
52
+ ## Gotchas
53
+
54
+ - canonical import is the default class: `import JsonRouter from '@web-ts-toolkit/express-json-router'`
55
+ - public type imports are available from the root: `JsonRouterCallback`, `JsonRouterEndpoint`, `JsonRouterHandlerInput`, `JsonRouterMethod`, `JsonRouterMiddlewares`, `JsonRouterRouteRegistrar`, `JsonRouteBuilder`
56
+ - route handlers may return plain values, promises, `JsonRouter.HttpResponse.*` wrappers, or throw `JsonRouter.clientErrors.*` / `@web-ts-toolkit/http-errors` errors
57
+ - `JsonRouter.supportedMethods` is the reviewed route-method contract; each method is available on the router and on `router.route(path)` builders
58
+ - `basePath`, route method paths, and `router.route(path)` intentionally accept string paths only; `RegExp` and path arrays are rejected before registration so `getEndpoints()` can keep `{ method, path: string }` metadata
59
+ - constructor middleware and `getEndpoints()` results are snapshots; mutating caller arrays or returned arrays does not change future route registration or endpoint metadata
60
+ - route-local Express error middleware with four arguments is rejected; mount error middleware with `router.use(...)`
61
+ - static defaults (`errorMessageProvider`, `preJson`, `postJson`, `preError`, `postError`) affect routers created after the change; existing routers keep their constructed response handler
62
+ - `JsonRouter.defaultHandler` returns a newly configured handler each time it is read; pass an explicit handler as the third constructor argument for isolated behavior
63
+ - Express is a direct runtime dependency because this package constructs `express.Router()` instances; `@types/express` is installed as a dependency for strict TypeScript consumers
64
+
65
+ ## Pointers
66
+
67
+ - README: installation, quickstart, main exports, supported methods, handler defaults
68
+ - website docs (not packed into the npm tarball): https://web-ts-toolkit.pages.dev/docs/packages/express-json-router
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@web-ts-toolkit/express-json-router",
3
3
  "description": "Express router wrapper for return-value JSON responses",
4
4
  "homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-json-router",
5
- "version": "0.32.0",
5
+ "version": "0.33.0",
6
6
  "sideEffects": false,
7
7
  "keywords": [
8
8
  "express",
@@ -16,19 +16,24 @@
16
16
  "types": "./index.d.ts",
17
17
  "exports": {
18
18
  ".": {
19
- "types": "./index.d.ts",
19
+ "types": {
20
+ "import": "./index.d.mts",
21
+ "require": "./index.d.ts",
22
+ "default": "./index.d.ts"
23
+ },
20
24
  "import": "./index.mjs",
21
25
  "require": "./index.js",
22
26
  "default": "./index.js"
23
27
  }
24
28
  },
25
29
  "engines": {
26
- "node": ">=20"
30
+ "node": ">=22"
27
31
  },
28
32
  "dependencies": {
29
- "@web-ts-toolkit/express-response-handler": "0.32.0",
30
- "@web-ts-toolkit/http-errors": "0.32.0",
31
- "@web-ts-toolkit/utils": "0.32.0",
33
+ "@web-ts-toolkit/express-response-handler": "0.33.0",
34
+ "@web-ts-toolkit/http-errors": "0.33.0",
35
+ "@web-ts-toolkit/utils": "0.33.0",
36
+ "@types/express": "^5.0.6",
32
37
  "express": "^5.2.1"
33
38
  },
34
39
  "files": [