@visulima/api-platform 1.0.2 → 1.1.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 (73) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/index.js +46 -0
  3. package/dist/chunk-AOL5OFCG.mjs +284 -0
  4. package/dist/chunk-AOL5OFCG.mjs.map +1 -0
  5. package/dist/chunk-ATZDRT65.js +284 -0
  6. package/dist/chunk-ATZDRT65.js.map +1 -0
  7. package/dist/{chunk-AJKZCWFG.js → chunk-F7RHRCUQ.js} +2 -2
  8. package/dist/chunk-F7RHRCUQ.js.map +1 -0
  9. package/dist/{chunk-XXZ56SKG.mjs → chunk-RPHC5ZGB.mjs} +2 -2
  10. package/dist/chunk-RPHC5ZGB.mjs.map +1 -0
  11. package/dist/index-server.d.ts +4 -14
  12. package/dist/index-server.js +9 -10
  13. package/dist/index-server.js.map +1 -1
  14. package/dist/index-server.mjs +7 -8
  15. package/dist/index-server.mjs.map +1 -1
  16. package/dist/next/cli/index.js +8 -8
  17. package/dist/next/cli/index.js.map +1 -1
  18. package/dist/next/cli/index.mjs +8 -8
  19. package/dist/next/cli/index.mjs.map +1 -1
  20. package/dist/next/index-browser.js +2 -2
  21. package/dist/next/index-browser.mjs +1 -1
  22. package/dist/next/index-server.d.ts +14 -13
  23. package/dist/next/index-server.js +4 -4
  24. package/dist/next/index-server.js.map +1 -1
  25. package/dist/next/index-server.mjs +2 -2
  26. package/dist/next/index-server.mjs.map +1 -1
  27. package/dist/swagger-handler-ffed72c2.d.ts +19 -0
  28. package/next/cli/package.json +12 -3
  29. package/next/package.json +9 -0
  30. package/package.json +20 -20
  31. package/recipes/api/swagger.ts +12 -0
  32. package/recipes/pages/redoc-ui.tsx +5 -0
  33. package/recipes/pages/swagger-ui.tsx +5 -0
  34. package/dist/chunk-2LATTLUM.mjs +0 -166
  35. package/dist/chunk-2LATTLUM.mjs.map +0 -1
  36. package/dist/chunk-AJKZCWFG.js.map +0 -1
  37. package/dist/chunk-S7GUPAL4.js +0 -166
  38. package/dist/chunk-S7GUPAL4.js.map +0 -1
  39. package/dist/chunk-XXZ56SKG.mjs.map +0 -1
  40. package/src/connect/create-node-router.ts +0 -44
  41. package/src/connect/handler.ts +0 -46
  42. package/src/connect/middleware/cors-middleware.ts +0 -10
  43. package/src/connect/middleware/http-header-normalizer.ts +0 -93
  44. package/src/connect/middleware/rate-limiter-middleware.ts +0 -43
  45. package/src/connect/middleware/serializers-middleware.ts +0 -121
  46. package/src/connect/serializers/types.d.ts +0 -1
  47. package/src/connect/serializers/xml.ts +0 -13
  48. package/src/connect/serializers/yaml.ts +0 -7
  49. package/src/error-handler/jsonapi-error-handler.ts +0 -46
  50. package/src/error-handler/problem-error-handler.ts +0 -44
  51. package/src/error-handler/types.d.ts +0 -14
  52. package/src/error-handler/utils.ts +0 -39
  53. package/src/index-browser.tsx +0 -1
  54. package/src/index-server.ts +0 -75
  55. package/src/next/cli/index.ts +0 -2
  56. package/src/next/cli/list/api-route-file-parser.ts +0 -74
  57. package/src/next/cli/list/collect-api-route-files.ts +0 -42
  58. package/src/next/cli/list/list-command.ts +0 -105
  59. package/src/next/cli/list/routes-render.ts +0 -62
  60. package/src/next/cli/list/types.d.ts +0 -1
  61. package/src/next/index-browser.tsx +0 -3
  62. package/src/next/index-server.ts +0 -6
  63. package/src/next/routes/api/swagger.ts +0 -23
  64. package/src/next/routes/pages/swagger/get-static-properties-swagger.ts +0 -32
  65. package/src/next/routes/pages/swagger/redoc.tsx +0 -35
  66. package/src/next/routes/pages/swagger/swagger.tsx +0 -44
  67. package/src/next/webpack/with-open-api.ts +0 -63
  68. package/src/swagger/extend-swagger-spec.ts +0 -167
  69. package/src/swagger/swagger-handler.ts +0 -83
  70. package/src/utils.ts +0 -37
  71. package/src/zod/date-in-schema.ts +0 -57
  72. package/src/zod/date-out-schema.ts +0 -41
  73. package/src/zod/index.ts +0 -9
@@ -1,63 +0,0 @@
1
- import type { BaseDefinition } from "@visulima/jsdoc-open-api";
2
- import { SwaggerCompilerPlugin } from "@visulima/jsdoc-open-api";
3
- import type { NextConfig } from "next";
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import type { Configuration } from "webpack";
7
-
8
- const withOpenApi = ({
9
- definition, sources, verbose, output = "swagger/swagger.json",
10
- // eslint-disable-next-line max-len
11
- }: { definition: Exclude<BaseDefinition, "openapi"> & { openapi?: string }; sources: string[]; verbose?: boolean; output: string }) => (nextConfig: NextConfig) => {
12
- return {
13
- ...nextConfig,
14
- webpack: (config: Configuration, options: any) => {
15
- if (!options.isServer) {
16
- return config;
17
- }
18
-
19
- if (output.startsWith("/")) {
20
- // eslint-disable-next-line no-param-reassign
21
- output = output.slice(1);
22
- }
23
-
24
- if (!output.endsWith(".json")) {
25
- throw new Error("The output path must end with .json");
26
- }
27
-
28
- // eslint-disable-next-line no-param-reassign
29
- config = {
30
- ...config,
31
- plugins: [
32
- // @ts-ignore
33
- ...config.plugins,
34
- new SwaggerCompilerPlugin(
35
- `${options.dir}/${output}`,
36
- sources.map((source) => {
37
- const combinedPath = path.join(options.dir, source.replace("./", ""));
38
-
39
- // Check if the path is a directory
40
- fs.lstatSync(combinedPath).isDirectory();
41
-
42
- return combinedPath;
43
- }),
44
- {
45
- // @ts-ignore
46
- openapi: "3.0.0",
47
- ...definition,
48
- },
49
- { verbose },
50
- ),
51
- ],
52
- };
53
-
54
- if (typeof nextConfig.webpack === "function") {
55
- return nextConfig.webpack(config, options);
56
- }
57
-
58
- return config;
59
- },
60
- };
61
- };
62
-
63
- export default withOpenApi;
@@ -1,167 +0,0 @@
1
- import { header as headerCase } from "case";
2
- import type { OpenAPIV3 } from "openapi-types";
3
- import type { OAS3Definition, Operation, Responses } from "swagger-jsdoc";
4
-
5
- const extendComponentSchemas = (spec: Partial<OAS3Definition>, schemaName: string, schema: OpenAPIV3.SchemaObject) => {
6
- if (typeof spec.components !== "object") {
7
- // eslint-disable-next-line no-param-reassign
8
- spec.components = {};
9
- }
10
-
11
- if (typeof spec.components.schemas !== "object") {
12
- // eslint-disable-next-line no-param-reassign
13
- spec.components.schemas = {};
14
- }
15
-
16
- if (typeof spec.components.schemas[schemaName] === "undefined") {
17
- // eslint-disable-next-line no-param-reassign
18
- spec.components.schemas[schemaName] = schema;
19
- }
20
- };
21
-
22
- const extendComponentExamples = (spec: Partial<OAS3Definition>, exampleName: string, example: OpenAPIV3.SchemaObject) => {
23
- if (typeof spec.components !== "object") {
24
- // eslint-disable-next-line no-param-reassign
25
- spec.components = {};
26
- }
27
-
28
- if (typeof spec.components.examples !== "object") {
29
- // eslint-disable-next-line no-param-reassign
30
- spec.components.examples = {};
31
- }
32
-
33
- if (typeof spec.components.examples[exampleName] === "undefined") {
34
- // eslint-disable-next-line no-param-reassign
35
- spec.components.examples[exampleName] = example;
36
- }
37
- };
38
-
39
- function extendSwaggerWithMediaTypeSchema(
40
- responseSpec: OpenAPIV3.ResponseObject,
41
- allowedMediaTypes: { [p: string]: boolean } | undefined,
42
- pathKey: string,
43
- spec: Partial<OAS3Definition>,
44
- methodSpec: Operation,
45
- status: string,
46
- ) {
47
- let examples:
48
- | {
49
- [media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;
50
- }
51
- | undefined;
52
-
53
- Object.entries(responseSpec.content as object).forEach(([mediaName, contentSpec]) => {
54
- if (typeof contentSpec.schema === "object") {
55
- const { schema } = contentSpec;
56
-
57
- if (mediaName === "application/json" && typeof contentSpec.examples !== "undefined") {
58
- examples = contentSpec.examples;
59
- }
60
-
61
- if (typeof (schema as OpenAPIV3.ReferenceObject).$ref !== "undefined") {
62
- return;
63
- }
64
-
65
- const schemaIsArray = (schema as OpenAPIV3.SchemaObject).type === "array";
66
-
67
- Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
68
- if (!allowed) {
69
- return;
70
- }
71
-
72
- // eslint-disable-next-line max-len
73
- const schemaName = `${headerCase(pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
74
-
75
- extendComponentSchemas(spec as OAS3Definition, schemaName, schema as OpenAPIV3.SchemaObject);
76
-
77
- if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.schema === "undefined") {
78
- // eslint-disable-next-line no-param-reassign
79
- (methodSpec.responses as Responses)[status].content[mediaType] = { schema: {} };
80
- }
81
-
82
- // eslint-disable-next-line no-param-reassign
83
- (methodSpec.responses as Responses)[status].content[mediaType].schema = schemaIsArray
84
- ? {
85
- type: "array",
86
- items: {
87
- $ref: `#/components/schemas/${schemaName}`,
88
- },
89
- }
90
- : {
91
- $ref: `#/components/schemas/${schemaName}`,
92
- };
93
-
94
- if (typeof methodSpec.produces === "undefined") {
95
- // eslint-disable-next-line no-param-reassign
96
- methodSpec.produces = [];
97
- }
98
-
99
- methodSpec.produces.push(mediaType);
100
- });
101
- }
102
- });
103
-
104
- return examples;
105
- }
106
-
107
- function extendSwaggerWithMediaTypeExamples(
108
- responseSpec: OpenAPIV3.ResponseObject,
109
- allowedMediaTypes: { [p: string]: boolean } | undefined,
110
- pathKey: string,
111
- spec: Partial<OAS3Definition>,
112
- examples: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject } | undefined,
113
- methodSpec: Operation,
114
- status: string,
115
- ) {
116
- Object.keys(responseSpec.content as object).forEach((mediaName) => {
117
- if (mediaName === "application/json") {
118
- return;
119
- }
120
-
121
- Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
122
- if (!allowed) {
123
- return;
124
- }
125
-
126
- // eslint-disable-next-line max-len
127
- const examplesName = `${headerCase(pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
128
-
129
- extendComponentExamples(spec as OAS3Definition, examplesName, examples as OpenAPIV3.SchemaObject);
130
-
131
- if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.examples === "undefined") {
132
- // eslint-disable-next-line no-param-reassign
133
- (methodSpec.responses as Responses)[status].content[mediaType] = { examples: {} };
134
- }
135
-
136
- // eslint-disable-next-line no-param-reassign
137
- (methodSpec.responses as Responses)[status].content[mediaType].examples = examples;
138
- });
139
- });
140
- }
141
-
142
- // eslint-disable-next-line radar/cognitive-complexity
143
- export default function extendSwaggerSpec(spec: Partial<OAS3Definition>, allowedMediaTypes?: { [key: string]: boolean }): Partial<OAS3Definition> {
144
- if (typeof spec === "object" && typeof spec.paths === "object") {
145
- Object.entries(spec.paths).forEach(([pathKey, pathSpec]) => {
146
- Object.values(pathSpec).forEach((methodSpec) => {
147
- if (typeof methodSpec.responses === "object") {
148
- Object.entries<OpenAPIV3.ResponseObject>(methodSpec.responses).forEach(([status, responseSpec]) => {
149
- if (typeof responseSpec.content === "object") {
150
- let examples:
151
- | {
152
- [media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;
153
- }
154
- | undefined = extendSwaggerWithMediaTypeSchema(responseSpec, allowedMediaTypes, pathKey, spec, methodSpec, status);
155
-
156
- if (typeof examples !== "undefined") {
157
- extendSwaggerWithMediaTypeExamples(responseSpec, allowedMediaTypes, pathKey, spec, examples, methodSpec, status);
158
- }
159
- }
160
- });
161
- }
162
- });
163
- });
164
- }
165
-
166
- return spec;
167
- }
@@ -1,83 +0,0 @@
1
- // eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies
2
- import type { ModelsToOpenApiParameters, SwaggerModelsConfig } from "@visulima/crud";
3
- // eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies
4
- import { modelsToOpenApi } from "@visulima/crud";
5
- import debug from "debug";
6
- import merge from "lodash.merge";
7
- import { existsSync, readFileSync } from "node:fs";
8
- import type { IncomingMessage, ServerResponse } from "node:http";
9
- import path from "node:path";
10
- import type { OAS3Definition, Tag } from "swagger-jsdoc";
11
-
12
- import yamlTransformer from "../connect/serializers/yaml";
13
- import extendSwaggerSpec from "./extend-swagger-spec";
14
-
15
- // eslint-disable-next-line testing-library/no-debugging-utils
16
- const swaggerCrudDebug = debug("visulima:api-platform:swagger:crud:get-static-properties-swagger");
17
-
18
- const swaggerHandler = (
19
- options: Partial<{
20
- allowedMediaTypes: { [key: string]: boolean };
21
- swaggerFilePath: string;
22
- crud: Exclude<ModelsToOpenApiParameters, "swagger"> & {
23
- swagger?: {
24
- models?: SwaggerModelsConfig<string>;
25
- };
26
- };
27
- }> = {},
28
- ) => {
29
- const {
30
- allowedMediaTypes = {
31
- "application/json": true,
32
- },
33
- swaggerFilePath,
34
- crud,
35
- } = options;
36
-
37
- return async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response) => {
38
- const swaggerPath = path.join(process.cwd(), swaggerFilePath || "swagger/swagger.json");
39
-
40
- if (!existsSync(swaggerPath)) {
41
- throw new Error(`Swagger file not found at ${swaggerPath}. Did you change the output path in "withOpenApi" inside the next.config.js file?`);
42
- }
43
-
44
- const fileContents = readFileSync(swaggerPath, "utf8");
45
-
46
- let spec = extendSwaggerSpec(JSON.parse(fileContents) as OAS3Definition, allowedMediaTypes) as OAS3Definition;
47
- let crudSwagger: Partial<OAS3Definition> = {};
48
-
49
- if (typeof crud !== "undefined") {
50
- try {
51
- const modelsOpenApi = await modelsToOpenApi(crud);
52
-
53
- crudSwagger = {
54
- components: { schemas: modelsOpenApi.schemas, examples: modelsOpenApi.examples },
55
- tags: modelsOpenApi.tags as Tag[],
56
- paths: modelsOpenApi.paths,
57
- };
58
-
59
- crudSwagger = extendSwaggerSpec(crudSwagger, allowedMediaTypes);
60
-
61
- swaggerCrudDebug(JSON.stringify(crudSwagger, null, 2));
62
-
63
- spec = merge(spec, crudSwagger);
64
- } catch (error) {
65
- // eslint-disable-next-line no-console
66
- console.log(error);
67
- throw new Error("Please install @visulima/crud to use the crud swagger generator.");
68
- }
69
- }
70
-
71
- if (typeof request.headers.accept === "string" && /yaml|yml/.test(request.headers.accept)) {
72
- response.statusCode = 200;
73
- response.setHeader("Content-Type", request.headers.accept);
74
- response.end(yamlTransformer(spec));
75
- } else {
76
- response.statusCode = 200;
77
- response.setHeader("Content-Type", "application/json");
78
- response.end(JSON.stringify(spec, null, 2));
79
- }
80
- };
81
- };
82
-
83
- export default swaggerHandler;
package/src/utils.ts DELETED
@@ -1,37 +0,0 @@
1
- import { IncomingMessage } from "node:http";
2
- import { parse as urlParse } from "node:url";
3
-
4
- type IncomingApiRequest<TApiRequest = IncomingMessage> = TApiRequest & {
5
- body?: any;
6
- query?: any;
7
- };
8
-
9
- export const jsonResponse = (response: any, status: number, data?: unknown): void => {
10
- response.statusCode = status;
11
- response.setHeader("Content-Type", "application/json");
12
- response.end(data ? JSON.stringify(data) : "");
13
- };
14
-
15
- export const parseBody = async (request: IncomingApiRequest): Promise<unknown> => {
16
- if (request.body) {
17
- return request.body;
18
- }
19
-
20
- const buffers = [];
21
-
22
- // eslint-disable-next-line no-restricted-syntax
23
- for await (const chunk of request) {
24
- buffers.push(chunk);
25
- }
26
-
27
- const data = Buffer.concat(buffers).toString();
28
-
29
- return data ? JSON.parse(data) : null;
30
- };
31
-
32
- export const parseQuery = (request: IncomingApiRequest): unknown => {
33
- if (request.query) {
34
- return request.query;
35
- }
36
- return urlParse(request.url ?? "", true).query;
37
- };
@@ -1,57 +0,0 @@
1
- import type { ParseInput, ParseReturnType, ZodTypeDef } from "zod";
2
- import {
3
- addIssueToContext, INVALID, ZodIssueCode, ZodParsedType, ZodType,
4
- } from "zod";
5
-
6
- const zodDateInKind = "ZodDateIn";
7
-
8
- // simple regex for ISO date, supports the following formats:
9
- // 2021-01-01T00:00:00.000Z
10
- // 2021-01-01T00:00:00Z
11
- // 2021-01-01T00:00:00
12
- // 2021-01-01
13
- export const isoDateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?)?Z?$/;
14
-
15
- // eslint-disable-next-line unicorn/prevent-abbreviations
16
- export interface ZodDateInDef extends ZodTypeDef {
17
- typeName: typeof zodDateInKind;
18
- }
19
-
20
- export class ZodDateIn extends ZodType<Date, ZodDateInDef, string> {
21
- // eslint-disable-next-line no-underscore-dangle
22
- _parse(input: ParseInput): ParseReturnType<Date> {
23
- // eslint-disable-next-line no-underscore-dangle
24
- const { status, ctx } = this._processInputParams(input);
25
- if (ctx.parsedType !== ZodParsedType.string) {
26
- addIssueToContext(ctx, {
27
- code: ZodIssueCode.invalid_type,
28
- expected: ZodParsedType.string,
29
- received: ctx.parsedType,
30
- });
31
- return INVALID;
32
- }
33
-
34
- if (!isoDateRegex.test(ctx.data as string)) {
35
- addIssueToContext(ctx, {
36
- code: ZodIssueCode.invalid_string,
37
- validation: "regex",
38
- });
39
- status.dirty();
40
- }
41
-
42
- const date = new Date(ctx.data);
43
-
44
- if (Number.isNaN(date.getTime())) {
45
- addIssueToContext(ctx, {
46
- code: ZodIssueCode.invalid_date,
47
- });
48
- return INVALID;
49
- }
50
-
51
- return { status: status.value, value: date };
52
- }
53
-
54
- static create = () => new ZodDateIn({
55
- typeName: zodDateInKind,
56
- });
57
- }
@@ -1,41 +0,0 @@
1
- import type { ParseInput, ParseReturnType, ZodTypeDef } from "zod";
2
- import {
3
- addIssueToContext, INVALID, ZodIssueCode, ZodParsedType, ZodType,
4
- } from "zod";
5
-
6
- const zodDateOutKind = "ZodDateOut";
7
-
8
- // eslint-disable-next-line unicorn/prevent-abbreviations
9
- export interface ZodDateOutDef extends ZodTypeDef {
10
- typeName: typeof zodDateOutKind;
11
- }
12
-
13
- export class ZodDateOut extends ZodType<string, ZodDateOutDef, Date> {
14
- // eslint-disable-next-line no-underscore-dangle
15
- _parse(input: ParseInput): ParseReturnType<string> {
16
- // eslint-disable-next-line no-underscore-dangle
17
- const { status, ctx } = this._processInputParams(input);
18
-
19
- if (ctx.parsedType !== ZodParsedType.date) {
20
- addIssueToContext(ctx, {
21
- code: ZodIssueCode.invalid_type,
22
- expected: ZodParsedType.date,
23
- received: ctx.parsedType,
24
- });
25
- return INVALID;
26
- }
27
-
28
- if (Number.isNaN(ctx.data.getTime())) {
29
- addIssueToContext(ctx, {
30
- code: ZodIssueCode.invalid_date,
31
- });
32
- return INVALID;
33
- }
34
-
35
- return { status: status.value, value: (ctx.data as Date).toISOString() };
36
- }
37
-
38
- static create = () => new ZodDateOut({
39
- typeName: zodDateOutKind,
40
- });
41
- }
package/src/zod/index.ts DELETED
@@ -1,9 +0,0 @@
1
- import { withGetType } from "zod-to-ts";
2
-
3
- import { ZodDateIn } from "./date-in-schema";
4
- import { ZodDateOut } from "./date-out-schema";
5
-
6
- // eslint-disable-next-line max-len
7
- export const dateIn = (...parameters: Parameters<typeof ZodDateIn.create>) => withGetType(ZodDateIn.create(...parameters), (ts) => ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
8
- // eslint-disable-next-line max-len
9
- export const dateOut = (...parameters: Parameters<typeof ZodDateOut.create>) => withGetType(ZodDateOut.create(...parameters), (ts) => ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));