@visulima/api-platform 1.0.2 → 1.0.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/CHANGELOG.md +16 -0
- package/bin/index.js +46 -0
- package/dist/{chunk-XXZ56SKG.mjs → chunk-2M45MXC2.mjs} +1 -2
- package/dist/chunk-2M45MXC2.mjs.map +1 -0
- package/dist/chunk-3Y3YBDRS.mjs +284 -0
- package/dist/chunk-3Y3YBDRS.mjs.map +1 -0
- package/dist/chunk-NY75KGAH.js +284 -0
- package/dist/chunk-NY75KGAH.js.map +1 -0
- package/dist/{chunk-AJKZCWFG.js → chunk-S46GRBOS.js} +1 -2
- package/dist/chunk-S46GRBOS.js.map +1 -0
- package/dist/index-server.d.ts +2 -0
- package/dist/index-server.js +9 -10
- package/dist/index-server.js.map +1 -1
- package/dist/index-server.mjs +7 -8
- package/dist/index-server.mjs.map +1 -1
- package/dist/next/cli/index.js +8 -8
- package/dist/next/cli/index.js.map +1 -1
- package/dist/next/cli/index.mjs +8 -8
- package/dist/next/cli/index.mjs.map +1 -1
- package/dist/next/index-browser.js +2 -2
- package/dist/next/index-browser.mjs +1 -1
- package/dist/next/index-server.d.ts +9 -5
- package/dist/next/index-server.js +4 -4
- package/dist/next/index-server.mjs +2 -2
- package/next/cli/package.json +12 -3
- package/next/package.json +9 -0
- package/package.json +20 -20
- package/recipes/api/swagger.ts +12 -0
- package/recipes/pages/redoc-ui.tsx +5 -0
- package/recipes/pages/swagger-ui.tsx +5 -0
- package/dist/chunk-2LATTLUM.mjs +0 -166
- package/dist/chunk-2LATTLUM.mjs.map +0 -1
- package/dist/chunk-AJKZCWFG.js.map +0 -1
- package/dist/chunk-S7GUPAL4.js +0 -166
- package/dist/chunk-S7GUPAL4.js.map +0 -1
- package/dist/chunk-XXZ56SKG.mjs.map +0 -1
- package/src/connect/create-node-router.ts +0 -44
- package/src/connect/handler.ts +0 -46
- package/src/connect/middleware/cors-middleware.ts +0 -10
- package/src/connect/middleware/http-header-normalizer.ts +0 -93
- package/src/connect/middleware/rate-limiter-middleware.ts +0 -43
- package/src/connect/middleware/serializers-middleware.ts +0 -121
- package/src/connect/serializers/types.d.ts +0 -1
- package/src/connect/serializers/xml.ts +0 -13
- package/src/connect/serializers/yaml.ts +0 -7
- package/src/error-handler/jsonapi-error-handler.ts +0 -46
- package/src/error-handler/problem-error-handler.ts +0 -44
- package/src/error-handler/types.d.ts +0 -14
- package/src/error-handler/utils.ts +0 -39
- package/src/index-browser.tsx +0 -1
- package/src/index-server.ts +0 -75
- package/src/next/cli/index.ts +0 -2
- package/src/next/cli/list/api-route-file-parser.ts +0 -74
- package/src/next/cli/list/collect-api-route-files.ts +0 -42
- package/src/next/cli/list/list-command.ts +0 -105
- package/src/next/cli/list/routes-render.ts +0 -62
- package/src/next/cli/list/types.d.ts +0 -1
- package/src/next/index-browser.tsx +0 -3
- package/src/next/index-server.ts +0 -6
- package/src/next/routes/api/swagger.ts +0 -23
- package/src/next/routes/pages/swagger/get-static-properties-swagger.ts +0 -32
- package/src/next/routes/pages/swagger/redoc.tsx +0 -35
- package/src/next/routes/pages/swagger/swagger.tsx +0 -44
- package/src/next/webpack/with-open-api.ts +0 -63
- package/src/swagger/extend-swagger-spec.ts +0 -167
- package/src/swagger/swagger-handler.ts +0 -83
- package/src/utils.ts +0 -37
- package/src/zod/date-in-schema.ts +0 -57
- package/src/zod/date-out-schema.ts +0 -41
- package/src/zod/index.ts +0 -9
package/src/connect/handler.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FunctionLike, Nextable, Route, ValueOrPromise,
|
|
3
|
-
} from "@visulima/connect";
|
|
4
|
-
import createHttpError from "http-errors";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
import JsonapiErrorHandler from "../error-handler/jsonapi-error-handler";
|
|
8
|
-
import ProblemErrorHandler from "../error-handler/problem-error-handler";
|
|
9
|
-
import type { ErrorHandler, ErrorHandlers } from "../error-handler/types";
|
|
10
|
-
|
|
11
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping,max-len
|
|
12
|
-
export const onError = <Request extends IncomingMessage, Response extends ServerResponse>(errorHandlers: ErrorHandlers, showTrace: boolean) => async (error: unknown, request: Request, response: Response): Promise<void> => {
|
|
13
|
-
const apiFormat: string = request.headers.accept as string;
|
|
14
|
-
|
|
15
|
-
let errorHandler: ErrorHandler = ProblemErrorHandler;
|
|
16
|
-
|
|
17
|
-
if (apiFormat === "application/vnd.api+json") {
|
|
18
|
-
errorHandler = JsonapiErrorHandler;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
22
|
-
for (const { regex, handler } of errorHandlers) {
|
|
23
|
-
if (regex.test(apiFormat)) {
|
|
24
|
-
errorHandler = handler;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// eslint-disable-next-line no-param-reassign
|
|
30
|
-
(error as { expose: boolean } & Error).expose = showTrace;
|
|
31
|
-
|
|
32
|
-
errorHandler(error, request, response);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const onNoMatch: <Request extends IncomingMessage, Response extends ServerResponse>(
|
|
36
|
-
request: Request,
|
|
37
|
-
response: Response,
|
|
38
|
-
routes: Route<Nextable<FunctionLike>>[],
|
|
39
|
-
) => ValueOrPromise<void> = async (request, response, routes) => {
|
|
40
|
-
const uniqueMethods = [...new Set(routes.map((route) => route.method))].join(", ");
|
|
41
|
-
|
|
42
|
-
response.setHeader("Allow", uniqueMethods);
|
|
43
|
-
response.statusCode = 405;
|
|
44
|
-
|
|
45
|
-
throw createHttpError(405, `No route with [${request.method}] method found.`);
|
|
46
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { expressWrapper } from "@visulima/connect";
|
|
2
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
-
import type { CorsOptions, CorsOptionsDelegate } from "cors";
|
|
4
|
-
import cors from "cors";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line max-len
|
|
8
|
-
const corsMiddleware = <Request extends IncomingMessage, Response extends ServerResponse>(options?: CorsOptions | CorsOptionsDelegate) => expressWrapper<Request, Response>(cors(options));
|
|
9
|
-
|
|
10
|
-
export default corsMiddleware;
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
|
3
|
-
|
|
4
|
-
const exceptions = {
|
|
5
|
-
alpn: "ALPN",
|
|
6
|
-
"c-pep": "C-PEP",
|
|
7
|
-
"c-pep-info": "C-PEP-Info",
|
|
8
|
-
"caldav-timezones": "CalDAV-Timezones",
|
|
9
|
-
"content-id": "Content-ID",
|
|
10
|
-
"content-md5": "Content-MD5",
|
|
11
|
-
dasl: "DASL",
|
|
12
|
-
dav: "DAV",
|
|
13
|
-
dnt: "DNT",
|
|
14
|
-
etag: "ETag",
|
|
15
|
-
getprofile: "GetProfile",
|
|
16
|
-
"http2-settings": "HTTP2-Settings",
|
|
17
|
-
"last-event-id": "Last-Event-ID",
|
|
18
|
-
"mime-version": "MIME-Version",
|
|
19
|
-
"optional-www-authenticate": "Optional-WWW-Authenticate",
|
|
20
|
-
"sec-websocket-accept": "Sec-WebSocket-Accept",
|
|
21
|
-
"sec-websocket-extensions": "Sec-WebSocket-Extensions",
|
|
22
|
-
"sec-webSocket-key": "Sec-WebSocket-Key",
|
|
23
|
-
"sec-webSocket-protocol": "Sec-WebSocket-Protocol",
|
|
24
|
-
"sec-webSocket-version": "Sec-WebSocket-Version",
|
|
25
|
-
slug: "SLUG",
|
|
26
|
-
tcn: "TCN",
|
|
27
|
-
te: "TE",
|
|
28
|
-
ttl: "TTL",
|
|
29
|
-
"www-authenticate": "WWW-Authenticate",
|
|
30
|
-
"x-att-deviceid": "X-ATT-DeviceId",
|
|
31
|
-
"x-dnsprefetch-control": "X-DNSPrefetch-Control",
|
|
32
|
-
"x-uidh": "X-UIDH",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const normalizeHeaderKey = (key: string, canonical: boolean) => {
|
|
36
|
-
const lowerCaseKey = key.toLowerCase();
|
|
37
|
-
|
|
38
|
-
if (!canonical) {
|
|
39
|
-
return lowerCaseKey;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (exceptions[lowerCaseKey as keyof typeof exceptions]) {
|
|
43
|
-
return exceptions[lowerCaseKey as keyof typeof exceptions];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
lowerCaseKey
|
|
48
|
-
.split("-")
|
|
49
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
50
|
-
.map((text) => text[0]?.toUpperCase() + text.slice(1))
|
|
51
|
-
.join("-")
|
|
52
|
-
);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const defaults = {
|
|
56
|
-
canonical: false,
|
|
57
|
-
normalizeHeaderKey,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* HTTP headers are case-insensitive.
|
|
62
|
-
* That's why NodeJS makes them lower case by default.
|
|
63
|
-
* While sensible, sometimes, for example for compatibility reasons, you might need them in their more common form.
|
|
64
|
-
*/
|
|
65
|
-
const httpHeaderNormalizerMiddleware = (options_?: { canonical?: boolean; normalizeHeaderKey?: (key: string, canonical: boolean) => string }) => {
|
|
66
|
-
const options = { ...defaults, ...options_ };
|
|
67
|
-
|
|
68
|
-
return async <Request extends IncomingMessage>(request: Request, _: any, next: NextHandler) => {
|
|
69
|
-
if (request.headers) {
|
|
70
|
-
const rawHeaders: IncomingHttpHeaders = {};
|
|
71
|
-
const headers: IncomingHttpHeaders = {};
|
|
72
|
-
|
|
73
|
-
Object.keys(request.headers).forEach((key) => {
|
|
74
|
-
rawHeaders[key] = request.headers[key];
|
|
75
|
-
|
|
76
|
-
const normalizedKey = options.normalizeHeaderKey(key, options.canonical);
|
|
77
|
-
|
|
78
|
-
if (typeof normalizedKey !== "undefined") {
|
|
79
|
-
headers[normalizedKey] = request.headers[key];
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
request.headers = headers;
|
|
84
|
-
// @TODO at type `request.rawHeaders` to global scope
|
|
85
|
-
// @ts-ignore
|
|
86
|
-
request.rawHeaders = rawHeaders;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return next();
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export default httpHeaderNormalizerMiddleware;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import createHttpError from "http-errors";
|
|
3
|
-
import type { NextApiResponse } from "next";
|
|
4
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
5
|
-
import type { RateLimiterAbstract, RateLimiterRes } from "rate-limiter-flexible";
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line max-len
|
|
8
|
-
const getIP: (request: IncomingMessage & { ip?: string }) => string | undefined = (request) => request?.ip
|
|
9
|
-
|| (request.headers["x-forwarded-for"] as string | undefined)
|
|
10
|
-
|| (request.headers["x-real-ip"] as string | undefined)
|
|
11
|
-
|| request.connection.remoteAddress;
|
|
12
|
-
|
|
13
|
-
type HeaderValue = string | number | ReadonlyArray<string>;
|
|
14
|
-
|
|
15
|
-
// eslint-disable-next-line max-len
|
|
16
|
-
const rateLimiterMiddleware = (rateLimiter: RateLimiterAbstract, headers?: (limiterResponse: RateLimiterRes) => { [key: string]: HeaderValue }) => async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response | NextApiResponse, next: NextHandler) => {
|
|
17
|
-
const ip = getIP(request);
|
|
18
|
-
|
|
19
|
-
if (typeof ip === "undefined") {
|
|
20
|
-
throw createHttpError(400, "Missing IP");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const limiter = await rateLimiter.consume(ip);
|
|
25
|
-
|
|
26
|
-
const mergedHeaders: { [key: string]: HeaderValue } = {
|
|
27
|
-
"Retry-After": Math.round(limiter.msBeforeNext / 1000) || 1,
|
|
28
|
-
"X-RateLimit-Remaining": limiter.remainingPoints,
|
|
29
|
-
"X-RateLimit-Reset": new Date(Date.now() + limiter.msBeforeNext).toISOString(),
|
|
30
|
-
...headers,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
Object.keys(mergedHeaders).forEach((key) => {
|
|
34
|
-
response.setHeader(key, mergedHeaders[key] as HeaderValue);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
await next();
|
|
38
|
-
} catch {
|
|
39
|
-
throw createHttpError(429, "Too Many Requests");
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export default rateLimiterMiddleware;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import accepts from "accepts";
|
|
3
|
-
import { header as headerCase } from "case";
|
|
4
|
-
import type { NextApiResponse } from "next";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
import type { Serializer } from "../serializers/types";
|
|
8
|
-
import xmlTransformer from "../serializers/xml";
|
|
9
|
-
import yamlTransformer from "../serializers/yaml";
|
|
10
|
-
|
|
11
|
-
function hasJsonStructure(string_: any): boolean {
|
|
12
|
-
if (typeof string_ !== "string") {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const result = JSON.parse(string_);
|
|
18
|
-
const type = Object.prototype.toString.call(result);
|
|
19
|
-
|
|
20
|
-
return type === "[object Object]" || type === "[object Array]";
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const contentTypeKey = "Content-Type";
|
|
27
|
-
|
|
28
|
-
// eslint-disable-next-line max-len
|
|
29
|
-
const serialize = <Request extends IncomingMessage, Response extends ServerResponse>(
|
|
30
|
-
serializers: Serializers,
|
|
31
|
-
request: Request,
|
|
32
|
-
response: Response | NextApiResponse,
|
|
33
|
-
data: any,
|
|
34
|
-
options: {
|
|
35
|
-
defaultContentType: string;
|
|
36
|
-
},
|
|
37
|
-
// eslint-disable-next-line radar/cognitive-complexity
|
|
38
|
-
) => {
|
|
39
|
-
const contentType = response.getHeader(contentTypeKey) as string | undefined;
|
|
40
|
-
|
|
41
|
-
// skip serialization when Content-Type is already set
|
|
42
|
-
if (typeof contentType === "string") {
|
|
43
|
-
return data;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const accept = accepts(request);
|
|
47
|
-
const types: string[] = [...(accept.types() as string[]), options.defaultContentType];
|
|
48
|
-
|
|
49
|
-
let serializedData = data;
|
|
50
|
-
|
|
51
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
52
|
-
types.every((type) => {
|
|
53
|
-
let breakTypes = false;
|
|
54
|
-
|
|
55
|
-
serializers.forEach(({ regex, serializer }) => {
|
|
56
|
-
if (!regex.test(type)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
response.setHeader(contentTypeKey, type);
|
|
61
|
-
serializedData = serializer(serializedData);
|
|
62
|
-
breakTypes = true;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (!breakTypes) {
|
|
66
|
-
if (/yaml|yml/.test(type)) {
|
|
67
|
-
response.setHeader(contentTypeKey, type);
|
|
68
|
-
|
|
69
|
-
serializedData = yamlTransformer(hasJsonStructure(data) ? JSON.parse(data) : data);
|
|
70
|
-
} else if (/xml/.test(type)) {
|
|
71
|
-
response.setHeader(contentTypeKey, type);
|
|
72
|
-
|
|
73
|
-
serializedData = xmlTransformer({
|
|
74
|
-
[headerCase(`${request.url?.replace("/api/", "")}`.trim())]: hasJsonStructure(data) ? JSON.parse(data) : data,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return breakTypes;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// eslint-disable-next-line no-param-reassign
|
|
83
|
-
return serializedData;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// eslint-disable-next-line max-len
|
|
87
|
-
const serializersMiddleware = (serializers: Serializers = [], defaultContentType: string = "application/json; charset=utf-8") => async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response | NextApiResponse, next: NextHandler) => {
|
|
88
|
-
if (typeof (response as NextApiResponse)?.send === "function") {
|
|
89
|
-
const oldSend = (response as NextApiResponse).send;
|
|
90
|
-
|
|
91
|
-
(response as NextApiResponse).send = (data) => {
|
|
92
|
-
(response as NextApiResponse).send = oldSend;
|
|
93
|
-
|
|
94
|
-
// eslint-disable-next-line no-param-reassign
|
|
95
|
-
data = serialize<Request, Response>(serializers, request, response, data, { defaultContentType });
|
|
96
|
-
|
|
97
|
-
return (response as NextApiResponse).send(data);
|
|
98
|
-
};
|
|
99
|
-
} else {
|
|
100
|
-
const oldEnd = response.end;
|
|
101
|
-
|
|
102
|
-
// @ts-ignore
|
|
103
|
-
response.end = (data, ...arguments_) => {
|
|
104
|
-
response.end = oldEnd;
|
|
105
|
-
|
|
106
|
-
// eslint-disable-next-line no-param-reassign
|
|
107
|
-
data = serialize<Request, Response>(serializers, request, response, data, { defaultContentType });
|
|
108
|
-
|
|
109
|
-
// @ts-ignore
|
|
110
|
-
return response.end(data, ...arguments_);
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return next();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export type Serializers = {
|
|
118
|
-
regex: RegExp;
|
|
119
|
-
serializer: Serializer;
|
|
120
|
-
}[];
|
|
121
|
-
export default serializersMiddleware;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Serializer = (data: any) => string | Buffer | Uint8Array;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { XmlElement } from "jstoxml";
|
|
2
|
-
import { toXML } from "jstoxml";
|
|
3
|
-
|
|
4
|
-
import type { Serializer } from "./types";
|
|
5
|
-
|
|
6
|
-
const config = {
|
|
7
|
-
header: true,
|
|
8
|
-
indent: " ",
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const xmlTransformer: Serializer = (data?: XmlElement | XmlElement[]) => toXML(data, config);
|
|
12
|
-
|
|
13
|
-
export default xmlTransformer;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { HttpError } from "http-errors";
|
|
2
|
-
import { getReasonPhrase } from "http-status-codes";
|
|
3
|
-
import { ErrorSerializer, JapiError } from "ts-japi";
|
|
4
|
-
|
|
5
|
-
import type { ErrorHandler } from "./types";
|
|
6
|
-
import { addStatusCodeToResponse, sendJson, setErrorHeaders } from "./utils";
|
|
7
|
-
|
|
8
|
-
const defaultTitle = "An error occurred";
|
|
9
|
-
|
|
10
|
-
const jsonapiErrorHandler: ErrorHandler = (error: HttpError | JapiError | Error, _request, response) => {
|
|
11
|
-
addStatusCodeToResponse(response, error);
|
|
12
|
-
|
|
13
|
-
setErrorHeaders(response, error);
|
|
14
|
-
|
|
15
|
-
if (error instanceof JapiError || JapiError.isLikeJapiError(error)) {
|
|
16
|
-
const serializer = new ErrorSerializer();
|
|
17
|
-
|
|
18
|
-
sendJson(response, serializer.serialize(error));
|
|
19
|
-
} else if (error instanceof HttpError) {
|
|
20
|
-
const { statusCode, title, message } = error;
|
|
21
|
-
|
|
22
|
-
sendJson(response, {
|
|
23
|
-
errors: [
|
|
24
|
-
{
|
|
25
|
-
code: statusCode,
|
|
26
|
-
title: title || getReasonPhrase(statusCode) || defaultTitle,
|
|
27
|
-
detail: message,
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
const { message } = error;
|
|
33
|
-
|
|
34
|
-
sendJson(response, {
|
|
35
|
-
errors: [
|
|
36
|
-
{
|
|
37
|
-
code: "500",
|
|
38
|
-
title: getReasonPhrase(response.statusCode) || defaultTitle,
|
|
39
|
-
detail: message,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export default jsonapiErrorHandler;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { HttpError } from "http-errors";
|
|
2
|
-
import { getReasonPhrase } from "http-status-codes";
|
|
3
|
-
|
|
4
|
-
import type { ErrorHandler } from "./types";
|
|
5
|
-
import { addStatusCodeToResponse, sendJson, setErrorHeaders } from "./utils";
|
|
6
|
-
|
|
7
|
-
const defaultType = "https://tools.ietf.org/html/rfc2616#section-10";
|
|
8
|
-
const defaultTitle = "An error occurred";
|
|
9
|
-
/**
|
|
10
|
-
* Normalizes errors according to the API Problem spec (RFC 7807).
|
|
11
|
-
*
|
|
12
|
-
* @see https://tools.ietf.org/html/rfc7807
|
|
13
|
-
*/
|
|
14
|
-
const problemErrorHandler: ErrorHandler = (error: HttpError | Error, _request, response) => {
|
|
15
|
-
const { stack, message } = error;
|
|
16
|
-
|
|
17
|
-
if (error instanceof HttpError) {
|
|
18
|
-
const {
|
|
19
|
-
statusCode, expose, title, type,
|
|
20
|
-
} = error;
|
|
21
|
-
|
|
22
|
-
response.statusCode = statusCode;
|
|
23
|
-
|
|
24
|
-
setErrorHeaders(response, error);
|
|
25
|
-
|
|
26
|
-
sendJson(response, {
|
|
27
|
-
type: type || defaultType,
|
|
28
|
-
title: title || getReasonPhrase(statusCode) || defaultTitle,
|
|
29
|
-
details: message,
|
|
30
|
-
...(expose ? { trace: stack } : {}),
|
|
31
|
-
});
|
|
32
|
-
} else {
|
|
33
|
-
addStatusCodeToResponse(response, error);
|
|
34
|
-
|
|
35
|
-
sendJson(response, {
|
|
36
|
-
type: defaultType,
|
|
37
|
-
title: getReasonPhrase(response.statusCode) || defaultTitle,
|
|
38
|
-
details: message,
|
|
39
|
-
...((error as { expose: boolean } & Error).expose ? { trace: stack } : {}),
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export default problemErrorHandler;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
|
|
3
|
-
export type ErrorHandler = <Request extends IncomingMessage, Response extends ServerResponse>(
|
|
4
|
-
error: any,
|
|
5
|
-
request: Request,
|
|
6
|
-
response: Response,
|
|
7
|
-
) => any | Promise<any>;
|
|
8
|
-
|
|
9
|
-
export type ErrorHandlers = {
|
|
10
|
-
regex: RegExp;
|
|
11
|
-
handler: ErrorHandler;
|
|
12
|
-
}[];
|
|
13
|
-
|
|
14
|
-
export type ApiFormat = "jsonapi" | "problem";
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { StatusCodes } from "http-status-codes";
|
|
2
|
-
import type { ServerResponse } from "node:http";
|
|
3
|
-
|
|
4
|
-
export const setErrorHeaders = (response: ServerResponse, error: any) => {
|
|
5
|
-
const headers: { [key: string]: number | string | ReadonlyArray<string> } = error.headers || {};
|
|
6
|
-
|
|
7
|
-
Object.keys(headers).forEach((header: string) => {
|
|
8
|
-
response.setHeader(header, headers[header] as number | string | ReadonlyArray<string>);
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Send `JSON` object
|
|
14
|
-
* @param response response object
|
|
15
|
-
* @param jsonBody of data
|
|
16
|
-
*/
|
|
17
|
-
export const sendJson = (response: ServerResponse, jsonBody: any): void => {
|
|
18
|
-
// Set header to application/json
|
|
19
|
-
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
20
|
-
|
|
21
|
-
response.end(JSON.stringify(jsonBody));
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const addStatusCodeToResponse = (response: ServerResponse, error: any): void => {
|
|
25
|
-
// respect err.statusCode
|
|
26
|
-
if (typeof error.statusCode !== "undefined") {
|
|
27
|
-
response.statusCode = error.statusCode;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// respect err.status
|
|
31
|
-
if (typeof error.status !== "undefined") {
|
|
32
|
-
response.statusCode = error.status;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// default status code to 500
|
|
36
|
-
if (response.statusCode < 400) {
|
|
37
|
-
response.statusCode = StatusCodes.INTERNAL_SERVER_ERROR;
|
|
38
|
-
}
|
|
39
|
-
};
|
package/src/index-browser.tsx
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * as zod from "./zod";
|
package/src/index-server.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
export * from "./index-browser";
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
default as createHttpError,
|
|
5
|
-
BadRequest,
|
|
6
|
-
Forbidden,
|
|
7
|
-
BadGateway,
|
|
8
|
-
BandwidthLimitExceeded,
|
|
9
|
-
Conflict,
|
|
10
|
-
ExpectationFailed,
|
|
11
|
-
FailedDependency,
|
|
12
|
-
GatewayTimeout,
|
|
13
|
-
Gone,
|
|
14
|
-
HTTPVersionNotSupported,
|
|
15
|
-
ImATeapot,
|
|
16
|
-
InsufficientStorage,
|
|
17
|
-
InternalServerError,
|
|
18
|
-
VariantAlsoNegotiates,
|
|
19
|
-
ProxyAuthenticationRequired,
|
|
20
|
-
NetworkAuthenticationRequire,
|
|
21
|
-
LengthRequired,
|
|
22
|
-
LoopDetected,
|
|
23
|
-
Locked,
|
|
24
|
-
MethodNotAllowed,
|
|
25
|
-
MisdirectedRequest,
|
|
26
|
-
NotAcceptable,
|
|
27
|
-
NotExtended,
|
|
28
|
-
NotFound,
|
|
29
|
-
NotImplemented,
|
|
30
|
-
PayloadTooLarge,
|
|
31
|
-
RequestHeaderFieldsTooLarge,
|
|
32
|
-
PaymentRequired,
|
|
33
|
-
PreconditionFailed,
|
|
34
|
-
PreconditionRequired,
|
|
35
|
-
RangeNotSatisfiable,
|
|
36
|
-
RequestTimeout,
|
|
37
|
-
ServiceUnavailable,
|
|
38
|
-
TooManyRequests,
|
|
39
|
-
Unauthorized,
|
|
40
|
-
UnorderedCollection,
|
|
41
|
-
UnprocessableEntity,
|
|
42
|
-
UnavailableForLegalReasons,
|
|
43
|
-
UnsupportedMediaType,
|
|
44
|
-
UpgradeRequired,
|
|
45
|
-
URITooLong,
|
|
46
|
-
} from "http-errors";
|
|
47
|
-
|
|
48
|
-
export { default as createNodeRouter } from "./connect/create-node-router";
|
|
49
|
-
export { onError, onNoMatch } from "./connect/handler";
|
|
50
|
-
export type {
|
|
51
|
-
EdgeRequestHandler,
|
|
52
|
-
ExpressRequestHandler,
|
|
53
|
-
NodeRequestHandler,
|
|
54
|
-
Route,
|
|
55
|
-
HandlerOptions,
|
|
56
|
-
NextHandler,
|
|
57
|
-
FunctionLike,
|
|
58
|
-
Nextable,
|
|
59
|
-
ValueOrPromise,
|
|
60
|
-
FindResult,
|
|
61
|
-
RouteShortcutMethod,
|
|
62
|
-
HttpMethod,
|
|
63
|
-
} from "@visulima/connect";
|
|
64
|
-
export {
|
|
65
|
-
createEdgeRouter, EdgeRouter, expressWrapper, NodeRouter, Router, withZod, sendJson,
|
|
66
|
-
} from "@visulima/connect";
|
|
67
|
-
|
|
68
|
-
export { default as rateLimiterMiddleware } from "./connect/middleware/rate-limiter-middleware";
|
|
69
|
-
export { default as corsMiddleware } from "./connect/middleware/cors-middleware";
|
|
70
|
-
export { default as serializersMiddleware } from "./connect/middleware/serializers-middleware";
|
|
71
|
-
export { default as httpHeaderNormalizerMiddleware } from "./connect/middleware/http-header-normalizer";
|
|
72
|
-
|
|
73
|
-
export { default as swaggerHandler } from "./swagger/swagger-handler";
|
|
74
|
-
|
|
75
|
-
export { dateIn, dateOut } from "./zod";
|
package/src/next/cli/index.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { OpenApiObject } from "@visulima/jsdoc-open-api";
|
|
2
|
-
import { jsDocumentCommentsToOpenApi, parseFile, swaggerJsDocumentCommentsToOpenApi } from "@visulima/jsdoc-open-api";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import process from "node:process";
|
|
5
|
-
|
|
6
|
-
import type { Route } from "./types";
|
|
7
|
-
|
|
8
|
-
const extensionRegex = /\.(js|ts|mjs|cjs)$/;
|
|
9
|
-
|
|
10
|
-
const apiRouteFileParser = (apiRouteFile: string, cwdPath: string, verbose: boolean = false): Route[] => {
|
|
11
|
-
let specs: OpenApiObject[] = [];
|
|
12
|
-
|
|
13
|
-
const parsedJsDocumentFile = parseFile(apiRouteFile, jsDocumentCommentsToOpenApi, verbose);
|
|
14
|
-
|
|
15
|
-
specs = [...specs, ...parsedJsDocumentFile.map((item) => item.spec)];
|
|
16
|
-
|
|
17
|
-
const parsedSwaggerJsDocumentFile = parseFile(apiRouteFile, swaggerJsDocumentCommentsToOpenApi, verbose);
|
|
18
|
-
|
|
19
|
-
specs = [...specs, ...parsedSwaggerJsDocumentFile.map((item) => item.spec)];
|
|
20
|
-
|
|
21
|
-
const routes: Route[] = [];
|
|
22
|
-
|
|
23
|
-
if (specs.length === 0) {
|
|
24
|
-
const apiRouteFileContent = fs.readFileSync(apiRouteFile, "utf8");
|
|
25
|
-
|
|
26
|
-
apiRouteFileContent.split(/\r?\n/).forEach((line) => {
|
|
27
|
-
const match = line.match(/[=aces|]+\s["'|](GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS)["'|]/);
|
|
28
|
-
|
|
29
|
-
if (match) {
|
|
30
|
-
let [, method] = match;
|
|
31
|
-
|
|
32
|
-
if (method === "GET") {
|
|
33
|
-
method = "GET|HEAD";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
routes.push({
|
|
37
|
-
method: method as string,
|
|
38
|
-
path: apiRouteFile.replace(cwdPath, "").replace(extensionRegex, ""),
|
|
39
|
-
tags: [],
|
|
40
|
-
file: apiRouteFile.replace(`${process.cwd()}/`, ""),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
if (routes.length === 0) {
|
|
46
|
-
routes.push({
|
|
47
|
-
method: "GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS",
|
|
48
|
-
path: apiRouteFile.replace(cwdPath, "").replace(extensionRegex, ""),
|
|
49
|
-
tags: [],
|
|
50
|
-
file: apiRouteFile.replace(`${process.cwd()}/`, ""),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return routes;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
specs.forEach((spec) => {
|
|
58
|
-
const paths = Object.entries(spec.paths);
|
|
59
|
-
|
|
60
|
-
paths.forEach(([path, pathSpec]) => {
|
|
61
|
-
const methods = Object.entries(pathSpec);
|
|
62
|
-
|
|
63
|
-
methods.forEach(([method, methodSpec]) => {
|
|
64
|
-
routes.push({
|
|
65
|
-
path, method: method.toUpperCase(), tags: methodSpec.tags, file: apiRouteFile.replace(`${process.cwd()}/`, ""),
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
return routes;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export default apiRouteFileParser;
|