@visulima/api-platform 1.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +137 -0
  4. package/dist/chunk-2LATTLUM.mjs +166 -0
  5. package/dist/chunk-2LATTLUM.mjs.map +1 -0
  6. package/dist/chunk-4DRV4PCJ.js +51 -0
  7. package/dist/chunk-4DRV4PCJ.js.map +1 -0
  8. package/dist/chunk-5VRACIDE.mjs +10 -0
  9. package/dist/chunk-5VRACIDE.mjs.map +1 -0
  10. package/dist/chunk-GSWANBU5.mjs +51 -0
  11. package/dist/chunk-GSWANBU5.mjs.map +1 -0
  12. package/dist/chunk-J4EBGCNK.mjs +99 -0
  13. package/dist/chunk-J4EBGCNK.mjs.map +1 -0
  14. package/dist/chunk-JC4IRQUL.js +10 -0
  15. package/dist/chunk-JC4IRQUL.js.map +1 -0
  16. package/dist/chunk-S7GUPAL4.js +166 -0
  17. package/dist/chunk-S7GUPAL4.js.map +1 -0
  18. package/dist/chunk-T25VSNTF.js +99 -0
  19. package/dist/chunk-T25VSNTF.js.map +1 -0
  20. package/dist/index-3318b0da.d.ts +33 -0
  21. package/dist/index-browser.d.ts +2 -0
  22. package/dist/index-browser.js +8 -0
  23. package/dist/index-browser.js.map +1 -0
  24. package/dist/index-browser.mjs +8 -0
  25. package/dist/index-browser.mjs.map +1 -0
  26. package/dist/index-server.d.ts +87 -0
  27. package/dist/index-server.js +454 -0
  28. package/dist/index-server.js.map +1 -0
  29. package/dist/index-server.mjs +454 -0
  30. package/dist/index-server.mjs.map +1 -0
  31. package/dist/next/cli/index.d.ts +11 -0
  32. package/dist/next/cli/index.js +203 -0
  33. package/dist/next/cli/index.js.map +1 -0
  34. package/dist/next/cli/index.mjs +203 -0
  35. package/dist/next/cli/index.mjs.map +1 -0
  36. package/dist/next/index-browser.d.ts +4 -0
  37. package/dist/next/index-browser.js +12 -0
  38. package/dist/next/index-browser.js.map +1 -0
  39. package/dist/next/index-browser.mjs +12 -0
  40. package/dist/next/index-browser.mjs.map +1 -0
  41. package/dist/next/index-server.d.ts +129 -0
  42. package/dist/next/index-server.js +76 -0
  43. package/dist/next/index-server.js.map +1 -0
  44. package/dist/next/index-server.mjs +76 -0
  45. package/dist/next/index-server.mjs.map +1 -0
  46. package/dist/swagger-6ad3b021.d.ts +11 -0
  47. package/next/cli/package.json +9 -0
  48. package/next/package.json +10 -0
  49. package/package.json +207 -0
  50. package/src/connect/create-node-router.ts +44 -0
  51. package/src/connect/handler.ts +46 -0
  52. package/src/connect/middleware/cors-middleware.ts +10 -0
  53. package/src/connect/middleware/http-header-normalizer.ts +93 -0
  54. package/src/connect/middleware/rate-limiter-middleware.ts +43 -0
  55. package/src/connect/middleware/serializers-middleware.ts +121 -0
  56. package/src/connect/serializers/types.d.ts +1 -0
  57. package/src/connect/serializers/xml.ts +13 -0
  58. package/src/connect/serializers/yaml.ts +7 -0
  59. package/src/error-handler/jsonapi-error-handler.ts +46 -0
  60. package/src/error-handler/problem-error-handler.ts +44 -0
  61. package/src/error-handler/types.d.ts +14 -0
  62. package/src/error-handler/utils.ts +39 -0
  63. package/src/index-browser.tsx +1 -0
  64. package/src/index-server.ts +75 -0
  65. package/src/next/cli/index.ts +2 -0
  66. package/src/next/cli/list/api-route-file-parser.ts +74 -0
  67. package/src/next/cli/list/collect-api-route-files.ts +42 -0
  68. package/src/next/cli/list/list-command.ts +105 -0
  69. package/src/next/cli/list/routes-render.ts +62 -0
  70. package/src/next/cli/list/types.d.ts +1 -0
  71. package/src/next/index-browser.tsx +3 -0
  72. package/src/next/index-server.ts +6 -0
  73. package/src/next/routes/api/swagger.ts +23 -0
  74. package/src/next/routes/pages/swagger/get-static-properties-swagger.ts +32 -0
  75. package/src/next/routes/pages/swagger/redoc.tsx +35 -0
  76. package/src/next/routes/pages/swagger/swagger.tsx +39 -0
  77. package/src/next/webpack/with-open-api.ts +63 -0
  78. package/src/swagger/extend-swagger-spec.ts +167 -0
  79. package/src/swagger/swagger-handler.ts +83 -0
  80. package/src/utils.ts +37 -0
  81. package/src/zod/date-in-schema.ts +57 -0
  82. package/src/zod/date-out-schema.ts +41 -0
  83. package/src/zod/index.ts +9 -0
@@ -0,0 +1,76 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
+
3
+ var _chunkS7GUPAL4js = require('../chunk-S7GUPAL4.js');
4
+
5
+
6
+
7
+
8
+ var _chunk4DRV4PCJjs = require('../chunk-4DRV4PCJ.js');
9
+ require('../chunk-JC4IRQUL.js');
10
+
11
+ // src/next/webpack/with-open-api.ts
12
+ var _jsdocopenapi = require('@visulima/jsdoc-open-api');
13
+ var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
14
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
15
+ var withOpenApi = ({
16
+ definition,
17
+ sources,
18
+ verbose,
19
+ output = "swagger/swagger.json"
20
+ }) => (nextConfig) => {
21
+ return {
22
+ ...nextConfig,
23
+ webpack: (config, options) => {
24
+ if (!options.isServer) {
25
+ return config;
26
+ }
27
+ if (output.startsWith("/")) {
28
+ output = output.slice(1);
29
+ }
30
+ if (!output.endsWith(".json")) {
31
+ throw new Error("The output path must end with .json");
32
+ }
33
+ config = {
34
+ ...config,
35
+ plugins: [
36
+ ...config.plugins,
37
+ new (0, _jsdocopenapi.SwaggerCompilerPlugin)(
38
+ `${options.dir}/${output}`,
39
+ sources.map((source) => {
40
+ const combinedPath = _path2.default.join(options.dir, source.replace("./", ""));
41
+ _fs2.default.lstatSync(combinedPath).isDirectory();
42
+ return combinedPath;
43
+ }),
44
+ {
45
+ openapi: "3.0.0",
46
+ ...definition
47
+ },
48
+ { verbose }
49
+ )
50
+ ]
51
+ };
52
+ if (typeof nextConfig.webpack === "function") {
53
+ return nextConfig.webpack(config, options);
54
+ }
55
+ return config;
56
+ }
57
+ };
58
+ };
59
+ var with_open_api_default = withOpenApi;
60
+
61
+ // src/next/routes/api/swagger.ts
62
+ var _connect = require('@visulima/connect');
63
+ var swaggerApiRoute = (options = {}) => {
64
+ const handler = _chunkS7GUPAL4js.swagger_handler_default.call(void 0, options);
65
+ const router = _connect.createNodeRouter.call(void 0, ).get(handler);
66
+ return router.handler();
67
+ };
68
+ var swagger_default2 = swaggerApiRoute;
69
+
70
+
71
+
72
+
73
+
74
+
75
+ exports.RedocPage = _chunk4DRV4PCJjs.redoc_default; exports.SwaggerPage = _chunk4DRV4PCJjs.swagger_default; exports.getSwaggerStaticProps = _chunk4DRV4PCJjs.get_static_properties_swagger_default; exports.swaggerApiRoute = swagger_default2; exports.withOpenApi = with_open_api_default;
76
+ //# sourceMappingURL=index-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/next/webpack/with-open-api.ts","../../src/next/routes/api/swagger.ts"],"names":["swagger_default"],"mappings":";;;;;;;;;;;AACA,SAAS,6BAA6B;AAEtC,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,cAAc,CAAC;AAAA,EACjB;AAAA,EAAY;AAAA,EAAS;AAAA,EAAS,SAAS;AAE3C,MAAuI,CAAC,eAA2B;AAC/J,SAAO;AAAA,IACH,GAAG;AAAA,IACH,SAAS,CAAC,QAAuB,YAAiB;AAC9C,UAAI,CAAC,QAAQ,UAAU;AACnB,eAAO;AAAA,MACX;AAEA,UAAI,OAAO,WAAW,GAAG,GAAG;AAExB,iBAAS,OAAO,MAAM,CAAC;AAAA,MAC3B;AAEA,UAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC3B,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACzD;AAGA,eAAS;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,UAEL,GAAG,OAAO;AAAA,UACV,IAAI;AAAA,YACA,GAAG,QAAQ,OAAO;AAAA,YAClB,QAAQ,IAAI,CAAC,WAAW;AACpB,oBAAM,eAAe,KAAK,KAAK,QAAQ,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;AAGpE,iBAAG,UAAU,YAAY,EAAE,YAAY;AAEvC,qBAAO;AAAA,YACX,CAAC;AAAA,YACD;AAAA,cAEI,SAAS;AAAA,cACT,GAAG;AAAA,YACP;AAAA,YACA,EAAE,QAAQ;AAAA,UACd;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,OAAO,WAAW,YAAY,YAAY;AAC1C,eAAO,WAAW,QAAQ,QAAQ,OAAO;AAAA,MAC7C;AAEA,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEA,IAAO,wBAAQ;;;AC9Df,SAAS,wBAAwB;AAQjC,IAAM,kBAAkB,CACpB,UAIK,CAAC,MACL;AACD,QAAM,UAAU,wBAAe,OAAO;AAEtC,QAAM,SAAS,iBAAkD,EAAE,IAAI,OAAO;AAE9E,SAAO,OAAO,QAAQ;AAC1B;AAEA,IAAOA,mBAAQ","sourcesContent":["import type { BaseDefinition } from \"@visulima/jsdoc-open-api\";\nimport { SwaggerCompilerPlugin } from \"@visulima/jsdoc-open-api\";\nimport type { NextConfig } from \"next\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Configuration } from \"webpack\";\n\nconst withOpenApi = ({\n definition, sources, verbose, output = \"swagger/swagger.json\",\n// eslint-disable-next-line max-len\n}: { definition: Exclude<BaseDefinition, \"openapi\"> & { openapi?: string }; sources: string[]; verbose?: boolean; output: string }) => (nextConfig: NextConfig) => {\n return {\n ...nextConfig,\n webpack: (config: Configuration, options: any) => {\n if (!options.isServer) {\n return config;\n }\n\n if (output.startsWith(\"/\")) {\n // eslint-disable-next-line no-param-reassign\n output = output.slice(1);\n }\n\n if (!output.endsWith(\".json\")) {\n throw new Error(\"The output path must end with .json\");\n }\n\n // eslint-disable-next-line no-param-reassign\n config = {\n ...config,\n plugins: [\n // @ts-ignore\n ...config.plugins,\n new SwaggerCompilerPlugin(\n `${options.dir}/${output}`,\n sources.map((source) => {\n const combinedPath = path.join(options.dir, source.replace(\"./\", \"\"));\n\n // Check if the path is a directory\n fs.lstatSync(combinedPath).isDirectory();\n\n return combinedPath;\n }),\n {\n // @ts-ignore\n openapi: \"3.0.0\",\n ...definition,\n },\n { verbose },\n ),\n ],\n };\n\n if (typeof nextConfig.webpack === \"function\") {\n return nextConfig.webpack(config, options);\n }\n\n return config;\n },\n };\n};\n\nexport default withOpenApi;\n","import { createNodeRouter } from \"@visulima/connect\";\n// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies\nimport type { ModelsToOpenApiParameters } from \"@visulima/crud\";\nimport type { NextApiRequest, NextApiResponse } from \"next\";\n\nimport swaggerHandler from \"../../../swagger/swagger-handler\";\n\n// eslint-disable-next-line max-len\nconst swaggerApiRoute = (\n options: Partial<{\n allowedMediaTypes: { [key: string]: boolean };\n swaggerFilePath: string;\n crud: Exclude<ModelsToOpenApiParameters, \"swagger.allowedMediaTypes\">;\n }> = {},\n) => {\n const handler = swaggerHandler(options);\n\n const router = createNodeRouter<NextApiRequest, NextApiResponse>().get(handler);\n\n return router.handler();\n};\n\nexport default swaggerApiRoute;\n"]}
@@ -0,0 +1,76 @@
1
+ import {
2
+ swagger_handler_default
3
+ } from "../chunk-2LATTLUM.mjs";
4
+ import {
5
+ get_static_properties_swagger_default,
6
+ redoc_default,
7
+ swagger_default
8
+ } from "../chunk-GSWANBU5.mjs";
9
+ import "../chunk-5VRACIDE.mjs";
10
+
11
+ // src/next/webpack/with-open-api.ts
12
+ import { SwaggerCompilerPlugin } from "@visulima/jsdoc-open-api";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ var withOpenApi = ({
16
+ definition,
17
+ sources,
18
+ verbose,
19
+ output = "swagger/swagger.json"
20
+ }) => (nextConfig) => {
21
+ return {
22
+ ...nextConfig,
23
+ webpack: (config, options) => {
24
+ if (!options.isServer) {
25
+ return config;
26
+ }
27
+ if (output.startsWith("/")) {
28
+ output = output.slice(1);
29
+ }
30
+ if (!output.endsWith(".json")) {
31
+ throw new Error("The output path must end with .json");
32
+ }
33
+ config = {
34
+ ...config,
35
+ plugins: [
36
+ ...config.plugins,
37
+ new SwaggerCompilerPlugin(
38
+ `${options.dir}/${output}`,
39
+ sources.map((source) => {
40
+ const combinedPath = path.join(options.dir, source.replace("./", ""));
41
+ fs.lstatSync(combinedPath).isDirectory();
42
+ return combinedPath;
43
+ }),
44
+ {
45
+ openapi: "3.0.0",
46
+ ...definition
47
+ },
48
+ { verbose }
49
+ )
50
+ ]
51
+ };
52
+ if (typeof nextConfig.webpack === "function") {
53
+ return nextConfig.webpack(config, options);
54
+ }
55
+ return config;
56
+ }
57
+ };
58
+ };
59
+ var with_open_api_default = withOpenApi;
60
+
61
+ // src/next/routes/api/swagger.ts
62
+ import { createNodeRouter } from "@visulima/connect";
63
+ var swaggerApiRoute = (options = {}) => {
64
+ const handler = swagger_handler_default(options);
65
+ const router = createNodeRouter().get(handler);
66
+ return router.handler();
67
+ };
68
+ var swagger_default2 = swaggerApiRoute;
69
+ export {
70
+ redoc_default as RedocPage,
71
+ swagger_default as SwaggerPage,
72
+ get_static_properties_swagger_default as getSwaggerStaticProps,
73
+ swagger_default2 as swaggerApiRoute,
74
+ with_open_api_default as withOpenApi
75
+ };
76
+ //# sourceMappingURL=index-server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/next/webpack/with-open-api.ts","../../src/next/routes/api/swagger.ts"],"sourcesContent":["import type { BaseDefinition } from \"@visulima/jsdoc-open-api\";\nimport { SwaggerCompilerPlugin } from \"@visulima/jsdoc-open-api\";\nimport type { NextConfig } from \"next\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Configuration } from \"webpack\";\n\nconst withOpenApi = ({\n definition, sources, verbose, output = \"swagger/swagger.json\",\n// eslint-disable-next-line max-len\n}: { definition: Exclude<BaseDefinition, \"openapi\"> & { openapi?: string }; sources: string[]; verbose?: boolean; output: string }) => (nextConfig: NextConfig) => {\n return {\n ...nextConfig,\n webpack: (config: Configuration, options: any) => {\n if (!options.isServer) {\n return config;\n }\n\n if (output.startsWith(\"/\")) {\n // eslint-disable-next-line no-param-reassign\n output = output.slice(1);\n }\n\n if (!output.endsWith(\".json\")) {\n throw new Error(\"The output path must end with .json\");\n }\n\n // eslint-disable-next-line no-param-reassign\n config = {\n ...config,\n plugins: [\n // @ts-ignore\n ...config.plugins,\n new SwaggerCompilerPlugin(\n `${options.dir}/${output}`,\n sources.map((source) => {\n const combinedPath = path.join(options.dir, source.replace(\"./\", \"\"));\n\n // Check if the path is a directory\n fs.lstatSync(combinedPath).isDirectory();\n\n return combinedPath;\n }),\n {\n // @ts-ignore\n openapi: \"3.0.0\",\n ...definition,\n },\n { verbose },\n ),\n ],\n };\n\n if (typeof nextConfig.webpack === \"function\") {\n return nextConfig.webpack(config, options);\n }\n\n return config;\n },\n };\n};\n\nexport default withOpenApi;\n","import { createNodeRouter } from \"@visulima/connect\";\n// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies\nimport type { ModelsToOpenApiParameters } from \"@visulima/crud\";\nimport type { NextApiRequest, NextApiResponse } from \"next\";\n\nimport swaggerHandler from \"../../../swagger/swagger-handler\";\n\n// eslint-disable-next-line max-len\nconst swaggerApiRoute = (\n options: Partial<{\n allowedMediaTypes: { [key: string]: boolean };\n swaggerFilePath: string;\n crud: Exclude<ModelsToOpenApiParameters, \"swagger.allowedMediaTypes\">;\n }> = {},\n) => {\n const handler = swaggerHandler(options);\n\n const router = createNodeRouter<NextApiRequest, NextApiResponse>().get(handler);\n\n return router.handler();\n};\n\nexport default swaggerApiRoute;\n"],"mappings":";;;;;;;;;;;AACA,SAAS,6BAA6B;AAEtC,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,cAAc,CAAC;AAAA,EACjB;AAAA,EAAY;AAAA,EAAS;AAAA,EAAS,SAAS;AAE3C,MAAuI,CAAC,eAA2B;AAC/J,SAAO;AAAA,IACH,GAAG;AAAA,IACH,SAAS,CAAC,QAAuB,YAAiB;AAC9C,UAAI,CAAC,QAAQ,UAAU;AACnB,eAAO;AAAA,MACX;AAEA,UAAI,OAAO,WAAW,GAAG,GAAG;AAExB,iBAAS,OAAO,MAAM,CAAC;AAAA,MAC3B;AAEA,UAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC3B,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACzD;AAGA,eAAS;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,UAEL,GAAG,OAAO;AAAA,UACV,IAAI;AAAA,YACA,GAAG,QAAQ,OAAO;AAAA,YAClB,QAAQ,IAAI,CAAC,WAAW;AACpB,oBAAM,eAAe,KAAK,KAAK,QAAQ,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;AAGpE,iBAAG,UAAU,YAAY,EAAE,YAAY;AAEvC,qBAAO;AAAA,YACX,CAAC;AAAA,YACD;AAAA,cAEI,SAAS;AAAA,cACT,GAAG;AAAA,YACP;AAAA,YACA,EAAE,QAAQ;AAAA,UACd;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,OAAO,WAAW,YAAY,YAAY;AAC1C,eAAO,WAAW,QAAQ,QAAQ,OAAO;AAAA,MAC7C;AAEA,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEA,IAAO,wBAAQ;;;AC9Df,SAAS,wBAAwB;AAQjC,IAAM,kBAAkB,CACpB,UAIK,CAAC,MACL;AACD,QAAM,UAAU,wBAAe,OAAO;AAEtC,QAAM,SAAS,iBAAkD,EAAE,IAAI,OAAO;AAE9E,SAAO,OAAO,QAAQ;AAC1B;AAEA,IAAOA,mBAAQ;","names":["swagger_default"]}
@@ -0,0 +1,11 @@
1
+ import { GetStaticProps, NextPage, InferGetStaticPropsType } from 'next';
2
+ import { RedocStandaloneProps } from 'redoc';
3
+ import { SwaggerUIProps } from 'swagger-ui-react';
4
+
5
+ declare const getStaticProps: (swaggerUrl: string) => GetStaticProps;
6
+
7
+ declare const RedocApiDocument: (name: string, swagger?: Exclude<RedocStandaloneProps, "spec">) => NextPage<InferGetStaticPropsType<typeof getStaticProps>>;
8
+
9
+ declare const SwaggerApiDocument: (name: string, swagger?: Exclude<SwaggerUIProps, "spec">) => NextPage<InferGetStaticPropsType<typeof getStaticProps>>;
10
+
11
+ export { RedocApiDocument as R, SwaggerApiDocument as S, getStaticProps as g };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@visulima/api-platform-next-cli",
3
+ "version": "0.0.0",
4
+ "description": "visulima api platform",
5
+ "license": "MIT",
6
+ "main": "../dist/next/cli/index.js",
7
+ "module": "../dist/next/cli/index.mjs",
8
+ "types": "../dist/next/cli/index.d.ts"
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@visulima/api-platform-next",
3
+ "version": "0.0.0",
4
+ "description": "visulima api platform",
5
+ "license": "MIT",
6
+ "main": "../dist/next/index-server.js",
7
+ "module": "../dist/next/index-server.mjs",
8
+ "browser": "../dist/next/index-browser.mjs",
9
+ "types": "../dist/next/index-server.d.ts"
10
+ }
package/package.json ADDED
@@ -0,0 +1,207 @@
1
+ {
2
+ "name": "@visulima/api-platform",
3
+ "version": "1.0.0",
4
+ "description": "visulima api platform",
5
+ "keywords": [
6
+ "anolilab",
7
+ "visulima",
8
+ "react",
9
+ "blitz",
10
+ "blitzjs",
11
+ "rate",
12
+ "limit",
13
+ "ratelimit",
14
+ "rate-limit",
15
+ "middleware",
16
+ "ip",
17
+ "auth",
18
+ "authorization",
19
+ "security",
20
+ "brute",
21
+ "force",
22
+ "bruteforce",
23
+ "brute-force",
24
+ "attack",
25
+ "accepts"
26
+ ],
27
+ "homepage": "https://visulima.com/packages/api-platform",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/visulima/visulima.git",
31
+ "directory": "packages/api-platform"
32
+ },
33
+ "funding": [
34
+ {
35
+ "type": "github",
36
+ "url": "https://github.com/sponsors/prisis"
37
+ },
38
+ {
39
+ "type": "consulting",
40
+ "url": "https://anolilab.com/support"
41
+ }
42
+ ],
43
+ "license": "MIT",
44
+ "author": {
45
+ "name": "Daniel Bannert",
46
+ "email": "d.bannert@anolilab.de"
47
+ },
48
+ "sideEffects": false,
49
+ "exports": {
50
+ ".": {
51
+ "types": "./dist/index-server.d.ts",
52
+ "browser": "./dist/index-browser.js",
53
+ "require": "./dist/index-server.js",
54
+ "import": "./dist/index-server.mjs"
55
+ },
56
+ "./next": {
57
+ "types": "./dist/next/index-server.d.ts",
58
+ "browser": "./dist/next/index-browser.js",
59
+ "require": "./dist/next/index-server.js",
60
+ "import": "./dist/next/index-server.mjs"
61
+ },
62
+ "./next/cli": {
63
+ "types": "./dist/next/cli/index.d.ts",
64
+ "require": "./dist/next/cli/index.js",
65
+ "import": "./dist/next/cli/index.mjs"
66
+ },
67
+ "./package.json": "./package.json"
68
+ },
69
+ "main": "dist/index-server.js",
70
+ "module": "dist/index-server.mjs",
71
+ "browser": "dist/index-browser.mjs",
72
+ "types": "dist/index-server.d.ts",
73
+ "files": [
74
+ "src",
75
+ "dist",
76
+ "next",
77
+ "README.md",
78
+ "CHANGELOG.md",
79
+ "LICENSE.md"
80
+ ],
81
+ "scripts": {
82
+ "build": "cross-env NODE_ENV=development tsup",
83
+ "build:prod": "cross-env NODE_ENV=production tsup",
84
+ "clean": "rimraf node_modules dist",
85
+ "coverage": "vitest run --coverage",
86
+ "dev": "pnpm predev && pnpm run build --watch",
87
+ "lint:eslint": "cross-env NO_LOGS=true eslint --ext js,jsx,ts,tsx --max-warnings=0 --config .eslintrc.cjs",
88
+ "lint:eslint:fix": "pnpm run lint:eslint --fix",
89
+ "test": "vitest"
90
+ },
91
+ "dependencies": {
92
+ "@visulima/connect": "1.2.0",
93
+ "@visulima/jsdoc-open-api": "1.2.0",
94
+ "accepts": "^1.3.8",
95
+ "case": "^1.6.3",
96
+ "debug": "^4.3.4",
97
+ "http-errors": "^2.0.0",
98
+ "http-status-codes": "^2.2.0",
99
+ "jstoxml": "^3.2.5",
100
+ "lodash.merge": "^4.6.2",
101
+ "schema-dts": "^1.1.0",
102
+ "ts-japi": "^1.8.0",
103
+ "yaml": "^2.1.3",
104
+ "zod-to-ts": "^1.1.1"
105
+ },
106
+ "devDependencies": {
107
+ "@anolilab/eslint-config": "^4.0.9",
108
+ "@anolilab/semantic-release-preset": "^2.0.7",
109
+ "@rushstack/eslint-plugin-security": "^0.5.0",
110
+ "@testing-library/react": "^13.4.0",
111
+ "@testing-library/react-hooks": "^8.0.1",
112
+ "@types/accepts": "^1.3.5",
113
+ "@types/cors": "^2.8.12",
114
+ "@types/debug": "^4.1.7",
115
+ "@types/http-errors": "^1.8.2",
116
+ "@types/jstoxml": "^2.0.2",
117
+ "@types/lodash.merge": "^4.6.7",
118
+ "@types/lodash.set": "^4.3.7",
119
+ "@types/node": "^18.8.4",
120
+ "@types/qs": "^6.9.7",
121
+ "@types/react": "^18.0.21",
122
+ "@types/react-dom": "^18.0.6",
123
+ "@types/swagger-jsdoc": "^6.0.1",
124
+ "@types/swagger-ui-react": "^4.11.0",
125
+ "@types/webpack": "^5.28.0",
126
+ "@typescript-eslint/eslint-plugin": "^5.40.0",
127
+ "@typescript-eslint/parser": "^5.40.0",
128
+ "@visulima/crud": "1.0.0",
129
+ "@visulima/readdir": "1.3.0",
130
+ "chalk": "4.1.2",
131
+ "commander": "^9.4.1",
132
+ "core-js": "^3.25.5",
133
+ "cors": "^2.8.5",
134
+ "cross-env": "^7.0.3",
135
+ "eslint": "^8.25.0",
136
+ "eslint-plugin-compat": "^4.0.2",
137
+ "eslint-plugin-eslint-comments": "^3.2.0",
138
+ "eslint-plugin-import": "^2.26.0",
139
+ "eslint-plugin-json": "^3.1.0",
140
+ "eslint-plugin-jsx-a11y": "^6.6.1",
141
+ "eslint-plugin-markdown": "^3.0.0",
142
+ "eslint-plugin-no-loops": "^0.3.0",
143
+ "eslint-plugin-no-secrets": "^0.8.9",
144
+ "eslint-plugin-node": "^11.1.0",
145
+ "eslint-plugin-optimize-regex": "^1.2.1",
146
+ "eslint-plugin-promise": "^6.0.1",
147
+ "eslint-plugin-radar": "^0.2.1",
148
+ "eslint-plugin-react": "7.31.10",
149
+ "eslint-plugin-react-hooks": "4.6.0",
150
+ "eslint-plugin-simple-import-sort": "^8.0.0",
151
+ "eslint-plugin-sort-keys-fix": "^1.1.2",
152
+ "eslint-plugin-testing-library": "^5.7.2",
153
+ "eslint-plugin-unicorn": "^44.0.2",
154
+ "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0",
155
+ "eslint-plugin-you-dont-need-momentjs": "^1.6.0",
156
+ "mobx": "^6.6.2",
157
+ "next": "^12.3.1",
158
+ "next-test-api-route-handler": "^4.0.0-canary.1",
159
+ "node-mocks-http": "^1.11.0",
160
+ "openapi-types": "^12.0.2",
161
+ "prettier": "^2.7.1",
162
+ "rate-limiter-flexible": "^2.3.11",
163
+ "react": "^18.2.0",
164
+ "react-dom": "^18.2.0",
165
+ "read-pkg": "^7.1.0",
166
+ "redoc": "^2.0.0",
167
+ "rimraf": "^3.0.2",
168
+ "semantic-release": "^19.0.5",
169
+ "styled-components": "^5.3.6",
170
+ "swagger-ui-react": "^4.14.3",
171
+ "tsup": "^6.2.3",
172
+ "typescript": "^4.8.4",
173
+ "vitest": "^0.24.1",
174
+ "webpack": "^5.74.0",
175
+ "zod": "^3.19.1"
176
+ },
177
+ "peerDependencies": {
178
+ "next": "^12.3.1",
179
+ "react": "^18.2.0",
180
+ "react-dom": "^18.2.0",
181
+ "zod": "^3.19.1"
182
+ },
183
+ "optionalDependencies": {
184
+ "@visulima/crud": "1.0.0",
185
+ "@visulima/readdir": "1.3.0",
186
+ "chalk": "4.1.2",
187
+ "commander": "^9.4.1",
188
+ "cors": "^2.8.5",
189
+ "rate-limiter-flexible": "^2.3.11",
190
+ "swagger-ui-react": "^4.14.3",
191
+ "redoc": "^2.0.0",
192
+ "webpack": "^5.74.0"
193
+ },
194
+ "engines": {
195
+ "node": ">=16"
196
+ },
197
+ "publishConfig": {
198
+ "access": "public"
199
+ },
200
+ "sources": [
201
+ "src/index-browser.tsx",
202
+ "src/index-server.ts",
203
+ "src/next/index-browser.tsx",
204
+ "src/next/index-server.ts",
205
+ "src/next/cli/index.ts"
206
+ ]
207
+ }
@@ -0,0 +1,44 @@
1
+ import { NodeRouter } from "@visulima/connect";
2
+ import type { IncomingMessage, ServerResponse } from "node:http";
3
+ import type { AnyZodObject } from "zod";
4
+ import { ZodObject } from "zod";
5
+
6
+ import type { ErrorHandlers } from "../error-handler/types";
7
+ import { onError, onNoMatch } from "./handler";
8
+ import httpHeaderNormalizerMiddleware from "./middleware/http-header-normalizer";
9
+ import type { Serializers } from "./middleware/serializers-middleware";
10
+ import serializersMiddleware from "./middleware/serializers-middleware";
11
+
12
+ const createNodeRouter = <
13
+ Request extends IncomingMessage,
14
+ Response extends ServerResponse,
15
+ Schema extends AnyZodObject = ZodObject<{ body?: AnyZodObject; headers?: AnyZodObject; query?: AnyZodObject }>,
16
+ >(
17
+ options: {
18
+ middlewares?: {
19
+ "http-header-normalizer"?: { canonical?: boolean; normalizeHeaderKey?: (key: string, canonical: boolean) => string };
20
+ serializers?: {
21
+ serializers?: Serializers;
22
+ defaultContentType?: string;
23
+ };
24
+ };
25
+ errorHandlers?: ErrorHandlers;
26
+ showTrace?: boolean;
27
+ } = {},
28
+ ) => {
29
+ const router = new NodeRouter<Request, Response, Schema>({
30
+ onNoMatch,
31
+ onError: onError(options.errorHandlers || [], options.showTrace || false),
32
+ });
33
+
34
+ return router
35
+ .use(httpHeaderNormalizerMiddleware(options?.middlewares?.["http-header-normalizer"] || {}))
36
+ .use(
37
+ serializersMiddleware(
38
+ options?.middlewares?.serializers?.serializers || [],
39
+ options?.middlewares?.serializers?.defaultContentType || "application/json; charset=utf-8",
40
+ ),
41
+ );
42
+ };
43
+
44
+ export default createNodeRouter;
@@ -0,0 +1,46 @@
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
+ };
@@ -0,0 +1,10 @@
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;
@@ -0,0 +1,93 @@
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;
@@ -0,0 +1,43 @@
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;