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,18 +1,20 @@
1
- import fastJson from 'fast-json-stringify';
2
- import { overrideResJson } from './override';
3
- import { sendJson } from './send';
1
+ import fastJsonStringify from 'fast-json-stringify';
2
+ import { kSerializer } from './install';
4
3
  /**
5
- * 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
+ *
6
7
  * @param {Schema} schema The schema used to stringify values
7
- * @param {Options} options The options to use (optional)
8
+ * @param {FastJsonSchemaOptions} options The fast-json-stringify options (optional)
8
9
  * @see https://www.npmjs.com/package/fast-json-stringify
9
10
  *
10
11
  * Examples:
11
12
  * ```ts
12
13
  * import express from 'express';
13
- * import { fastJsonSchema, Schema } from 'express-fast-json-stringify';
14
+ * import { installFastJson, fastJsonSchema, Schema } from 'express-fast-json-stringify';
14
15
  *
15
16
  * const app = express();
17
+ * installFastJson(app);
16
18
  *
17
19
  * const schema: Schema = {
18
20
  * title: 'Example Schema',
@@ -48,26 +50,12 @@ export const fastJsonSchema = (schema, options) => {
48
50
  if (!schema || (typeof schema !== 'object' && typeof schema !== 'boolean')) {
49
51
  throw new TypeError(`express-fast-json-stringify: invalid schema`);
50
52
  }
51
- const { overrideJson = false, onError, ...fastJsonOptions } = options ?? {};
52
- const fjs = fastJson(schema, fastJsonOptions);
53
- return (req, res, next) => {
54
- /**
55
- * Send JSON response.
56
- *
57
- * Examples:
58
- * ```ts
59
- * res.fastJson({ user: 'Simone Nigro' });
60
- * res.status(200).fastJson({ user: 'Simone Nigro' });
61
- * ```
62
- */
63
- res.fastJson = (body) => sendJson(req, res, fjs(body));
64
- if (overrideJson) {
65
- // A single schema describes the successful payload, so applying it to an
66
- // error body would rewrite it into the wrong shape. Only 2xx responses
67
- // take the fast path; everything else keeps the stock res.json().
68
- overrideResJson(req, res, (status) => (status >= 200 && status < 300 ? fjs : null), onError);
69
- }
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
56
+ return (_req, res, next) => {
57
+ res.locals[kSerializer] = serialize;
70
58
  next();
71
59
  };
72
60
  };
73
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLFFBQXVDLE1BQU0scUJBQXFCLENBQUM7QUFFMUUsT0FBTyxFQUE2QixlQUFlLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDeEUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQXNCbEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBDRztBQUNILE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLE1BQWMsRUFBRSxPQUErQixFQUFFLEVBQUU7SUFDaEYsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxPQUFPLE1BQU0sS0FBSyxTQUFTLENBQUMsRUFBRSxDQUFDO1FBQzNFLE1BQU0sSUFBSSxTQUFTLENBQUMsNkNBQTZDLENBQUMsQ0FBQztJQUNyRSxDQUFDO0lBQ0QsTUFBTSxFQUFFLFlBQVksR0FBRyxLQUFLLEVBQUUsT0FBTyxFQUFFLEdBQUcsZUFBZSxFQUFFLEdBQUcsT0FBTyxJQUFJLEVBQUUsQ0FBQztJQUM1RSxNQUFNLEdBQUcsR0FBRyxRQUFRLENBQUMsTUFBTSxFQUFFLGVBQWUsQ0FBQyxDQUFDO0lBQzlDLE9BQU8sQ0FBQyxHQUFZLEVBQUUsR0FBYSxFQUFFLElBQWtCLEVBQUUsRUFBRTtRQUN6RDs7Ozs7Ozs7V0FRRztRQUNILEdBQUcsQ0FBQyxRQUFRLEdBQUcsQ0FBQyxJQUFTLEVBQVksRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBRXRFLElBQUksWUFBWSxFQUFFLENBQUM7WUFDakIseUVBQXlFO1lBQ3pFLHVFQUF1RTtZQUN2RSxrRUFBa0U7WUFDbEUsZUFBZSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsTUFBTSxJQUFJLEdBQUcsSUFBSSxNQUFNLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQy9GLENBQUM7UUFFRCxJQUFJLEVBQUUsQ0FBQztJQUNULENBQUMsQ0FBQztBQUNKLENBQUMsQ0FBQyJ9
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLGlCQUFnRCxNQUFNLHFCQUFxQixDQUFDO0FBRW5GLE9BQU8sRUFBRSxXQUFXLEVBQW1CLE1BQU0sV0FBVyxDQUFDO0FBTXpEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Q0c7QUFDSCxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxNQUFjLEVBQUUsT0FBK0IsRUFBRSxFQUFFO0lBQ2hGLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksT0FBTyxNQUFNLEtBQUssU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUMzRSxNQUFNLElBQUksU0FBUyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUNELE1BQU0sU0FBUyxHQUFlLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNqRSw2RkFBNkY7SUFDN0YsNkZBQTZGO0lBQzdGLE9BQU8sQ0FBQyxJQUFhLEVBQUUsR0FBYSxFQUFFLElBQWtCLEVBQVEsRUFBRTtRQUMvRCxHQUFHLENBQUMsTUFBaUQsQ0FBQyxXQUFXLENBQUMsR0FBRyxTQUFTLENBQUM7UUFDaEYsSUFBSSxFQUFFLENBQUM7SUFDVCxDQUFDLENBQUM7QUFDSixDQUFDLENBQUMifQ==
@@ -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;
@@ -1,6 +1,5 @@
1
- import fastJson from 'fast-json-stringify';
2
- import { overrideResJson } from './override';
3
- import { sendJson } from './send';
1
+ import fastJsonStringify from 'fast-json-stringify';
2
+ import { installFastJson, setResolver } from './install';
4
3
  /**
5
4
  * Translate an Express route pattern into the OpenAPI equivalent:
6
5
  * `/users/:id` becomes `/users/{id}`. Express parameter modifiers — a trailing
@@ -62,15 +61,45 @@ const withSharedSchemas = (document, schema) => {
62
61
  }
63
62
  return result;
64
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
+ };
65
69
  /**
66
- * Build a stringify middleware that takes its schemas from an OpenAPI or Swagger
67
- * document, so the contract you already publish is the one used to serialize.
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
68
80
  *
69
- * The operation is resolved per request from the matched Express route, and the
70
- * schema from the response status code, which means `res.status(201).fastJson()`
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()`
71
99
  * serializes with the `201` schema. Routes the document does not describe fall
72
100
  * back to `res.json()` unless `strict` is set.
73
101
  *
102
+ * @param {Application} app The application
74
103
  * @param {OpenApiDocument} document The OpenAPI 3.x or Swagger 2.0 document
75
104
  * @param {OpenApiOptions} options The options to use (optional)
76
105
  *
@@ -83,7 +112,7 @@ const withSharedSchemas = (document, schema) => {
83
112
  * const app = express();
84
113
  * const document = swaggerJsdoc({ definition: { openapi: '3.1.0', info: { title: 'API', version: '1.0.0' } }, apis: ['./routes/*.ts'] });
85
114
  *
86
- * app.use(fastJsonOpenApi(document));
115
+ * fastJsonOpenApi(app, document);
87
116
  *
88
117
  * app.get('/users/:id', (req, res, next) => {
89
118
  * try {
@@ -94,53 +123,52 @@ const withSharedSchemas = (document, schema) => {
94
123
  * });
95
124
  * ```
96
125
  */
97
- export const fastJsonOpenApi = (document, options = {}) => {
98
- if (!document || typeof document !== 'object' || typeof document.paths !== 'object' || document.paths === null) {
99
- throw new TypeError(`express-fast-json-stringify: invalid OpenAPI document`);
100
- }
101
- const { contentType = 'application/json', path: pinnedPath, method: pinnedMethod, strict = false, overrideJson = false, onError, ...fastJsonOptions } = options;
102
- // One compiled serializer per operation and status code. Misses are cached as
103
- // `null` so an undocumented route costs a single lookup.
104
- const serializers = new Map();
105
- const routePath = (req) => pinnedPath ?? toOpenApiPath(`${req.baseUrl}${req.route?.path ?? req.path}`);
106
- const serializerFor = (req, status) => {
107
- const path = routePath(req);
108
- const method = (pinnedMethod ?? req.method).toLowerCase();
109
- const key = `${method} ${path} ${status}`;
110
- const cached = serializers.get(key);
111
- if (cached !== undefined) {
112
- return cached;
113
- }
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) => {
114
131
  const schema = findResponseSchema(document, path, method, status, contentType);
115
- const serializer = schema ? fastJson(withSharedSchemas(document, schema), fastJsonOptions) : null;
116
- serializers.set(key, serializer);
117
- return serializer;
132
+ return schema ? fastJsonStringify(withSharedSchemas(document, schema), fastJsonOptions) : null;
118
133
  };
119
- return (req, res, next) => {
120
- /**
121
- * Send JSON response, serialized with the schema the document declares for
122
- * this route and status code.
123
- *
124
- * Examples:
125
- * ```ts
126
- * res.fastJson({ user: 'Simone Nigro' });
127
- * res.status(201).fastJson({ user: 'Simone Nigro' });
128
- * ```
129
- */
130
- res.fastJson = (body) => {
131
- const serializer = serializerFor(req, res.statusCode);
132
- if (!serializer) {
133
- if (strict) {
134
- throw new Error(`express-fast-json-stringify: no ${contentType} schema for ${req.method} ${routePath(req)} with status ${res.statusCode}`);
135
- }
136
- return res.json(body);
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);
137
152
  }
138
- return sendJson(req, res, serializer(body));
139
- };
140
- if (overrideJson) {
141
- overrideResJson(req, res, (status) => serializerFor(req, status), onError);
153
+ return serialize;
142
154
  }
143
- next();
144
- };
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
+ });
145
173
  };
146
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvb3BlbmFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLFFBQXVDLE1BQU0scUJBQXFCLENBQUM7QUFFMUUsT0FBTyxFQUE2QixlQUFlLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDeEUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQWlEbEM7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBRyxDQUFDLElBQVksRUFBVSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxrQ0FBa0MsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUVoSDs7O0dBR0c7QUFDSCxNQUFNLGdCQUFnQixHQUFHLENBQUMsTUFBYyxFQUFxQixFQUFFLENBQUMsQ0FBQyxNQUFNLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0FBRWpIOzs7R0FHRztBQUNILE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxNQUFjLEVBQXFCLEVBQUU7SUFDN0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDLENBQUM7SUFDdkMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxHQUFHLEtBQUssSUFBSSxFQUFFLEdBQUcsS0FBSyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUM7QUFDakUsQ0FBQyxDQUFDO0FBUUYsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFFBQXlCLEVBQUUsSUFBWSxFQUFFLE1BQWMsRUFBRSxNQUFjLEVBQUUsV0FBbUIsRUFBVyxFQUFFO0lBQ25JLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxJQUFJLENBQWtELENBQUM7SUFDM0YsSUFBSSxD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
174
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvb3BlbmFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLGlCQUFnRCxNQUFNLHFCQUFxQixDQUFDO0FBRW5GLE9BQU8sRUFBMEMsZUFBZSxFQUFtQixXQUFXLEVBQUUsTUFBTSxXQUFXLENBQUM7QUF3QmxIOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxJQUFZLEVBQVUsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsa0NBQWtDLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFFaEg7OztHQUdHO0FBQ0gsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE1BQWMsRUFBcUIsRUFBRSxDQUFDLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUVqSDs7O0dBR0c7QUFDSCxNQUFNLGdCQUFnQixHQUFHLENBQUMsTUFBYyxFQUFxQixFQUFFO0lBQzdELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsR0FBRyxLQUFLLElBQUksRUFBRSxHQUFHLEtBQUssSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBQ2pFLENBQUMsQ0FBQztBQVFGLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxRQUF5QixFQUFFLElBQVksRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLFdBQW1CLEVBQVcsRUFBRTtJQUNuSSxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFrRCxDQUFDO0lBQzNGLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNoQixPQUFP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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,41 +1,14 @@
1
1
  /**
2
- * Write an already serialized JSON payload with the same HTTP semantics as
3
- * `res.json()`: charset, `Content-Length`, `ETag`, conditional requests and the
4
- * empty body rules for `204`/`304`.
5
- *
6
- * Every middleware in this package goes through here, so they cannot drift
7
- * apart from each other or from Express.
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.
8
7
  */
9
- export const sendJson = (req, res, serialized) => {
10
- let payload = serialized;
11
- // Do not clobber a content type the route set on purpose (eg. res.type('application/vnd.api+json')).
12
- if (!res.getHeader('Content-Type')) {
13
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
8
+ export const sendSerialized = (res, json) => {
9
+ if (!res.get('Content-Type')) {
10
+ res.set('Content-Type', 'application/json; charset=utf-8');
14
11
  }
15
- // Mirror res.send(): honour the app `etag` setting so that swapping
16
- // res.json() for res.fastJson() keeps conditional requests working.
17
- const etagFn = res.app?.get('etag fn');
18
- if (typeof etagFn === 'function' && !res.getHeader('ETag')) {
19
- const etag = etagFn(payload, 'utf-8');
20
- if (etag) {
21
- res.setHeader('ETag', etag);
22
- }
23
- }
24
- if (req.fresh) {
25
- res.statusCode = 304;
26
- }
27
- // 204 No Content and 304 Not Modified must not carry a body, nor describe one.
28
- if (res.statusCode === 204 || res.statusCode === 304) {
29
- res.removeHeader('Content-Type');
30
- res.removeHeader('Content-Length');
31
- res.removeHeader('Transfer-Encoding');
32
- payload = '';
33
- }
34
- else {
35
- // Without this the response falls back to chunked encoding, and HEAD
36
- // requests answer with no length at all.
37
- res.setHeader('Content-Length', Buffer.byteLength(payload));
38
- }
39
- return res.end(payload);
12
+ return res.send(json);
40
13
  };
41
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvc2VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLENBQUMsR0FBWSxFQUFFLEdBQWEsRUFBRSxVQUFrQixFQUFZLEVBQUU7SUFDcEYsSUFBSSxPQUFPLEdBQUcsVUFBVSxDQUFDO0lBRXpCLHFHQUFxRztJQUNyRyxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUFDO1FBQ25DLEdBQUcsQ0FBQyxTQUFTLENBQUMsY0FBYyxFQUFFLGlDQUFpQyxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVELG9FQUFvRTtJQUNwRSxvRUFBb0U7SUFDcEUsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsU0FBUyxDQUE0RSxDQUFDO0lBQ2xILElBQUksT0FBTyxNQUFNLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQzNELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUNULEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzlCLENBQUM7SUFDSCxDQUFDO0lBRUQsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDZCxHQUFHLENBQUMsVUFBVSxHQUFHLEdBQUcsQ0FBQztJQUN2QixDQUFDO0lBRUQsK0VBQStFO0lBQy9FLElBQUksR0FBRyxDQUFDLFVBQVUsS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLFVBQVUsS0FBSyxHQUFHLEVBQUUsQ0FBQztRQUNyRCxHQUFHLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ2pDLEdBQUcsQ0FBQyxZQUFZLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUNuQyxHQUFHLENBQUMsWUFBWSxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDdEMsT0FBTyxHQUFHLEVBQUUsQ0FBQztJQUNmLENBQUM7U0FBTSxDQUFDO1FBQ04scUVBQXFFO1FBQ3JFLHlDQUF5QztRQUN6QyxHQUFHLENBQUMsU0FBUyxDQUFDLGdCQUFnQixFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUM5RCxDQUFDO0lBRUQsT0FBTyxHQUFHLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQzFCLENBQUMsQ0FBQyJ9
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvc2VuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxHQUFhLEVBQUUsSUFBWSxFQUFZLEVBQUU7SUFDdEUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztRQUM3QixHQUFHLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxpQ0FBaUMsQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFDRCxPQUFPLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDeEIsQ0FBQyxDQUFDIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-fast-json-stringify",
3
- "version": "1.3.0",
3
+ "version": "2.0.0",
4
4
  "description": "With express-fast-json-stringify, you can leverage fast-json-stringify in your Express application to improve JSON serialization performance",
5
5
  "main": "build/main/index.js",
6
6
  "typings": "build/main/index.d.ts",
@@ -44,7 +44,8 @@
44
44
  "prepare-release": "run-s reset-hard test cov:check doc:html release doc:publish",
45
45
  "example": "tsx example/index.ts",
46
46
  "release": "release-it",
47
- "example:openapi": "tsx example/openapi.ts"
47
+ "example:openapi": "tsx example/openapi.ts",
48
+ "prepare": "husky"
48
49
  },
49
50
  "engines": {
50
51
  "node": ">=20"
@@ -66,7 +67,10 @@
66
67
  "eslint-plugin-functional": "^10.0.0",
67
68
  "eslint-plugin-import-x": "^4.17.1",
68
69
  "express": "^5.2.1",
70
+ "fulmine.js": "^5.21.2",
69
71
  "gh-pages": "^6.3.0",
72
+ "husky": "^9.1.7",
73
+ "lint-staged": "^17.2.0",
70
74
  "npm-run-all2": "^9.0.2",
71
75
  "open-cli": "^9.0.0",
72
76
  "prettier": "^3.9.6",
@@ -93,5 +97,8 @@
93
97
  "prettier": {
94
98
  "singleQuote": true,
95
99
  "printWidth": 180
100
+ },
101
+ "lint-staged": {
102
+ "*": "prettier --write --ignore-unknown"
96
103
  }
97
104
  }
@@ -1,17 +0,0 @@
1
- import type { Request, Response } from 'express';
2
- /** Resolves the serializer to use for a response status, or `null` for none. */
3
- export type SerializerResolver = (status: number) => ((body: any) => string) | null;
4
- /** Notified when an overridden `res.json()` could not use the fast path. */
5
- export type OverrideErrorHandler = (error: unknown, req: Request) => void;
6
- /**
7
- * Route `res.json()` through a compiled serializer when one is available.
8
- *
9
- * Only `res.json` is replaced: Express implements `res.send(object)` by calling
10
- * `res.json(object)`, so both entry points are covered by the single hook.
11
- *
12
- * The override is deliberately incapable of breaking a route. Whenever the fast
13
- * path does not apply — no schema for this response, custom JSON settings, or a
14
- * serializer that throws on the given body — the original `res.json()` runs and
15
- * the response is exactly what it was before.
16
- */
17
- export declare const overrideResJson: (req: Request, res: Response, resolve: SerializerResolver, onError?: OverrideErrorHandler) => void;
@@ -1,49 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.overrideResJson = void 0;
4
- const send_1 = require("./send");
5
- /**
6
- * Settings that change the bytes `res.json()` writes. A compiled serializer
7
- * cannot reproduce any of them, so the fast path has to step aside when one is
8
- * configured rather than silently change the output.
9
- */
10
- const hasCustomJsonSettings = (res) => {
11
- var _a, _b;
12
- const app = res.app;
13
- if (!app) {
14
- return false;
15
- }
16
- return Boolean((_b = (_a = app.get('json replacer')) !== null && _a !== void 0 ? _a : app.get('json spaces')) !== null && _b !== void 0 ? _b : app.get('json escape'));
17
- };
18
- /**
19
- * Route `res.json()` through a compiled serializer when one is available.
20
- *
21
- * Only `res.json` is replaced: Express implements `res.send(object)` by calling
22
- * `res.json(object)`, so both entry points are covered by the single hook.
23
- *
24
- * The override is deliberately incapable of breaking a route. Whenever the fast
25
- * path does not apply — no schema for this response, custom JSON settings, or a
26
- * serializer that throws on the given body — the original `res.json()` runs and
27
- * the response is exactly what it was before.
28
- */
29
- const overrideResJson = (req, res, resolve, onError) => {
30
- const original = res.json.bind(res);
31
- res.json = (body) => {
32
- if (!hasCustomJsonSettings(res)) {
33
- const serialize = resolve(res.statusCode);
34
- if (serialize) {
35
- try {
36
- return (0, send_1.sendJson)(req, res, serialize(body));
37
- }
38
- catch (error) {
39
- // A body that does not fit the schema must not turn into a failed
40
- // request: report it, then fall through to the stock res.json().
41
- onError === null || onError === void 0 ? void 0 : onError(error, req);
42
- }
43
- }
44
- }
45
- return original(body);
46
- };
47
- };
48
- exports.overrideResJson = overrideResJson;
49
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3ZlcnJpZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL292ZXJyaWRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLGlDQUFrQztBQVFsQzs7OztHQUlHO0FBQ0gsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLEdBQWEsRUFBVyxFQUFFOztJQUN2RCxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDO0lBQ3BCLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNULE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDLE1BQUEsTUFBQSxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxtQ0FBSSxHQUFHLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxtQ0FBSSxHQUFHLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7QUFDL0YsQ0FBQyxDQUFDO0FBRUY7Ozs7Ozs7Ozs7R0FVRztBQUNJLE1BQU0sZUFBZSxHQUFHLENBQUMsR0FBWSxFQUFFLEdBQWEsRUFBRSxPQUEyQixFQUFFLE9BQThCLEVBQVEsRUFBRTtJQUNoSSxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUVwQyxHQUFHLENBQUMsSUFBSSxHQUFHLENBQUMsSUFBUyxFQUFZLEVBQUU7UUFDakMsSUFBSSxDQUFDLHFCQUFxQixDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDaEMsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUMxQyxJQUFJLFNBQVMsRUFBRSxDQUFDO2dCQUNkLElBQUksQ0FBQztvQkFDSCxPQUFPLElBQUEsZUFBUSxFQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7Z0JBQzdDLENBQUM7Z0JBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztvQkFDZixrRUFBa0U7b0JBQ2xFLGlFQUFpRTtvQkFDakUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFHLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQztnQkFDeEIsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEIsQ0FBQyxDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBbEJXLFFBQUEsZUFBZSxtQkFrQjFCIn0=
@@ -1,17 +0,0 @@
1
- import type { Request, Response } from 'express';
2
- /** Resolves the serializer to use for a response status, or `null` for none. */
3
- export type SerializerResolver = (status: number) => ((body: any) => string) | null;
4
- /** Notified when an overridden `res.json()` could not use the fast path. */
5
- export type OverrideErrorHandler = (error: unknown, req: Request) => void;
6
- /**
7
- * Route `res.json()` through a compiled serializer when one is available.
8
- *
9
- * Only `res.json` is replaced: Express implements `res.send(object)` by calling
10
- * `res.json(object)`, so both entry points are covered by the single hook.
11
- *
12
- * The override is deliberately incapable of breaking a route. Whenever the fast
13
- * path does not apply — no schema for this response, custom JSON settings, or a
14
- * serializer that throws on the given body — the original `res.json()` runs and
15
- * the response is exactly what it was before.
16
- */
17
- export declare const overrideResJson: (req: Request, res: Response, resolve: SerializerResolver, onError?: OverrideErrorHandler) => void;
@@ -1,44 +0,0 @@
1
- import { sendJson } from './send';
2
- /**
3
- * Settings that change the bytes `res.json()` writes. A compiled serializer
4
- * cannot reproduce any of them, so the fast path has to step aside when one is
5
- * configured rather than silently change the output.
6
- */
7
- const hasCustomJsonSettings = (res) => {
8
- const app = res.app;
9
- if (!app) {
10
- return false;
11
- }
12
- return Boolean(app.get('json replacer') ?? app.get('json spaces') ?? app.get('json escape'));
13
- };
14
- /**
15
- * Route `res.json()` through a compiled serializer when one is available.
16
- *
17
- * Only `res.json` is replaced: Express implements `res.send(object)` by calling
18
- * `res.json(object)`, so both entry points are covered by the single hook.
19
- *
20
- * The override is deliberately incapable of breaking a route. Whenever the fast
21
- * path does not apply — no schema for this response, custom JSON settings, or a
22
- * serializer that throws on the given body — the original `res.json()` runs and
23
- * the response is exactly what it was before.
24
- */
25
- export const overrideResJson = (req, res, resolve, onError) => {
26
- const original = res.json.bind(res);
27
- res.json = (body) => {
28
- if (!hasCustomJsonSettings(res)) {
29
- const serialize = resolve(res.statusCode);
30
- if (serialize) {
31
- try {
32
- return sendJson(req, res, serialize(body));
33
- }
34
- catch (error) {
35
- // A body that does not fit the schema must not turn into a failed
36
- // request: report it, then fall through to the stock res.json().
37
- onError?.(error, req);
38
- }
39
- }
40
- }
41
- return original(body);
42
- };
43
- };
44
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3ZlcnJpZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL292ZXJyaWRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFRbEM7Ozs7R0FJRztBQUNILE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxHQUFhLEVBQVcsRUFBRTtJQUN2RCxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDO0lBQ3BCLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNULE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksR0FBRyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsSUFBSSxHQUFHLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7QUFDL0YsQ0FBQyxDQUFDO0FBRUY7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsT0FBMkIsRUFBRSxPQUE4QixFQUFRLEVBQUU7SUFDaEksTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFcEMsR0FBRyxDQUFDLElBQUksR0FBRyxDQUFDLElBQVMsRUFBWSxFQUFFO1FBQ2pDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ2hDLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDMUMsSUFBSSxTQUFTLEVBQUUsQ0FBQztnQkFDZCxJQUFJLENBQUM7b0JBQ0gsT0FBTyxRQUFRLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDN0MsQ0FBQztnQkFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO29CQUNmLGtFQUFrRTtvQkFDbEUsaUVBQWlFO29CQUNqRSxPQUFPLEVBQUUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7Z0JBQ3hCLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hCLENBQUMsQ0FBQztBQUNKLENBQUMsQ0FBQyJ9