express-fast-json-stringify 1.2.9 → 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.
@@ -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,18 +1,22 @@
1
1
  import type { NextFunction, Request, Response } from 'express';
2
2
  import { type Options, type Schema } from 'fast-json-stringify';
3
3
  export type { Schema, Options } from 'fast-json-stringify';
4
+ export type FastJsonSchemaOptions = Omit<Options, 'mode'>;
4
5
  /**
5
- * 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
+ *
6
9
  * @param {Schema} schema The schema used to stringify values
7
- * @param {Options} options The options to use (optional)
10
+ * @param {FastJsonSchemaOptions} options The fast-json-stringify options (optional)
8
11
  * @see https://www.npmjs.com/package/fast-json-stringify
9
12
  *
10
13
  * Examples:
11
14
  * ```ts
12
15
  * import express from 'express';
13
- * import { fastJsonSchema, Schema } from 'express-fast-json-stringify';
16
+ * import { installFastJson, fastJsonSchema, Schema } from 'express-fast-json-stringify';
14
17
  *
15
18
  * const app = express();
19
+ * installFastJson(app);
16
20
  *
17
21
  * const schema: Schema = {
18
22
  * title: 'Example Schema',
@@ -44,20 +48,4 @@ export type { Schema, Options } from 'fast-json-stringify';
44
48
  * });
45
49
  * ```
46
50
  */
47
- export declare const fastJsonSchema: (schema: Schema, options?: Omit<Options, "mode">) => (_req: Request, res: Response, next: NextFunction) => void;
48
- declare global {
49
- namespace Express {
50
- interface Response {
51
- /**
52
- * Send JSON response.
53
- *
54
- * Examples:
55
- * ```ts
56
- * res.fastJson({ user: 'Simone Nigro' });
57
- * res.status(200).fastJson({ user: 'Simone Nigro' });
58
- * ```
59
- */
60
- fastJson: (body: any) => Response;
61
- }
62
- }
63
- }
51
+ export declare const fastJsonSchema: (schema: Schema, options?: FastJsonSchemaOptions) => (_req: Request, res: Response, next: NextFunction) => void;
@@ -1,16 +1,20 @@
1
- import fastJson from 'fast-json-stringify';
1
+ import fastJsonStringify from 'fast-json-stringify';
2
+ import { kSerializer } from './install';
2
3
  /**
3
- * Build a stringify function using a schema of the documents that should be stringified
4
+ * Build a middleware that gives its route a serializer compiled from the schema.
5
+ * The application needs `installFastJson(app)` once for `res.fastJson()` to exist.
6
+ *
4
7
  * @param {Schema} schema The schema used to stringify values
5
- * @param {Options} options The options to use (optional)
8
+ * @param {FastJsonSchemaOptions} options The fast-json-stringify options (optional)
6
9
  * @see https://www.npmjs.com/package/fast-json-stringify
7
10
  *
8
11
  * Examples:
9
12
  * ```ts
10
13
  * import express from 'express';
11
- * import { fastJsonSchema, Schema } from 'express-fast-json-stringify';
14
+ * import { installFastJson, fastJsonSchema, Schema } from 'express-fast-json-stringify';
12
15
  *
13
16
  * const app = express();
17
+ * installFastJson(app);
14
18
  *
15
19
  * const schema: Schema = {
16
20
  * title: 'Example Schema',
@@ -43,25 +47,15 @@ import fastJson from 'fast-json-stringify';
43
47
  * ```
44
48
  */
45
49
  export const fastJsonSchema = (schema, options) => {
46
- if (!schema) {
50
+ if (!schema || (typeof schema !== 'object' && typeof schema !== 'boolean')) {
47
51
  throw new TypeError(`express-fast-json-stringify: invalid schema`);
48
52
  }
49
- const fjs = fastJson(schema, options);
53
+ const serialize = fastJsonStringify(schema, options);
54
+ // One write on res.locals and a next(), nothing else on req or res: a framework that reads a
55
+ // middleware's source to skip work, fulmine does, must be able to see this touches no header
50
56
  return (_req, res, next) => {
51
- /**
52
- * Send JSON response.
53
- *
54
- * Examples:
55
- * ```ts
56
- * res.fastJson({ user: 'Simone Nigro' });
57
- * res.status(200).fastJson({ user: 'Simone Nigro' });
58
- * ```
59
- */
60
- res.fastJson = (body) => {
61
- res.setHeader('Content-Type', 'application/json');
62
- return res.end(fjs(body));
63
- };
57
+ res.locals[kSerializer] = serialize;
64
58
  next();
65
59
  };
66
60
  };
67
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLFFBQXVDLE1BQU0scUJBQXFCLENBQUM7QUFJMUU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBDRztBQUNILE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLE1BQWMsRUFBRSxPQUErQixFQUFFLEVBQUU7SUFDaEYsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxJQUFJLFNBQVMsQ0FBQyw2Q0FBNkMsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7SUFDRCxNQUFNLEdBQUcsR0FBRyxRQUFRLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3RDLE9BQU8sQ0FBQyxJQUFhLEVBQUUsR0FBYSxFQUFFLElBQWtCLEVBQUUsRUFBRTtRQUMxRDs7Ozs7Ozs7V0FRRztRQUNILEdBQUcsQ0FBQyxRQUFRLEdBQUcsQ0FBQyxJQUFTLEVBQVksRUFBRTtZQUNyQyxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1lBQ2xELE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUM7UUFDRixJQUFJLEVBQUUsQ0FBQztJQUNULENBQUMsQ0FBQztBQUNKLENBQUMsQ0FBQyJ9
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLGlCQUFnRCxNQUFNLHFCQUFxQixDQUFDO0FBRW5GLE9BQU8sRUFBRSxXQUFXLEVBQW1CLE1BQU0sV0FBVyxDQUFDO0FBTXpEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Q0c7QUFDSCxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxNQUFjLEVBQUUsT0FBK0IsRUFBRSxFQUFFO0lBQ2hGLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksT0FBTyxNQUFNLEtBQUssU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUMzRSxNQUFNLElBQUksU0FBUyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUNELE1BQU0sU0FBUyxHQUFlLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNqRSw2RkFBNkY7SUFDN0YsNkZBQTZGO0lBQzdGLE9BQU8sQ0FBQyxJQUFhLEVBQUUsR0FBYSxFQUFFLElBQWtCLEVBQVEsRUFBRTtRQUMvRCxHQUFHLENBQUMsTUFBaUQsQ0FBQyxXQUFXLENBQUMsR0FBRyxTQUFTLENBQUM7UUFDaEYsSUFBSSxFQUFFLENBQUM7SUFDVCxDQUFDLENBQUM7QUFDSixDQUFDLENBQUMifQ==
@@ -0,0 +1,81 @@
1
+ import { type Options, type Schema } from 'fast-json-stringify';
2
+ import { type Application, type FastJsonOptions } from './install';
3
+ /**
4
+ * The parts of an OpenAPI 3.x or Swagger 2.0 document this package reads.
5
+ *
6
+ * It is deliberately a plain document rather than an integration with a
7
+ * specific library: every popular Express toolchain either consumes or produces
8
+ * one of these, so `swagger-jsdoc`, `swagger-ui-express`, `tsoa`,
9
+ * `express-openapi-validator` and a hand written file all work unchanged.
10
+ */
11
+ export type OpenApiDocument = {
12
+ readonly openapi?: string;
13
+ readonly swagger?: string;
14
+ readonly paths?: Readonly<Record<string, unknown>>;
15
+ readonly components?: Readonly<Record<string, unknown>>;
16
+ readonly definitions?: Readonly<Record<string, unknown>>;
17
+ };
18
+ export type OpenApiOptions = Omit<Options, 'mode'> & FastJsonOptions & {
19
+ /** Media type to read the schema from. Defaults to `application/json`. */
20
+ readonly contentType?: string;
21
+ };
22
+ /**
23
+ * Translate an Express route pattern into the OpenAPI equivalent:
24
+ * `/users/:id` becomes `/users/{id}`. Express parameter modifiers — a trailing
25
+ * `?` or an inline `(regex)` — are dropped, since OpenAPI has no notion of them.
26
+ */
27
+ export declare const toOpenApiPath: (path: string) => string;
28
+ /**
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.
32
+ *
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()`
54
+ * serializes with the `201` schema. Routes the document does not describe fall
55
+ * back to `res.json()` unless `strict` is set.
56
+ *
57
+ * @param {Application} app The application
58
+ * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
59
+ * @param {OpenApiOptions} options The options to use (optional)
60
+ *
61
+ * Examples:
62
+ * ```ts
63
+ * import express from 'express';
64
+ * import swaggerJsdoc from 'swagger-jsdoc';
65
+ * import { fastJsonOpenApi } from 'express-fast-json-stringify';
66
+ *
67
+ * const app = express();
68
+ * const document = swaggerJsdoc({ definition: { openapi: '3.1.0', info: { title: 'API', version: '1.0.0' } }, apis: ['./routes/*.ts'] });
69
+ *
70
+ * fastJsonOpenApi(app, document);
71
+ *
72
+ * app.get('/users/:id', (req, res, next) => {
73
+ * try {
74
+ * res.fastJson({ id: Number(req.params.id), firstName: 'Simone' });
75
+ * } catch (error) {
76
+ * next(error);
77
+ * }
78
+ * });
79
+ * ```
80
+ */
81
+ export declare const fastJsonOpenApi: (app: Application, document: OpenApiDocument, options?: OpenApiOptions) => void;
@@ -0,0 +1,174 @@
1
+ import fastJsonStringify from 'fast-json-stringify';
2
+ import { installFastJson, setResolver } from './install';
3
+ /**
4
+ * Translate an Express route pattern into the OpenAPI equivalent:
5
+ * `/users/:id` becomes `/users/{id}`. Express parameter modifiers — a trailing
6
+ * `?` or an inline `(regex)` — are dropped, since OpenAPI has no notion of them.
7
+ */
8
+ export const toOpenApiPath = (path) => path.replace(/:([A-Za-z0-9_]+)(\([^)]*\))?\??/g, '{$1}');
9
+ /**
10
+ * Express answers a HEAD request with the GET handler, and documents rarely
11
+ * describe a `head` operation, so fall back to `get`.
12
+ */
13
+ const methodCandidates = (method) => (method === 'head' ? ['head', 'get'] : [method]);
14
+ /**
15
+ * Response keys to try, most specific first. OpenAPI allows a wildcard range
16
+ * (`2XX`) and a catch all (`default`) next to explicit codes.
17
+ */
18
+ const statusCandidates = (status) => {
19
+ const range = Math.floor(status / 100);
20
+ return [String(status), `${range}XX`, `${range}xx`, 'default'];
21
+ };
22
+ const findResponseSchema = (document, path, method, status, contentType) => {
23
+ const operations = document.paths?.[path];
24
+ if (!operations) {
25
+ return undefined;
26
+ }
27
+ for (const candidate of methodCandidates(method)) {
28
+ const operation = operations[candidate];
29
+ const responses = operation?.responses;
30
+ if (!responses) {
31
+ continue;
32
+ }
33
+ for (const key of statusCandidates(status)) {
34
+ const response = responses[key];
35
+ if (!response) {
36
+ continue;
37
+ }
38
+ // OpenAPI 3.x keys the schema by media type; Swagger 2.0 does not.
39
+ const schema = response.content?.[contentType]?.schema ?? response.schema;
40
+ if (schema) {
41
+ return schema;
42
+ }
43
+ }
44
+ }
45
+ return undefined;
46
+ };
47
+ /**
48
+ * fast-json-stringify resolves `$ref` as a JSON pointer against the root of the
49
+ * schema it is given, so the document's shared schemas only have to be reachable
50
+ * under the key the references already use — `components` for OpenAPI 3.x,
51
+ * `definitions` for Swagger 2.0. No rewriting needed, and recursive references
52
+ * keep working.
53
+ */
54
+ const withSharedSchemas = (document, schema) => {
55
+ const result = { ...schema };
56
+ if (document.components && result.components === undefined) {
57
+ result.components = document.components;
58
+ }
59
+ if (document.definitions && result.definitions === undefined) {
60
+ result.definitions = document.definitions;
61
+ }
62
+ return result;
63
+ };
64
+ const validateDocument = (document) => {
65
+ if (!document || typeof document !== 'object' || typeof document.paths !== 'object' || document.paths === null) {
66
+ throw new TypeError(`express-fast-json-stringify: invalid OpenAPI document`);
67
+ }
68
+ };
69
+ /**
70
+ * The schema the document declares for one operation and status, with the
71
+ * shared schemas attached so `$ref` resolves, ready for `fastJsonSchema`. For
72
+ * a route whose Express path does not match the document.
73
+ *
74
+ * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
75
+ * @param {string} path The OpenAPI path, `/users/{id}`
76
+ * @param {string} method The operation, `get`
77
+ * @param {number} status The response status, `200` when omitted
78
+ * @param {string} contentType The media type, `application/json` when omitted
79
+ * @returns {Schema | undefined} undefined when the document describes no such response
80
+ *
81
+ * Examples:
82
+ * ```ts
83
+ * app.get('/v2/people/:id', fastJsonSchema(openApiSchema(document, '/users/{id}', 'get')!), handler);
84
+ * ```
85
+ */
86
+ export const openApiSchema = (document, path, method, status = 200, contentType = 'application/json') => {
87
+ validateDocument(document);
88
+ const schema = findResponseSchema(document, path, method.toLowerCase(), status, contentType);
89
+ return schema ? withSharedSchemas(document, schema) : undefined;
90
+ };
91
+ /**
92
+ * Serialize every documented response of an application from its OpenAPI or
93
+ * Swagger document, so the contract you already publish is the one used to
94
+ * serialize. Once per app, at setup: it calls `installFastJson(app)` itself, and no
95
+ * middleware runs per request.
96
+ *
97
+ * The operation is resolved from the matched Express route, and the schema
98
+ * from the response status code, which means `res.status(201).fastJson()`
99
+ * serializes with the `201` schema. Routes the document does not describe fall
100
+ * back to `res.json()` unless `strict` is set.
101
+ *
102
+ * @param {Application} app The application
103
+ * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
104
+ * @param {OpenApiOptions} options The options to use (optional)
105
+ *
106
+ * Examples:
107
+ * ```ts
108
+ * import express from 'express';
109
+ * import swaggerJsdoc from 'swagger-jsdoc';
110
+ * import { fastJsonOpenApi } from 'express-fast-json-stringify';
111
+ *
112
+ * const app = express();
113
+ * const document = swaggerJsdoc({ definition: { openapi: '3.1.0', info: { title: 'API', version: '1.0.0' } }, apis: ['./routes/*.ts'] });
114
+ *
115
+ * fastJsonOpenApi(app, document);
116
+ *
117
+ * app.get('/users/:id', (req, res, next) => {
118
+ * try {
119
+ * res.fastJson({ id: Number(req.params.id), firstName: 'Simone' });
120
+ * } catch (error) {
121
+ * next(error);
122
+ * }
123
+ * });
124
+ * ```
125
+ */
126
+ export const fastJsonOpenApi = (app, document, options = {}) => {
127
+ validateDocument(document);
128
+ const { contentType = 'application/json', overrideJson, onError, strict, ...fastJsonOptions } = options;
129
+ installFastJson(app, { overrideJson, onError, strict });
130
+ const compile = (path, method, status) => {
131
+ const schema = findResponseSchema(document, path, method, status, contentType);
132
+ return schema ? fastJsonStringify(withSharedSchemas(document, schema), fastJsonOptions) : null;
133
+ };
134
+ // One compiled serializer per route, mount and status, found from the route object the
135
+ // framework already matched: no path is rebuilt per request. Misses are cached as `null`.
136
+ // A router mounted twice answers under two mounts, so the mount is a key of its own.
137
+ const byRoute = new WeakMap();
138
+ // Answered from plain middleware, before any route matched: the request path is what names
139
+ // the operation, and a string key is all there is.
140
+ const byPath = new Map();
141
+ setResolver(app, (res) => {
142
+ const req = res.req;
143
+ const status = res.statusCode;
144
+ const method = req.method.toLowerCase();
145
+ const route = req.route;
146
+ if (route === undefined) {
147
+ const key = `${method} ${req.baseUrl}${req.path} ${status}`;
148
+ let serialize = byPath.get(key);
149
+ if (serialize === undefined) {
150
+ serialize = compile(toOpenApiPath(`${req.baseUrl}${req.path}`), method, status);
151
+ byPath.set(key, serialize);
152
+ }
153
+ return serialize;
154
+ }
155
+ let byMount = byRoute.get(route);
156
+ if (byMount === undefined) {
157
+ byMount = new Map();
158
+ byRoute.set(route, byMount);
159
+ }
160
+ const baseUrl = req.baseUrl;
161
+ let byStatus = byMount.get(baseUrl);
162
+ if (byStatus === undefined) {
163
+ byStatus = new Map();
164
+ byMount.set(baseUrl, byStatus);
165
+ }
166
+ let serialize = byStatus.get(status);
167
+ if (serialize === undefined) {
168
+ serialize = compile(toOpenApiPath(`${baseUrl}${route.path}`), method, status);
169
+ byStatus.set(status, serialize);
170
+ }
171
+ return serialize;
172
+ });
173
+ };
174
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvb3BlbmFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLGlCQUFnRCxNQUFNLHFCQUFxQixDQUFDO0FBRW5GLE9BQU8sRUFBMEMsZUFBZSxFQUFtQixXQUFXLEVBQUUsTUFBTSxXQUFXLENBQUM7QUF3QmxIOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxJQUFZLEVBQVUsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsa0NBQWtDLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFFaEg7OztHQUdHO0FBQ0gsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE1BQWMsRUFBcUIsRUFBRSxDQUFDLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUVqSDs7O0dBR0c7QUFDSCxNQUFNLGdCQUFnQixHQUFHLENBQUMsTUFBYyxFQUFxQixFQUFFO0lBQzdELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsR0FBRyxLQUFLLElBQUksRUFBRSxHQUFHLEtBQUssSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBQ2pFLENBQUMsQ0FBQztBQVFGLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxRQUF5QixFQUFFLElBQVksRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLFdBQW1CLEVBQVcsRUFBRTtJQUNuSSxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFrRCxDQUFDO0lBQzNGLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNoQixPQUFP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
@@ -0,0 +1,9 @@
1
+ import type { Response } from 'express';
2
+ /**
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.
8
+ */
9
+ export declare const sendSerialized: (res: Response, json: string) => Response;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Write an already serialized JSON payload the way `res.json()` does once the
3
+ * body is a string: the type when the route set none, then `res.send()`, which
4
+ * owns `Content-Length`, the `ETag`, conditional requests, `204`/`304` and
5
+ * `HEAD` in Express and in the frameworks that stand in for it. Nothing is
6
+ * reimplemented here, so nothing can drift.
7
+ */
8
+ export const sendSerialized = (res, json) => {
9
+ if (!res.get('Content-Type')) {
10
+ res.set('Content-Type', 'application/json; charset=utf-8');
11
+ }
12
+ return res.send(json);
13
+ };
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvc2VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxHQUFhLEVBQUUsSUFBWSxFQUFZLEVBQUU7SUFDdEUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztRQUM3QixHQUFHLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxpQ0FBaUMsQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFDRCxPQUFPLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDeEIsQ0FBQyxDQUFDIn0=