express-fast-json-stringify 1.3.0 → 2.0.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.
@@ -1,6 +1,5 @@
1
- import type { NextFunction, Request, Response } from 'express';
2
- import { type Options } from 'fast-json-stringify';
3
- import { type OverrideErrorHandler } from './override';
1
+ import { type Options, type Schema } from 'fast-json-stringify';
2
+ import { type Application, type FastJsonOptions } from './install';
4
3
  /**
5
4
  * The parts of an OpenAPI 3.x or Swagger 2.0 document this package reads.
6
5
  *
@@ -16,35 +15,9 @@ export type OpenApiDocument = {
16
15
  readonly components?: Readonly<Record<string, unknown>>;
17
16
  readonly definitions?: Readonly<Record<string, unknown>>;
18
17
  };
19
- export type OpenApiOptions = Omit<Options, 'mode'> & {
18
+ export type OpenApiOptions = Omit<Options, 'mode'> & FastJsonOptions & {
20
19
  /** Media type to read the schema from. Defaults to `application/json`. */
21
20
  readonly contentType?: string;
22
- /** Pin the OpenAPI path instead of deriving it from the matched route. */
23
- readonly path?: string;
24
- /** Pin the OpenAPI method instead of using the request method. */
25
- readonly method?: string;
26
- /**
27
- * Throw when the document describes no schema for a response, instead of
28
- * quietly falling back to `res.json()`. Only applies to explicit
29
- * `res.fastJson()` calls — an overridden `res.json()` always falls back.
30
- */
31
- readonly strict?: boolean;
32
- /**
33
- * Also route `res.json()` — and therefore `res.send(object)`, which Express
34
- * implements on top of it — through the compiled serializer whenever the
35
- * document describes the response.
36
- *
37
- * Off by default. Turning it on lets an existing codebase benefit without
38
- * rewriting a single call site: responses the document covers are serialized
39
- * from the schema, everything else keeps the stock behavior.
40
- */
41
- readonly overrideJson?: boolean;
42
- /**
43
- * Called when an overridden `res.json()` could not use the fast path because
44
- * the serializer threw. The response falls back to the stock `res.json()`
45
- * either way; this is only so the mismatch is visible.
46
- */
47
- readonly onError?: OverrideErrorHandler;
48
21
  };
49
22
  /**
50
23
  * Translate an Express route pattern into the OpenAPI equivalent:
@@ -53,14 +26,35 @@ export type OpenApiOptions = Omit<Options, 'mode'> & {
53
26
  */
54
27
  export declare const toOpenApiPath: (path: string) => string;
55
28
  /**
56
- * Build a stringify middleware that takes its schemas from an OpenAPI or Swagger
57
- * document, so the contract you already publish is the one used to serialize.
29
+ * The schema the document declares for one operation and status, with the
30
+ * shared schemas attached so `$ref` resolves, ready for `fastJsonSchema`. For
31
+ * a route whose Express path does not match the document.
58
32
  *
59
- * The operation is resolved per request from the matched Express route, and the
60
- * schema from the response status code, which means `res.status(201).fastJson()`
33
+ * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
34
+ * @param {string} path The OpenAPI path, `/users/{id}`
35
+ * @param {string} method The operation, `get`
36
+ * @param {number} status The response status, `200` when omitted
37
+ * @param {string} contentType The media type, `application/json` when omitted
38
+ * @returns {Schema | undefined} undefined when the document describes no such response
39
+ *
40
+ * Examples:
41
+ * ```ts
42
+ * app.get('/v2/people/:id', fastJsonSchema(openApiSchema(document, '/users/{id}', 'get')!), handler);
43
+ * ```
44
+ */
45
+ export declare const openApiSchema: (document: OpenApiDocument, path: string, method: string, status?: number, contentType?: string) => Schema | undefined;
46
+ /**
47
+ * Serialize every documented response of an application from its OpenAPI or
48
+ * Swagger document, so the contract you already publish is the one used to
49
+ * serialize. Once per app, at setup: it calls `installFastJson(app)` itself, and no
50
+ * middleware runs per request.
51
+ *
52
+ * The operation is resolved from the matched Express route, and the schema
53
+ * from the response status code, which means `res.status(201).fastJson()`
61
54
  * serializes with the `201` schema. Routes the document does not describe fall
62
55
  * back to `res.json()` unless `strict` is set.
63
56
  *
57
+ * @param {Application} app The application
64
58
  * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
65
59
  * @param {OpenApiOptions} options The options to use (optional)
66
60
  *
@@ -73,7 +67,7 @@ export declare const toOpenApiPath: (path: string) => string;
73
67
  * const app = express();
74
68
  * const document = swaggerJsdoc({ definition: { openapi: '3.1.0', info: { title: 'API', version: '1.0.0' } }, apis: ['./routes/*.ts'] });
75
69
  *
76
- * app.use(fastJsonOpenApi(document));
70
+ * fastJsonOpenApi(app, document);
77
71
  *
78
72
  * app.get('/users/:id', (req, res, next) => {
79
73
  * try {
@@ -84,4 +78,4 @@ export declare const toOpenApiPath: (path: string) => string;
84
78
  * });
85
79
  * ```
86
80
  */
87
- export declare const fastJsonOpenApi: (document: OpenApiDocument, options?: OpenApiOptions) => (req: Request, res: Response, next: NextFunction) => void;
81
+ export declare const fastJsonOpenApi: (app: Application, document: OpenApiDocument, options?: OpenApiOptions) => void;
@@ -14,10 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.fastJsonOpenApi = exports.toOpenApiPath = void 0;
17
+ exports.fastJsonOpenApi = exports.openApiSchema = exports.toOpenApiPath = void 0;
18
18
  const fast_json_stringify_1 = __importDefault(require("fast-json-stringify"));
19
- const override_1 = require("./override");
20
- const send_1 = require("./send");
19
+ const install_1 = require("./install");
21
20
  /**
22
21
  * Translate an Express route pattern into the OpenAPI equivalent:
23
22
  * `/users/:id` becomes `/users/{id}`. Express parameter modifiers — a trailing
@@ -81,15 +80,46 @@ const withSharedSchemas = (document, schema) => {
81
80
  }
82
81
  return result;
83
82
  };
83
+ const validateDocument = (document) => {
84
+ if (!document || typeof document !== 'object' || typeof document.paths !== 'object' || document.paths === null) {
85
+ throw new TypeError(`express-fast-json-stringify: invalid OpenAPI document`);
86
+ }
87
+ };
84
88
  /**
85
- * Build a stringify middleware that takes its schemas from an OpenAPI or Swagger
86
- * document, so the contract you already publish is the one used to serialize.
89
+ * The schema the document declares for one operation and status, with the
90
+ * shared schemas attached so `$ref` resolves, ready for `fastJsonSchema`. For
91
+ * a route whose Express path does not match the document.
92
+ *
93
+ * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
94
+ * @param {string} path The OpenAPI path, `/users/{id}`
95
+ * @param {string} method The operation, `get`
96
+ * @param {number} status The response status, `200` when omitted
97
+ * @param {string} contentType The media type, `application/json` when omitted
98
+ * @returns {Schema | undefined} undefined when the document describes no such response
87
99
  *
88
- * The operation is resolved per request from the matched Express route, and the
89
- * schema from the response status code, which means `res.status(201).fastJson()`
100
+ * Examples:
101
+ * ```ts
102
+ * app.get('/v2/people/:id', fastJsonSchema(openApiSchema(document, '/users/{id}', 'get')!), handler);
103
+ * ```
104
+ */
105
+ const openApiSchema = (document, path, method, status = 200, contentType = 'application/json') => {
106
+ validateDocument(document);
107
+ const schema = findResponseSchema(document, path, method.toLowerCase(), status, contentType);
108
+ return schema ? withSharedSchemas(document, schema) : undefined;
109
+ };
110
+ exports.openApiSchema = openApiSchema;
111
+ /**
112
+ * Serialize every documented response of an application from its OpenAPI or
113
+ * Swagger document, so the contract you already publish is the one used to
114
+ * serialize. Once per app, at setup: it calls `installFastJson(app)` itself, and no
115
+ * middleware runs per request.
116
+ *
117
+ * The operation is resolved from the matched Express route, and the schema
118
+ * from the response status code, which means `res.status(201).fastJson()`
90
119
  * serializes with the `201` schema. Routes the document does not describe fall
91
120
  * back to `res.json()` unless `strict` is set.
92
121
  *
122
+ * @param {Application} app The application
93
123
  * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
94
124
  * @param {OpenApiOptions} options The options to use (optional)
95
125
  *
@@ -102,7 +132,7 @@ const withSharedSchemas = (document, schema) => {
102
132
  * const app = express();
103
133
  * const document = swaggerJsdoc({ definition: { openapi: '3.1.0', info: { title: 'API', version: '1.0.0' } }, apis: ['./routes/*.ts'] });
104
134
  *
105
- * app.use(fastJsonOpenApi(document));
135
+ * fastJsonOpenApi(app, document);
106
136
  *
107
137
  * app.get('/users/:id', (req, res, next) => {
108
138
  * try {
@@ -113,54 +143,53 @@ const withSharedSchemas = (document, schema) => {
113
143
  * });
114
144
  * ```
115
145
  */
116
- const fastJsonOpenApi = (document, options = {}) => {
117
- if (!document || typeof document !== 'object' || typeof document.paths !== 'object' || document.paths === null) {
118
- throw new TypeError(`express-fast-json-stringify: invalid OpenAPI document`);
119
- }
120
- const { contentType = 'application/json', path: pinnedPath, method: pinnedMethod, strict = false, overrideJson = false, onError } = options, fastJsonOptions = __rest(options, ["contentType", "path", "method", "strict", "overrideJson", "onError"]);
121
- // One compiled serializer per operation and status code. Misses are cached as
122
- // `null` so an undocumented route costs a single lookup.
123
- const serializers = new Map();
124
- const routePath = (req) => { var _a, _b; return pinnedPath !== null && pinnedPath !== void 0 ? pinnedPath : (0, exports.toOpenApiPath)(`${req.baseUrl}${(_b = (_a = req.route) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : req.path}`); };
125
- const serializerFor = (req, status) => {
126
- const path = routePath(req);
127
- const method = (pinnedMethod !== null && pinnedMethod !== void 0 ? pinnedMethod : req.method).toLowerCase();
128
- const key = `${method} ${path} ${status}`;
129
- const cached = serializers.get(key);
130
- if (cached !== undefined) {
131
- return cached;
132
- }
146
+ const fastJsonOpenApi = (app, document, options = {}) => {
147
+ validateDocument(document);
148
+ const { contentType = 'application/json', overrideJson, onError, strict } = options, fastJsonOptions = __rest(options, ["contentType", "overrideJson", "onError", "strict"]);
149
+ (0, install_1.installFastJson)(app, { overrideJson, onError, strict });
150
+ const compile = (path, method, status) => {
133
151
  const schema = findResponseSchema(document, path, method, status, contentType);
134
- const serializer = schema ? (0, fast_json_stringify_1.default)(withSharedSchemas(document, schema), fastJsonOptions) : null;
135
- serializers.set(key, serializer);
136
- return serializer;
152
+ return schema ? (0, fast_json_stringify_1.default)(withSharedSchemas(document, schema), fastJsonOptions) : null;
137
153
  };
138
- return (req, res, next) => {
139
- /**
140
- * Send JSON response, serialized with the schema the document declares for
141
- * this route and status code.
142
- *
143
- * Examples:
144
- * ```ts
145
- * res.fastJson({ user: 'Simone Nigro' });
146
- * res.status(201).fastJson({ user: 'Simone Nigro' });
147
- * ```
148
- */
149
- res.fastJson = (body) => {
150
- const serializer = serializerFor(req, res.statusCode);
151
- if (!serializer) {
152
- if (strict) {
153
- throw new Error(`express-fast-json-stringify: no ${contentType} schema for ${req.method} ${routePath(req)} with status ${res.statusCode}`);
154
- }
155
- return res.json(body);
154
+ // One compiled serializer per route, mount and status, found from the route object the
155
+ // framework already matched: no path is rebuilt per request. Misses are cached as `null`.
156
+ // A router mounted twice answers under two mounts, so the mount is a key of its own.
157
+ const byRoute = new WeakMap();
158
+ // Answered from plain middleware, before any route matched: the request path is what names
159
+ // the operation, and a string key is all there is.
160
+ const byPath = new Map();
161
+ (0, install_1.setResolver)(app, (res) => {
162
+ const req = res.req;
163
+ const status = res.statusCode;
164
+ const method = req.method.toLowerCase();
165
+ const route = req.route;
166
+ if (route === undefined) {
167
+ const key = `${method} ${req.baseUrl}${req.path} ${status}`;
168
+ let serialize = byPath.get(key);
169
+ if (serialize === undefined) {
170
+ serialize = compile((0, exports.toOpenApiPath)(`${req.baseUrl}${req.path}`), method, status);
171
+ byPath.set(key, serialize);
156
172
  }
157
- return (0, send_1.sendJson)(req, res, serializer(body));
158
- };
159
- if (overrideJson) {
160
- (0, override_1.overrideResJson)(req, res, (status) => serializerFor(req, status), onError);
173
+ return serialize;
161
174
  }
162
- next();
163
- };
175
+ let byMount = byRoute.get(route);
176
+ if (byMount === undefined) {
177
+ byMount = new Map();
178
+ byRoute.set(route, byMount);
179
+ }
180
+ const baseUrl = req.baseUrl;
181
+ let byStatus = byMount.get(baseUrl);
182
+ if (byStatus === undefined) {
183
+ byStatus = new Map();
184
+ byMount.set(baseUrl, byStatus);
185
+ }
186
+ let serialize = byStatus.get(status);
187
+ if (serialize === undefined) {
188
+ serialize = compile((0, exports.toOpenApiPath)(`${baseUrl}${route.path}`), method, status);
189
+ byStatus.set(status, serialize);
190
+ }
191
+ return serialize;
192
+ });
164
193
  };
165
194
  exports.fastJsonOpenApi = fastJsonOpenApi;
166
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvb3BlbmFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLDhFQUEwRTtBQUUxRSx5Q0FBd0U7QUFDeEUsaUNBQWtDO0FBaURsQzs7OztHQUlHO0FBQ0ksTUFBTSxhQUFhLEdBQUcsQ0FBQyxJQUFZLEVBQVUsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsa0NBQWtDLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFBbkcsUUFBQSxhQUFhLGlCQUFzRjtBQUVoSDs7O0dBR0c7QUFDSCxNQUFNLGdCQUFnQixHQUFHLENBQUMsTUFBYyxFQUFxQixFQUFFLENBQUMsQ0FBQyxNQUFNLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0FBRWpIOzs7R0FHRztBQUNILE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxNQUFjLEVBQXFCLEVBQUU7SUFDN0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDLENBQUM7SUFDdkMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxHQUFHLEtBQUssSUFBSSxFQUFFLEdBQUcsS0FBSyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUM7QUFDakUsQ0FBQyxDQUFDO0FBUUYsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFFBQXlCLEVBQUUsSUFBWSxFQUFFLE1BQWMsRUFBRSxNQUFjLEVBQUUsV0FBbUIsRUFBVyxFQUFFOztJQUNuSSxNQUFNLFVBQVUsR0FBRyxNQUFBLFFBQVEsQ0FBQyxLQUFLLDBDQUFHLElBQUksQ0FBa0QsQ0FBQztJQUMzRixJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDaEIsT0FBTyxTQUFTLENBQUM7SUFDbkIs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
195
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvb3BlbmFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLDhFQUFtRjtBQUVuRix1Q0FBa0g7QUF3QmxIOzs7O0dBSUc7QUFDSSxNQUFNLGFBQWEsR0FBRyxDQUFDLElBQVksRUFBVSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxrQ0FBa0MsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUFuRyxRQUFBLGFBQWEsaUJBQXNGO0FBRWhIOzs7R0FHRztBQUNILE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxNQUFjLEVBQXFCLEVBQUUsQ0FBQyxDQUFDLE1BQU0sS0FBSyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFakg7OztHQUdHO0FBQ0gsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE1BQWMsRUFBcUIsRUFBRTtJQUM3RCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQztJQUN2QyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEdBQUcsS0FBSyxJQUFJLEVBQUUsR0FBRyxLQUFLLElBQUksRUFBRSxTQUFTLENBQUMsQ0FBQztBQUNqRSxDQUFDLENBQUM7QUFRRixNQUFNLGtCQUFrQixHQUFHLENBQUMsUUFBeUIsRUFBRSxJQUFZLEVBQUUsTUFBYyxFQUFFLE1BQWMsRUFBRSxXQUFtQixFQUFXLEVBQUU7O0lBQ25JLE1BQU0sVUFBVSxHQUFHLE1BQUEsUUFBUSxDQUFDLEtBQUssMENBQUcsSUFBSSxDQUFrRCxDQUFDO0lBQzNGLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNoQixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsS0FB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
@@ -1,10 +1,9 @@
1
- import type { Request, Response } from 'express';
1
+ import type { Response } from 'express';
2
2
  /**
3
- * Write an already serialized JSON payload with the same HTTP semantics as
4
- * `res.json()`: charset, `Content-Length`, `ETag`, conditional requests and the
5
- * empty body rules for `204`/`304`.
6
- *
7
- * Every middleware in this package goes through here, so they cannot drift
8
- * apart from each other or from Express.
3
+ * Write an already serialized JSON payload the way `res.json()` does once the
4
+ * body is a string: the type when the route set none, then `res.send()`, which
5
+ * owns `Content-Length`, the `ETag`, conditional requests, `204`/`304` and
6
+ * `HEAD` in Express and in the frameworks that stand in for it. Nothing is
7
+ * reimplemented here, so nothing can drift.
9
8
  */
10
- export declare const sendJson: (req: Request, res: Response, serialized: string) => Response;
9
+ export declare const sendSerialized: (res: Response, json: string) => Response;
@@ -1,46 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendJson = void 0;
3
+ exports.sendSerialized = void 0;
4
4
  /**
5
- * Write an already serialized JSON payload with the same HTTP semantics as
6
- * `res.json()`: charset, `Content-Length`, `ETag`, conditional requests and the
7
- * empty body rules for `204`/`304`.
8
- *
9
- * Every middleware in this package goes through here, so they cannot drift
10
- * apart from each other or from Express.
5
+ * Write an already serialized JSON payload the way `res.json()` does once the
6
+ * body is a string: the type when the route set none, then `res.send()`, which
7
+ * owns `Content-Length`, the `ETag`, conditional requests, `204`/`304` and
8
+ * `HEAD` in Express and in the frameworks that stand in for it. Nothing is
9
+ * reimplemented here, so nothing can drift.
11
10
  */
12
- const sendJson = (req, res, serialized) => {
13
- var _a;
14
- let payload = serialized;
15
- // Do not clobber a content type the route set on purpose (eg. res.type('application/vnd.api+json')).
16
- if (!res.getHeader('Content-Type')) {
17
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
11
+ const sendSerialized = (res, json) => {
12
+ if (!res.get('Content-Type')) {
13
+ res.set('Content-Type', 'application/json; charset=utf-8');
18
14
  }
19
- // Mirror res.send(): honour the app `etag` setting so that swapping
20
- // res.json() for res.fastJson() keeps conditional requests working.
21
- const etagFn = (_a = res.app) === null || _a === void 0 ? void 0 : _a.get('etag fn');
22
- if (typeof etagFn === 'function' && !res.getHeader('ETag')) {
23
- const etag = etagFn(payload, 'utf-8');
24
- if (etag) {
25
- res.setHeader('ETag', etag);
26
- }
27
- }
28
- if (req.fresh) {
29
- res.statusCode = 304;
30
- }
31
- // 204 No Content and 304 Not Modified must not carry a body, nor describe one.
32
- if (res.statusCode === 204 || res.statusCode === 304) {
33
- res.removeHeader('Content-Type');
34
- res.removeHeader('Content-Length');
35
- res.removeHeader('Transfer-Encoding');
36
- payload = '';
37
- }
38
- else {
39
- // Without this the response falls back to chunked encoding, and HEAD
40
- // requests answer with no length at all.
41
- res.setHeader('Content-Length', Buffer.byteLength(payload));
42
- }
43
- return res.end(payload);
15
+ return res.send(json);
44
16
  };
45
- exports.sendJson = sendJson;
46
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvc2VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFFQTs7Ozs7OztHQU9HO0FBQ0ksTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFZLEVBQUUsR0FBYSxFQUFFLFVBQWtCLEVBQVksRUFBRTs7SUFDcEYsSUFBSSxPQUFPLEdBQUcsVUFBVSxDQUFDO0lBRXpCLHFHQUFxRztJQUNyRyxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUFDO1FBQ25DLEdBQUcsQ0FBQyxTQUFTLENBQUMsY0FBYyxFQUFFLGlDQUFpQyxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVELG9FQUFvRTtJQUNwRSxvRUFBb0U7SUFDcEUsTUFBTSxNQUFNLEdBQUcsTUFBQSxHQUFHLENBQUMsR0FBRywwQ0FBRSxHQUFHLENBQUMsU0FBUyxDQUE0RSxDQUFDO0lBQ2xILElBQUksT0FBTyxNQUFNLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQzNELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUNULEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzlCLENBQUM7SUFDSCxDQUFDO0lBRUQsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDZCxHQUFHLENBQUMsVUFBVSxHQUFHLEdBQUcsQ0FBQztJQUN2QixDQUFDO0lBRUQsK0VBQStFO0lBQy9FLElBQUksR0FBRyxDQUFDLFVBQVUsS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLFVBQVUsS0FBSyxHQUFHLEVBQUUsQ0FBQztRQUNyRCxHQUFHLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ2pDLEdBQUcsQ0FBQyxZQUFZLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUNuQyxHQUFHLENBQUMsWUFBWSxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDdEMsT0FBTyxHQUFHLEVBQUUsQ0FBQztJQUNmLENBQUM7U0FBTSxDQUFDO1FBQ04scUVBQXFFO1FBQ3JFLHlDQUF5QztRQUN6QyxHQUFHLENBQUMsU0FBUyxDQUFDLGdCQUFnQixFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUM5RCxDQUFDO0lBRUQsT0FBTyxHQUFHLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQzFCLENBQUMsQ0FBQztBQW5DVyxRQUFBLFFBQVEsWUFtQ25CIn0=
17
+ exports.sendSerialized = sendSerialized;
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvc2VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFFQTs7Ozs7O0dBTUc7QUFDSSxNQUFNLGNBQWMsR0FBRyxDQUFDLEdBQWEsRUFBRSxJQUFZLEVBQVksRUFBRTtJQUN0RSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUFDO1FBQzdCLEdBQUcsQ0FBQyxHQUFHLENBQUMsY0FBYyxFQUFFLGlDQUFpQyxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUN4QixDQUFDLENBQUM7QUFMVyxRQUFBLGNBQWMsa0JBS3pCIn0=
@@ -1,3 +1,3 @@
1
+ export { type Application, installFastJson, type FastJsonOptions, type OverrideErrorHandler } from './lib/install';
1
2
  export * from './lib/middleware';
2
3
  export * from './lib/openapi';
3
- export type { OverrideErrorHandler } from './lib/override';
@@ -1,3 +1,4 @@
1
+ export { installFastJson } from './lib/install';
1
2
  export * from './lib/middleware';
2
3
  export * from './lib/openapi';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLGVBQWUsQ0FBQyJ9
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFvQixlQUFlLEVBQW1ELE1BQU0sZUFBZSxDQUFDO0FBQ25ILGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxlQUFlLENBQUMifQ==
@@ -0,0 +1,77 @@
1
+ import type { Express, Request, Response } from 'express';
2
+ /** What the install needs of an application: the prototype its responses share. Fulmine has one too. */
3
+ export type Application = Pick<Express, 'response'>;
4
+ /** A compiled serializer, as fast-json-stringify builds it. */
5
+ export type Serializer = (body: any) => string;
6
+ /** Finds the serializer for a response from something other than the route, an OpenAPI document. */
7
+ export type SerializerResolver = (res: Response) => Serializer | null;
8
+ /** Notified when an overridden `res.json()` could not use the fast path. */
9
+ export type OverrideErrorHandler = (error: unknown, req: Request) => void;
10
+ export type FastJsonOptions = {
11
+ /**
12
+ * Also route `res.json()`, and `res.send(object)` which Express implements on
13
+ * top of it, through the serializer in force.
14
+ *
15
+ * Off by default. A route's own schema describes the successful payload, so
16
+ * under it only `2xx` responses take the fast path: an error body would
17
+ * otherwise be rewritten into the wrong shape.
18
+ */
19
+ readonly overrideJson?: boolean;
20
+ /**
21
+ * Called when an overridden `res.json()` could not use the fast path because
22
+ * the serializer threw. The response falls back to the stock `res.json()`
23
+ * either way; this is only so the mismatch is visible.
24
+ */
25
+ readonly onError?: OverrideErrorHandler;
26
+ /**
27
+ * Make `res.fastJson()` throw when no schema is known for the response,
28
+ * instead of quietly falling back to `res.json()`. An overridden `res.json()`
29
+ * always falls back.
30
+ */
31
+ readonly strict?: boolean;
32
+ };
33
+ /** Where `fastJsonSchema` leaves the serializer of the route, on `res.locals`. */
34
+ export declare const kSerializer: unique symbol;
35
+ /**
36
+ * Give an application `res.fastJson()`, and with `overrideJson` a `res.json()`
37
+ * that serializes through the schema in force. Once per app, at setup: the
38
+ * methods go on `app.response`, so a request pays nothing to have them.
39
+ *
40
+ * `fastJsonSchema` chooses the schema per route, `fastJsonOpenApi` per
41
+ * operation from a document and calls this itself.
42
+ *
43
+ * @param {Application} app The application to extend
44
+ * @param {FastJsonOptions} options The options to use (optional)
45
+ *
46
+ * Examples:
47
+ * ```ts
48
+ * import express from 'express';
49
+ * import { installFastJson, fastJsonSchema } from 'express-fast-json-stringify';
50
+ *
51
+ * const app = express();
52
+ * installFastJson(app);
53
+ *
54
+ * app.get('/', fastJsonSchema(schema), (req, res) => {
55
+ * res.fastJson({ firstName: 'Simone', lastName: 'Nigro', age: 40 });
56
+ * });
57
+ * ```
58
+ */
59
+ export declare const installFastJson: (app: Application, options?: FastJsonOptions) => void;
60
+ /** Registers where the document based serializers come from, see fastJsonOpenApi. */
61
+ export declare const setResolver: (app: Application, resolver: SerializerResolver) => void;
62
+ declare global {
63
+ namespace Express {
64
+ interface Response {
65
+ /**
66
+ * Send JSON response, serialized with the schema in force for the route.
67
+ *
68
+ * Examples:
69
+ * ```ts
70
+ * res.fastJson({ user: 'Simone Nigro' });
71
+ * res.status(200).fastJson({ user: 'Simone Nigro' });
72
+ * ```
73
+ */
74
+ fastJson: (body: any) => Response;
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,92 @@
1
+ import { sendSerialized } from './send';
2
+ /** Where `fastJsonSchema` leaves the serializer of the route, on `res.locals`. */
3
+ export const kSerializer = Symbol('express-fast-json-stringify');
4
+ // one state per response prototype, which is one per app
5
+ const states = new WeakMap();
6
+ const routeOf = (req) => `${req.baseUrl}${req.route?.path ?? req.path}`;
7
+ // the route's own serializer first, then what the document says
8
+ const pick = (state, res, successOnly) => {
9
+ const own = res.locals[kSerializer];
10
+ if (own !== undefined && (!successOnly || (res.statusCode >= 200 && res.statusCode < 300))) {
11
+ return own;
12
+ }
13
+ return state.resolver === null ? null : state.resolver(res);
14
+ };
15
+ const fastJsonMethod = (state) => function installFastJson(body) {
16
+ const serialize = pick(state, this, false);
17
+ if (serialize === null) {
18
+ if (state.strict) {
19
+ throw new Error(`express-fast-json-stringify: no schema for ${this.req.method} ${routeOf(this.req)}`);
20
+ }
21
+ return state.stockJson.call(this, body);
22
+ }
23
+ return sendSerialized(this, serialize(body));
24
+ };
25
+ // The stock res.json() runs whenever the fast path does not apply: a setting that changes the
26
+ // bytes (json replacer, spaces, escape), no schema for this response, or a body the schema refuses
27
+ const jsonMethod = (state) => function json(body) {
28
+ const app = this.app;
29
+ if (!app.get('json replacer') && !app.get('json spaces') && !app.get('json escape')) {
30
+ const serialize = pick(state, this, true);
31
+ if (serialize !== null) {
32
+ let out;
33
+ try {
34
+ out = serialize(body);
35
+ }
36
+ catch (error) {
37
+ state.onError?.(error, this.req);
38
+ }
39
+ if (out !== undefined) {
40
+ return sendSerialized(this, out);
41
+ }
42
+ }
43
+ }
44
+ return state.stockJson.call(this, body);
45
+ };
46
+ /**
47
+ * Give an application `res.fastJson()`, and with `overrideJson` a `res.json()`
48
+ * that serializes through the schema in force. Once per app, at setup: the
49
+ * methods go on `app.response`, so a request pays nothing to have them.
50
+ *
51
+ * `fastJsonSchema` chooses the schema per route, `fastJsonOpenApi` per
52
+ * operation from a document and calls this itself.
53
+ *
54
+ * @param {Application} app The application to extend
55
+ * @param {FastJsonOptions} options The options to use (optional)
56
+ *
57
+ * Examples:
58
+ * ```ts
59
+ * import express from 'express';
60
+ * import { installFastJson, fastJsonSchema } from 'express-fast-json-stringify';
61
+ *
62
+ * const app = express();
63
+ * installFastJson(app);
64
+ *
65
+ * app.get('/', fastJsonSchema(schema), (req, res) => {
66
+ * res.fastJson({ firstName: 'Simone', lastName: 'Nigro', age: 40 });
67
+ * });
68
+ * ```
69
+ */
70
+ export const installFastJson = (app, options = {}) => {
71
+ const proto = app?.response;
72
+ if (!proto || typeof proto.json !== 'function') {
73
+ throw new TypeError('express-fast-json-stringify: an Express application is required');
74
+ }
75
+ let state = states.get(proto);
76
+ if (state === undefined) {
77
+ state = { stockJson: proto.json, resolver: null, overrideJson: false, onError: undefined, strict: false };
78
+ states.set(proto, state);
79
+ proto.fastJson = fastJsonMethod(state);
80
+ }
81
+ state.overrideJson = options.overrideJson === true;
82
+ state.onError = options.onError;
83
+ state.strict = options.strict === true;
84
+ // the stock method is put back when the override is off, so nothing runs on res.json() that
85
+ // was not there before, and a framework reading the prototype sees the method it knows
86
+ proto.json = state.overrideJson ? jsonMethod(state) : state.stockJson;
87
+ };
88
+ /** Registers where the document based serializers come from, see fastJsonOpenApi. */
89
+ export const setResolver = (app, resolver) => {
90
+ states.get(app.response).resolver = resolver;
91
+ };
92
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFsbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvaW5zdGFsbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBc0N4QyxrRkFBa0Y7QUFDbEYsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFrQixNQUFNLENBQUMsNkJBQTZCLENBQUMsQ0FBQztBQVloRix5REFBeUQ7QUFDekQsTUFBTSxNQUFNLEdBQUcsSUFBSSxPQUFPLEVBQWlCLENBQUM7QUFFNUMsTUFBTSxPQUFPLEdBQUcsQ0FBQyxHQUFZLEVBQVUsRUFBRSxDQUFDLEdBQUcsR0FBRyxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLElBQUksSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7QUFFekYsZ0VBQWdFO0FBQ2hFLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBWSxFQUFFLEdBQWEsRUFBRSxXQUFvQixFQUFxQixFQUFFO0lBQ3BGLE1BQU0sR0FBRyxHQUFJLEdBQUcsQ0FBQyxNQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ2hELElBQUksR0FBRyxLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsV0FBVyxJQUFJLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDLFVBQVUsR0FBRyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDM0YsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUMsUUFBUSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQzlELENBQUMsQ0FBQztBQUVGLE1BQU0sY0FBYyxHQUFHLENBQUMsS0FBWSxFQUFFLEVBQUUsQ0FDdEMsU0FBUyxlQUFlLENBQWlCLElBQVM7SUFDaEQsTUFB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
@@ -1,36 +1,22 @@
1
1
  import type { NextFunction, Request, Response } from 'express';
2
2
  import { type Options, type Schema } from 'fast-json-stringify';
3
- import { type OverrideErrorHandler } from './override';
4
3
  export type { Schema, Options } from 'fast-json-stringify';
5
- export type FastJsonSchemaOptions = Omit<Options, 'mode'> & {
6
- /**
7
- * Also route `res.json()` — and therefore `res.send(object)`, which Express
8
- * implements on top of it — through the compiled serializer.
9
- *
10
- * Off by default. Because a single schema describes the successful payload,
11
- * only `2xx` responses take the fast path: an error body would otherwise be
12
- * rewritten into the shape of the success schema.
13
- */
14
- readonly overrideJson?: boolean;
15
- /**
16
- * Called when an overridden `res.json()` could not use the fast path because
17
- * the serializer threw. The response falls back to the stock `res.json()`
18
- * either way; this is only so the mismatch is visible.
19
- */
20
- readonly onError?: OverrideErrorHandler;
21
- };
4
+ export type FastJsonSchemaOptions = Omit<Options, 'mode'>;
22
5
  /**
23
- * Build a stringify function using a schema of the documents that should be stringified
6
+ * Build a middleware that gives its route a serializer compiled from the schema.
7
+ * The application needs `installFastJson(app)` once for `res.fastJson()` to exist.
8
+ *
24
9
  * @param {Schema} schema The schema used to stringify values
25
- * @param {Options} options The options to use (optional)
10
+ * @param {FastJsonSchemaOptions} options The fast-json-stringify options (optional)
26
11
  * @see https://www.npmjs.com/package/fast-json-stringify
27
12
  *
28
13
  * Examples:
29
14
  * ```ts
30
15
  * import express from 'express';
31
- * import { fastJsonSchema, Schema } from 'express-fast-json-stringify';
16
+ * import { installFastJson, fastJsonSchema, Schema } from 'express-fast-json-stringify';
32
17
  *
33
18
  * const app = express();
19
+ * installFastJson(app);
34
20
  *
35
21
  * const schema: Schema = {
36
22
  * title: 'Example Schema',
@@ -62,20 +48,4 @@ export type FastJsonSchemaOptions = Omit<Options, 'mode'> & {
62
48
  * });
63
49
  * ```
64
50
  */
65
- export declare const fastJsonSchema: (schema: Schema, options?: FastJsonSchemaOptions) => (req: Request, res: Response, next: NextFunction) => void;
66
- declare global {
67
- namespace Express {
68
- interface Response {
69
- /**
70
- * Send JSON response.
71
- *
72
- * Examples:
73
- * ```ts
74
- * res.fastJson({ user: 'Simone Nigro' });
75
- * res.status(200).fastJson({ user: 'Simone Nigro' });
76
- * ```
77
- */
78
- fastJson: (body: any) => Response;
79
- }
80
- }
81
- }
51
+ export declare const fastJsonSchema: (schema: Schema, options?: FastJsonSchemaOptions) => (_req: Request, res: Response, next: NextFunction) => void;