@tramvai/papi 2.22.0 → 2.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,29 +5,168 @@ sidebar_position: 4
5
5
 
6
6
  # Papi
7
7
 
8
- Library for creating and working with papi handlers.
8
+ Library for creating and working with papi handlers. More on papi feature you can find [here](features/papi/introduction.md)
9
9
 
10
10
  ## Installation
11
11
 
12
12
  You need to install `@tramvai/papi`
13
13
 
14
- ```bash
15
- yarn add @tramvai/module-papi
14
+ ```bash npm2yarn
15
+ npm i @tramvai/papi
16
16
  ```
17
17
 
18
- ## Usage
18
+ ## Explanation
19
+
20
+ Library mostly provides strong typings to papi handlers and most of the logic of integration it to the tramvai app is done by [@tramvai/module-server](references/modules/server.md)
21
+
22
+ ## Api
23
+
24
+ ### createPapiMethod
25
+
26
+ Options:
27
+ - `path` - specifies the path of url that current papi should handle. Required when specifying papi in tramvai DI, and not used when specified through file api.
28
+ - `method` - specified HTTP-method that is acceptable by current papi. By default, it equals to "all"
29
+ - `handler` - see [handler](#handler)
30
+ - `deps` - any DI deps that papi may require
31
+ - `options` - additional options that controls how current papi works
32
+ - `timeout` - timeout for executing current papi. If timeout exceeded request is resolved with Execution timeout error.
33
+
34
+ #### handler
35
+
36
+ Function that handles actual request and should return. It accepts the details of the request and should return the response
37
+
38
+ Details of the request are passed with first parameter when handler is called. You can see available properties in typings when specifying papi method. It should provide most of the data that might be required to handle the request.
39
+
40
+ Additionally, though `this` passed data that bounded to papi method:
41
+ - `deps` - resolved deps that were specified when defining papi method. Deps are resolved with [Child DI Container](concepts/di.md#container-is-a-child), so do not use it for creating caches as it won't work.
42
+ - `log` - instance of [LOGGER_TOKEN](references/modules/log.md#logger_token) bounded with current papi method
43
+
44
+ ### isPapiMethod
45
+
46
+ Type guard to check is passed object is papi handler
47
+
48
+ ## How to
49
+
50
+ ### Create simple papi
19
51
 
20
52
  ```tsx
21
53
  import { createPapiMethod } from '@tramvai/papi';
22
54
 
23
55
  export const papi = createPapiMethod({
56
+ // will handle requests to `/app/papi/my/papi` (actual url depends on setup)
24
57
  path: '/my/papi',
25
- method: 'post',
26
- async handler(deps) {
58
+ // only requests with GET http method will be handled
59
+ method: 'get',
60
+ // function to return response to the client
61
+ async handler() {
27
62
  return 'test';
28
63
  },
64
+ });
65
+ ```
66
+
67
+ ### Use parameters of the request
68
+
69
+ ```tsx
70
+ import { createPapiMethod } from '@tramvai/papi';
71
+
72
+ export const papi = createPapiMethod({
73
+ async handler({ query, cookies }) {
74
+ const { a, b } = query;
75
+ const { testCookie } = cookie;
76
+
77
+ return {
78
+ testCookie,
79
+ a,
80
+ b
81
+ };
82
+ },
83
+ });
84
+ ```
85
+
86
+ ### Settings headers and status
87
+
88
+ It can be done with [responseManager](references/tokens/common.md#responsemanager-tokens)
89
+
90
+ ```tsx
91
+ import { createPapiMethod } from '@tramvai/papi';
92
+
93
+ export const papi = createPapiMethod({
94
+ async handler({ responseManager }) {
95
+ responseManager.setHeader('content-type', 'text/html');
96
+ responseManager.setStatus(200);
97
+
98
+ return `<html>...</html>`;
99
+ },
100
+ });
101
+ ```
102
+
103
+ ### Use deps
104
+
105
+ ```tsx
106
+ import { createPapiMethod } from '@tramvai/papi';
107
+
108
+ export const papi = createPapiMethod({
109
+ async handler() {
110
+ const { cookieManager } = this.deps;
111
+
112
+ cookieManager.set({ name: 'b', value: 'abc', expires: 35 });
113
+
114
+ return "response";
115
+ },
29
116
  deps: {
30
- tinkoffApiService: TINKOFF_API_SERVICE,
117
+ cookieManager: COOKIE_MANAGER_TOKEN,
118
+ }
119
+ });
120
+ ```
121
+
122
+ ### Use cache with deps
123
+
124
+ Deps are resolved with ChildContainer that means they are getting resolved on every request in order to provide request specific info. To use any of deps that should outlive scope of the request you should use provider that was initialized with [scope=SINGLETON](concepts/di.md#root-container)
125
+
126
+ For example, if you want to use some papi specific cache you should create new token and provide the cache instance with that token and with option `scope: Scope.SINGLETON`
127
+
128
+ ```tsx
129
+ import { createToken, Scope, provide } from '@tinkoff/dippy'
130
+ import { createApp } from '@tramvai/core';
131
+ import { createPapiMethod } from '@tramvai/papi';
132
+ import { Cache, CREATE_CACHE_TOKEN } from '@tramvai/tokens-common';
133
+
134
+ export const PAPI_CACHE_TOKEN = createToken<Cache<string>>('app papi cache');
135
+
136
+ const app = createApp({
137
+ // ...,
138
+ providers: [
139
+ // ...,
140
+ provide({
141
+ provide: PAPI_CACHE_TOKEN,
142
+ scope: Scope.SINGLETON,
143
+ useFactory: ({ createCache }) => {
144
+ return createCache('memory');
145
+ },
146
+ deps: {
147
+ createCache: CREATE_CACHE_TOKEN,
148
+ },
149
+ })
150
+ ]
151
+ });
152
+
153
+ export const papi = createPapiMethod({
154
+ async handler({ query }) {
155
+ const { cacheKey } = query;
156
+ const { cache } = this.deps;
157
+
158
+ if (cache.has(cacheKey)) {
159
+ return cache.get(cacheKey);
160
+ }
161
+
162
+ const result = heavyComputation();
163
+
164
+ cache.set(cacheKey, result);
165
+
166
+ return result;
31
167
  },
168
+ deps: {
169
+ cache: PAPI_CACHE_TOKEN
170
+ }
32
171
  });
33
172
  ```
@@ -1,4 +1,5 @@
1
1
  import type { PapiParameters, Papi } from './types';
2
2
  export declare const PAPI_PARAMETERS = "__papi_parameters__";
3
- export declare const createPapiMethod: <Deps = any, Result = any>(papi: PapiParameters<Deps, Result>) => Papi<any, any>;
4
- export declare const getPapiParameters: <Deps = any, Result = any>(papi: Papi<Deps, Result>) => import("./types").NormalizedPapiParameters<Deps, Result>;
3
+ export declare const createPapiMethod: <Result = any, Deps = any>(papi: PapiParameters<Result, Deps>) => Papi<any, any>;
4
+ export declare const getPapiParameters: (papi: Papi<any, any>) => import("./types").NormalizedPapiParameters<any, any>;
5
+ export declare const isPapiMethod: (papi: any) => papi is Papi<any, any>;
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './createPapiMethod';
2
+ export * from './types';
@@ -0,0 +1,23 @@
1
+ const DEFAULT_TIMEOUT = 10000;
2
+ const PAPI_PARAMETERS = '__papi_parameters__';
3
+ const createPapiMethod = (papi) => {
4
+ const result = Object.assign(papi.handler, {
5
+ [PAPI_PARAMETERS]: {
6
+ ...papi,
7
+ options: {
8
+ timeout: DEFAULT_TIMEOUT,
9
+ ...papi.options,
10
+ },
11
+ method: papi.method || 'all',
12
+ },
13
+ });
14
+ return result;
15
+ };
16
+ const getPapiParameters = (papi) => {
17
+ return papi[PAPI_PARAMETERS];
18
+ };
19
+ const isPapiMethod = (papi) => {
20
+ return papi && !!getPapiParameters(papi);
21
+ };
22
+
23
+ export { PAPI_PARAMETERS, createPapiMethod, getPapiParameters, isPapiMethod };
package/lib/index.js ADDED
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const DEFAULT_TIMEOUT = 10000;
6
+ const PAPI_PARAMETERS = '__papi_parameters__';
7
+ const createPapiMethod = (papi) => {
8
+ const result = Object.assign(papi.handler, {
9
+ [PAPI_PARAMETERS]: {
10
+ ...papi,
11
+ options: {
12
+ timeout: DEFAULT_TIMEOUT,
13
+ ...papi.options,
14
+ },
15
+ method: papi.method || 'all',
16
+ },
17
+ });
18
+ return result;
19
+ };
20
+ const getPapiParameters = (papi) => {
21
+ return papi[PAPI_PARAMETERS];
22
+ };
23
+ const isPapiMethod = (papi) => {
24
+ return papi && !!getPapiParameters(papi);
25
+ };
26
+
27
+ exports.PAPI_PARAMETERS = PAPI_PARAMETERS;
28
+ exports.createPapiMethod = createPapiMethod;
29
+ exports.getPapiParameters = getPapiParameters;
30
+ exports.isPapiMethod = isPapiMethod;
package/lib/types.d.ts CHANGED
@@ -1,38 +1,44 @@
1
- import type { RequestHandler, ErrorRequestHandler } from 'express';
2
- import { Request, Response } from 'express';
1
+ /// <reference types="node" />
2
+ import type { IncomingHttpHeaders } from 'http';
3
+ import type { ProvideDepsIterator } from '@tinkoff/dippy';
4
+ import type { Url } from '@tinkoff/url';
5
+ import type { REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN, Logger } from '@tramvai/tokens-common';
3
6
  import type { PAPI_PARAMETERS } from './createPapiMethod';
4
- declare module 'express-serve-static-core' {
5
- interface Response {
6
- papiState: Record<string, any>;
7
- }
8
- }
9
- export { Request, Response };
10
- export declare type Handler = ErrorRequestHandler | RequestHandler;
11
7
  export declare type Method = 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head';
12
- export declare type Middleware = Handler | Array<Handler> | null;
13
8
  export interface Options {
14
9
  /**
15
- * Если включить эту опцию, свойство `request.url` не будет содержать путь текущего papi обработчика
10
+ * Timeout in ms for handling request. If timeout is exceeded the response is resolved with 500 status.
11
+ * **Note**: handler will still continues its execution possibly leading to heavy resource consumption
12
+ *
13
+ * @default 10000
16
14
  */
17
- trimPapiUrl?: boolean;
15
+ timeout?: number;
18
16
  }
19
- export declare type DepsWithDefaults<Deps> = Deps & {
20
- req: Request;
21
- res: Response;
22
- };
23
- export declare type NormalizedHandler<Deps, Result> = (deps: DepsWithDefaults<Deps>) => Result;
24
- export interface PapiParameters<Deps, Result> {
25
- path: string;
17
+ export interface PapiHandlerOptions {
18
+ headers: IncomingHttpHeaders;
19
+ cookies: Record<string, string>;
20
+ params: Record<string, string>;
21
+ body: any;
22
+ parsedUrl: Url;
23
+ requestManager: typeof REQUEST_MANAGER_TOKEN;
24
+ responseManager: typeof RESPONSE_MANAGER_TOKEN;
25
+ }
26
+ export interface PapiHandlerContext<Deps> {
27
+ log: Logger;
28
+ deps: ProvideDepsIterator<Deps>;
29
+ }
30
+ export declare type NormalizedHandler<Result, Deps> = (this: PapiHandlerContext<Deps>, options: PapiHandlerOptions) => Result;
31
+ export interface PapiParameters<Result, Deps> {
32
+ path?: string;
26
33
  method?: Method;
27
- handler: NormalizedHandler<Deps, Result> | ((req: Request, res: Response, deps: DepsWithDefaults<Deps>) => Result);
34
+ handler: NormalizedHandler<Result, Deps>;
28
35
  options?: Options;
29
36
  deps?: Deps;
30
37
  }
31
- export declare type NormalizedPapiParameters<Deps, Result> = Required<PapiParameters<Deps, Result>> & {
32
- handler: NormalizedHandler<Deps, Result>;
38
+ export declare type NormalizedPapiParameters<Result, Deps> = Required<PapiParameters<Result, Deps>> & {
39
+ handler: NormalizedHandler<Result, Deps>;
40
+ options: Required<Options>;
33
41
  };
34
- export declare type Papi<Deps = any, Result = any> = NormalizedHandler<Deps, Result> & {
42
+ export declare type Papi<Result = any, Deps = any> = NormalizedHandler<Deps, Result> & {
35
43
  [PAPI_PARAMETERS]: NormalizedPapiParameters<Deps, Result>;
36
44
  };
37
- export declare type MiddlewareCreator = (papi: NormalizedPapiParameters<any, any>) => Middleware;
38
- export declare type Chain = Array<MiddlewareCreator>;
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@tramvai/papi",
3
- "version": "2.22.0",
3
+ "version": "2.24.3",
4
4
  "description": "",
5
- "main": "lib/server.js",
6
- "browser": "lib/browser.js",
7
- "typings": "lib/server.d.ts",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
8
7
  "files": [
9
8
  "lib",
10
9
  "__migrations__"
@@ -19,22 +18,12 @@
19
18
  "build-for-publish": "true"
20
19
  },
21
20
  "dependencies": {
21
+ "@tinkoff/url": "0.8.2",
22
22
  "@tinkoff/utils": "^2.1.2",
23
- "@types/connect-timeout": "^0.0.34",
24
- "ajv": "^6.5.4",
25
- "body-parser": "^1.18.3",
26
- "connect-timeout": "^1.9.0",
27
- "cookie-parser": "^1.4.5",
28
- "express": "^4.17.1",
29
23
  "tslib": "^2.0.3"
30
24
  },
31
- "devDependencies": {
32
- "@types/connect-timeout": "^0.0.34",
33
- "@types/cookie-parser": "^1.4.2",
34
- "@types/express": "^4.17.9",
35
- "supertest": "^3.3.0"
36
- },
25
+ "devDependencies": {},
37
26
  "gitHead": "8e826a214c87b188fc4d254cdd8f2a2b2c55f3a8",
38
- "module": "lib/server.es.js",
27
+ "module": "lib/index.es.js",
39
28
  "license": "Apache-2.0"
40
29
  }
package/lib/browser.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export { createPapiMethod, getPapiParameters } from './createPapiMethod';
2
- export * from './types';
package/lib/browser.js DELETED
@@ -1,22 +0,0 @@
1
- const PAPI_PARAMETERS = '__papi_parameters__';
2
- const createPapiMethod = (papi) => {
3
- const handler = papi.handler.length > 1
4
- ? (deps) => {
5
- return papi.handler(deps === null || deps === void 0 ? void 0 : deps.req, deps === null || deps === void 0 ? void 0 : deps.res, deps);
6
- }
7
- : papi.handler;
8
- const result = Object.assign(handler, {
9
- [PAPI_PARAMETERS]: {
10
- ...papi,
11
- handler,
12
- options: papi.options || {},
13
- method: papi.method || 'all',
14
- },
15
- });
16
- return result;
17
- };
18
- const getPapiParameters = (papi) => {
19
- return papi[PAPI_PARAMETERS];
20
- };
21
-
22
- export { createPapiMethod, getPapiParameters };
package/lib/create.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { RequestHandler } from 'express';
2
- import type { Chain, Papi } from './types';
3
- export declare function create(papis: Papi[], chain: Chain): RequestHandler;
@@ -1,10 +0,0 @@
1
- import type { MiddlewareCreator } from '../types';
2
- export interface Options {
3
- limit?: string | number;
4
- }
5
- declare module '../types' {
6
- interface Options {
7
- disableBodyCookie?: boolean;
8
- }
9
- }
10
- export declare const body: (options?: Options | undefined) => MiddlewareCreator;
@@ -1,7 +0,0 @@
1
- import type { MiddlewareCreator } from '../types';
2
- declare module '../types' {
3
- interface Options {
4
- disableBodyCookie?: boolean;
5
- }
6
- }
7
- export declare const cookie: () => MiddlewareCreator;
@@ -1,7 +0,0 @@
1
- import type { MiddlewareCreator } from '../types';
2
- export interface Options {
3
- logger?: (name: string) => {
4
- error: (data: any) => void;
5
- };
6
- }
7
- export declare const error: ({ logger }?: Options) => MiddlewareCreator;
@@ -1,6 +0,0 @@
1
- import type { Request, Response } from 'express';
2
- import type { MiddlewareCreator, PapiParameters } from '../types';
3
- export interface Options {
4
- fillState: (papi: PapiParameters<any, any>) => (req: Request, res: Response) => any;
5
- }
6
- export declare const fillPapiState: ({ fillState }: Options) => MiddlewareCreator;
@@ -1,7 +0,0 @@
1
- import type { MiddlewareCreator } from '../types';
2
- declare module '../types' {
3
- interface Options {
4
- respond?: boolean;
5
- }
6
- }
7
- export declare const handler: () => MiddlewareCreator;
@@ -1,7 +0,0 @@
1
- export { body } from './body';
2
- export { cookie } from './cookie';
3
- export { handler } from './handler';
4
- export { error } from './error';
5
- export { timeout } from './timeout';
6
- export { validate } from './validate';
7
- export { fillPapiState } from './fillPapiState';
@@ -1,10 +0,0 @@
1
- import type { MiddlewareCreator } from '../types';
2
- export interface Options {
3
- timeout?: string;
4
- }
5
- declare module '../types' {
6
- interface Options {
7
- timeout?: string;
8
- }
9
- }
10
- export declare const timeout: (options?: Options | undefined) => MiddlewareCreator;
@@ -1,15 +0,0 @@
1
- import Ajv from 'ajv';
2
- import type { MiddlewareCreator } from '../types';
3
- export interface Options {
4
- validator?: Ajv.Ajv;
5
- }
6
- declare module '../types' {
7
- interface Options {
8
- schema?: {
9
- params?: {};
10
- query?: {};
11
- body?: {};
12
- };
13
- }
14
- }
15
- export declare const validate: (options?: Options | undefined) => MiddlewareCreator;
package/lib/server.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import * as middlewares from './middlewares';
2
- export { create } from './create';
3
- export { createPapiMethod, getPapiParameters } from './createPapiMethod';
4
- export * from './types';
5
- export { middlewares };
package/lib/server.es.js DELETED
@@ -1,239 +0,0 @@
1
- import bodyParser from 'body-parser';
2
- import cookieParser from 'cookie-parser';
3
- import isNumber from '@tinkoff/utils/is/number';
4
- import connectTimeout from 'connect-timeout';
5
- import Ajv from 'ajv';
6
- import flatten from '@tinkoff/utils/array/flatten';
7
- import isFunction from '@tinkoff/utils/is/function';
8
- import { Router } from 'express';
9
-
10
- const body = (options) => {
11
- var _a;
12
- // PF-12590
13
- const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : '2mb';
14
- return (papi) => {
15
- if (papi.options.disableBodyCookie) {
16
- return null;
17
- }
18
- return [
19
- bodyParser.text({ limit }),
20
- bodyParser.urlencoded({
21
- limit,
22
- extended: true,
23
- }),
24
- bodyParser.json({
25
- limit,
26
- type: ['application/json', 'application/csp-report'],
27
- }),
28
- ];
29
- };
30
- };
31
-
32
- const cookie = () => {
33
- return (papi) => {
34
- if (papi.options.disableBodyCookie) {
35
- return null;
36
- }
37
- return cookieParser();
38
- };
39
- };
40
-
41
- const handler = () => {
42
- return (papi) => {
43
- return async (req, res, next) => {
44
- try {
45
- const payload = await papi.handler(res.papiState);
46
- // Do nothing if response already has been sent (i.e. timeout error)
47
- if (papi.options.respond === false || res.headersSent) {
48
- return;
49
- }
50
- res.json({
51
- resultCode: 'OK',
52
- payload,
53
- });
54
- return;
55
- }
56
- catch (err) {
57
- next(err);
58
- return;
59
- }
60
- next();
61
- };
62
- };
63
- };
64
-
65
- const error = ({ logger } = {}) => {
66
- return (papi) => {
67
- const log = logger === null || logger === void 0 ? void 0 : logger(papi.path);
68
- return (err, req, res, next) => {
69
- // Do nothing if respone already has been sent (i.e. timeout error)
70
- if (res.headersSent) {
71
- return;
72
- }
73
- log === null || log === void 0 ? void 0 : log.error({
74
- error: err,
75
- event: 'papiError',
76
- path: papi.path,
77
- method: papi.method,
78
- });
79
- res.status(isNumber(err.status) ? err.status : 500);
80
- // TODO: We need a general application-wide error factory which will let us to
81
- // decide what can be exposed in the response
82
- res.json({
83
- resultCode: 'INTERNAL_ERROR',
84
- errorMessage: 'internal error',
85
- });
86
- };
87
- };
88
- };
89
-
90
- const timeout = (options) => {
91
- var _a;
92
- const defaultTimeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : '10s';
93
- return (papi) => {
94
- const papiTimeout = papi.options.timeout || defaultTimeout;
95
- return connectTimeout(papiTimeout);
96
- };
97
- };
98
-
99
- const KEYS = ['params', 'query', 'body'];
100
- const validate = (options) => {
101
- var _a;
102
- const validator = (_a = options === null || options === void 0 ? void 0 : options.validator) !== null && _a !== void 0 ? _a : new Ajv({ coerceTypes: true });
103
- return (papi) => {
104
- const { schema } = papi.options;
105
- if (!schema || !KEYS.some((key) => schema[key])) {
106
- return null;
107
- }
108
- return async (req, res, next) => {
109
- for (const key of KEYS) {
110
- const compiledValidator = schema[key]
111
- ? validator.compile(schema[key])
112
- : () => true;
113
- const data = req[key];
114
- // console.log(data, schema[key], validate, validate(data));
115
- if (!compiledValidator(data)) {
116
- // console.log('validation failed');
117
- // TODO: Should be replaced with general application-wide error factory
118
- const err = new Error(`invalid ${key}`);
119
- err.status = 400;
120
- err.expose = true;
121
- err.errors = compiledValidator.errors;
122
- return next(err);
123
- }
124
- }
125
- next();
126
- };
127
- };
128
- };
129
-
130
- const fillPapiState = ({ fillState }) => {
131
- return (papi) => {
132
- const fn = fillState(papi);
133
- return async (req, res, next) => {
134
- try {
135
- res.papiState = await fn(req, res);
136
- }
137
- catch (err) {
138
- next(err);
139
- return;
140
- }
141
- next();
142
- };
143
- };
144
- };
145
-
146
- var index = {
147
- __proto__: null,
148
- body: body,
149
- cookie: cookie,
150
- handler: handler,
151
- error: error,
152
- timeout: timeout,
153
- validate: validate,
154
- fillPapiState: fillPapiState
155
- };
156
-
157
- const PAPI_PARAMETERS = '__papi_parameters__';
158
- const createPapiMethod = (papi) => {
159
- const handler = papi.handler.length > 1
160
- ? (deps) => {
161
- return papi.handler(deps === null || deps === void 0 ? void 0 : deps.req, deps === null || deps === void 0 ? void 0 : deps.res, deps);
162
- }
163
- : papi.handler;
164
- const result = Object.assign(handler, {
165
- [PAPI_PARAMETERS]: {
166
- ...papi,
167
- handler,
168
- options: papi.options || {},
169
- method: papi.method || 'all',
170
- },
171
- });
172
- return result;
173
- };
174
- const getPapiParameters = (papi) => {
175
- return papi[PAPI_PARAMETERS];
176
- };
177
-
178
- function convert(papis) {
179
- const paths = new Set();
180
- const result = [];
181
- for (const papi of papis) {
182
- const papiParams = getPapiParameters(papi);
183
- if (!papiParams) {
184
- throw new Error(`papi should be created using createPapiMethod from @tramvai/papi,
185
- got: ${JSON.stringify(papi)}`);
186
- }
187
- const key = `${papiParams.method} ${papiParams.path}`;
188
- if (paths.has(key)) {
189
- throw new Error(`papi: route '${key}' already registered`);
190
- }
191
- result.push(papiParams);
192
- paths.add(key);
193
- }
194
- return result;
195
- }
196
- function create(papis, chain) {
197
- const papiParams = convert(papis);
198
- const router = Router({
199
- strict: false,
200
- });
201
- router.use((req, res, next) => {
202
- res.setHeader('cache-control', 'no-cache,no-store,max-age=0,must-revalidate');
203
- next();
204
- });
205
- for (const papi of papiParams) {
206
- const middlewaresMaybeArray = chain.map((createMiddleware) => createMiddleware(papi));
207
- const middlewares = flatten(middlewaresMaybeArray).filter(isFunction);
208
- if (papi.options.trimPapiUrl) {
209
- const subRouter = Router({
210
- strict: false,
211
- });
212
- subRouter[papi.method]('*', middlewares);
213
- // express позволяет делать вложенный роутинг https://gist.github.com/zcaceres/f38b208a492e4dcd45f487638eff716c,
214
- // в этом случае в `request.url` будет находиться не значение `papi.path`, а просто `/`,
215
- // полный путь при необходимости можно найти в `request.baseUrl`
216
- router.use(papi.path, subRouter);
217
- }
218
- else {
219
- router[papi.method](papi.path, middlewares);
220
- }
221
- }
222
- // Handles non existed routes and twice async next() calls where second call done
223
- // without arguments or with 'route' as first argument
224
- router.use((req, res) => {
225
- // Do nothing if respone already has been sent (i.e. timeout error)
226
- if (res.headersSent) {
227
- return;
228
- }
229
- res.status(404);
230
- // Just for backward compatibility. Maybe we can remove it.
231
- res.json({
232
- resultCode: 'Not found',
233
- payload: {},
234
- });
235
- });
236
- return router;
237
- }
238
-
239
- export { create, createPapiMethod, getPapiParameters, index as middlewares };
package/lib/server.js DELETED
@@ -1,256 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var bodyParser = require('body-parser');
6
- var cookieParser = require('cookie-parser');
7
- var isNumber = require('@tinkoff/utils/is/number');
8
- var connectTimeout = require('connect-timeout');
9
- var Ajv = require('ajv');
10
- var flatten = require('@tinkoff/utils/array/flatten');
11
- var isFunction = require('@tinkoff/utils/is/function');
12
- var express = require('express');
13
-
14
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
15
-
16
- var bodyParser__default = /*#__PURE__*/_interopDefaultLegacy(bodyParser);
17
- var cookieParser__default = /*#__PURE__*/_interopDefaultLegacy(cookieParser);
18
- var isNumber__default = /*#__PURE__*/_interopDefaultLegacy(isNumber);
19
- var connectTimeout__default = /*#__PURE__*/_interopDefaultLegacy(connectTimeout);
20
- var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv);
21
- var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
22
- var isFunction__default = /*#__PURE__*/_interopDefaultLegacy(isFunction);
23
-
24
- const body = (options) => {
25
- var _a;
26
- // PF-12590
27
- const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : '2mb';
28
- return (papi) => {
29
- if (papi.options.disableBodyCookie) {
30
- return null;
31
- }
32
- return [
33
- bodyParser__default["default"].text({ limit }),
34
- bodyParser__default["default"].urlencoded({
35
- limit,
36
- extended: true,
37
- }),
38
- bodyParser__default["default"].json({
39
- limit,
40
- type: ['application/json', 'application/csp-report'],
41
- }),
42
- ];
43
- };
44
- };
45
-
46
- const cookie = () => {
47
- return (papi) => {
48
- if (papi.options.disableBodyCookie) {
49
- return null;
50
- }
51
- return cookieParser__default["default"]();
52
- };
53
- };
54
-
55
- const handler = () => {
56
- return (papi) => {
57
- return async (req, res, next) => {
58
- try {
59
- const payload = await papi.handler(res.papiState);
60
- // Do nothing if response already has been sent (i.e. timeout error)
61
- if (papi.options.respond === false || res.headersSent) {
62
- return;
63
- }
64
- res.json({
65
- resultCode: 'OK',
66
- payload,
67
- });
68
- return;
69
- }
70
- catch (err) {
71
- next(err);
72
- return;
73
- }
74
- next();
75
- };
76
- };
77
- };
78
-
79
- const error = ({ logger } = {}) => {
80
- return (papi) => {
81
- const log = logger === null || logger === void 0 ? void 0 : logger(papi.path);
82
- return (err, req, res, next) => {
83
- // Do nothing if respone already has been sent (i.e. timeout error)
84
- if (res.headersSent) {
85
- return;
86
- }
87
- log === null || log === void 0 ? void 0 : log.error({
88
- error: err,
89
- event: 'papiError',
90
- path: papi.path,
91
- method: papi.method,
92
- });
93
- res.status(isNumber__default["default"](err.status) ? err.status : 500);
94
- // TODO: We need a general application-wide error factory which will let us to
95
- // decide what can be exposed in the response
96
- res.json({
97
- resultCode: 'INTERNAL_ERROR',
98
- errorMessage: 'internal error',
99
- });
100
- };
101
- };
102
- };
103
-
104
- const timeout = (options) => {
105
- var _a;
106
- const defaultTimeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : '10s';
107
- return (papi) => {
108
- const papiTimeout = papi.options.timeout || defaultTimeout;
109
- return connectTimeout__default["default"](papiTimeout);
110
- };
111
- };
112
-
113
- const KEYS = ['params', 'query', 'body'];
114
- const validate = (options) => {
115
- var _a;
116
- const validator = (_a = options === null || options === void 0 ? void 0 : options.validator) !== null && _a !== void 0 ? _a : new Ajv__default["default"]({ coerceTypes: true });
117
- return (papi) => {
118
- const { schema } = papi.options;
119
- if (!schema || !KEYS.some((key) => schema[key])) {
120
- return null;
121
- }
122
- return async (req, res, next) => {
123
- for (const key of KEYS) {
124
- const compiledValidator = schema[key]
125
- ? validator.compile(schema[key])
126
- : () => true;
127
- const data = req[key];
128
- // console.log(data, schema[key], validate, validate(data));
129
- if (!compiledValidator(data)) {
130
- // console.log('validation failed');
131
- // TODO: Should be replaced with general application-wide error factory
132
- const err = new Error(`invalid ${key}`);
133
- err.status = 400;
134
- err.expose = true;
135
- err.errors = compiledValidator.errors;
136
- return next(err);
137
- }
138
- }
139
- next();
140
- };
141
- };
142
- };
143
-
144
- const fillPapiState = ({ fillState }) => {
145
- return (papi) => {
146
- const fn = fillState(papi);
147
- return async (req, res, next) => {
148
- try {
149
- res.papiState = await fn(req, res);
150
- }
151
- catch (err) {
152
- next(err);
153
- return;
154
- }
155
- next();
156
- };
157
- };
158
- };
159
-
160
- var index = {
161
- __proto__: null,
162
- body: body,
163
- cookie: cookie,
164
- handler: handler,
165
- error: error,
166
- timeout: timeout,
167
- validate: validate,
168
- fillPapiState: fillPapiState
169
- };
170
-
171
- const PAPI_PARAMETERS = '__papi_parameters__';
172
- const createPapiMethod = (papi) => {
173
- const handler = papi.handler.length > 1
174
- ? (deps) => {
175
- return papi.handler(deps === null || deps === void 0 ? void 0 : deps.req, deps === null || deps === void 0 ? void 0 : deps.res, deps);
176
- }
177
- : papi.handler;
178
- const result = Object.assign(handler, {
179
- [PAPI_PARAMETERS]: {
180
- ...papi,
181
- handler,
182
- options: papi.options || {},
183
- method: papi.method || 'all',
184
- },
185
- });
186
- return result;
187
- };
188
- const getPapiParameters = (papi) => {
189
- return papi[PAPI_PARAMETERS];
190
- };
191
-
192
- function convert(papis) {
193
- const paths = new Set();
194
- const result = [];
195
- for (const papi of papis) {
196
- const papiParams = getPapiParameters(papi);
197
- if (!papiParams) {
198
- throw new Error(`papi should be created using createPapiMethod from @tramvai/papi,
199
- got: ${JSON.stringify(papi)}`);
200
- }
201
- const key = `${papiParams.method} ${papiParams.path}`;
202
- if (paths.has(key)) {
203
- throw new Error(`papi: route '${key}' already registered`);
204
- }
205
- result.push(papiParams);
206
- paths.add(key);
207
- }
208
- return result;
209
- }
210
- function create(papis, chain) {
211
- const papiParams = convert(papis);
212
- const router = express.Router({
213
- strict: false,
214
- });
215
- router.use((req, res, next) => {
216
- res.setHeader('cache-control', 'no-cache,no-store,max-age=0,must-revalidate');
217
- next();
218
- });
219
- for (const papi of papiParams) {
220
- const middlewaresMaybeArray = chain.map((createMiddleware) => createMiddleware(papi));
221
- const middlewares = flatten__default["default"](middlewaresMaybeArray).filter(isFunction__default["default"]);
222
- if (papi.options.trimPapiUrl) {
223
- const subRouter = express.Router({
224
- strict: false,
225
- });
226
- subRouter[papi.method]('*', middlewares);
227
- // express позволяет делать вложенный роутинг https://gist.github.com/zcaceres/f38b208a492e4dcd45f487638eff716c,
228
- // в этом случае в `request.url` будет находиться не значение `papi.path`, а просто `/`,
229
- // полный путь при необходимости можно найти в `request.baseUrl`
230
- router.use(papi.path, subRouter);
231
- }
232
- else {
233
- router[papi.method](papi.path, middlewares);
234
- }
235
- }
236
- // Handles non existed routes and twice async next() calls where second call done
237
- // without arguments or with 'route' as first argument
238
- router.use((req, res) => {
239
- // Do nothing if respone already has been sent (i.e. timeout error)
240
- if (res.headersSent) {
241
- return;
242
- }
243
- res.status(404);
244
- // Just for backward compatibility. Maybe we can remove it.
245
- res.json({
246
- resultCode: 'Not found',
247
- payload: {},
248
- });
249
- });
250
- return router;
251
- }
252
-
253
- exports.create = create;
254
- exports.createPapiMethod = createPapiMethod;
255
- exports.getPapiParameters = getPapiParameters;
256
- exports.middlewares = index;