enlace-openapi 0.0.1-beta.3 → 0.0.1-beta.5
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 +109 -5
- package/dist/cli.js +18 -12
- package/dist/cli.mjs +18 -12
- package/dist/index.js +18 -12
- package/dist/index.mjs +18 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,13 +135,11 @@ This generates:
|
|
|
135
135
|
}
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
## Endpoint
|
|
138
|
+
## Endpoint Types
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
### `Endpoint<TData, TBody?, TError?>`
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
Endpoint<TData, TBody?, TError?>
|
|
144
|
-
```
|
|
142
|
+
For endpoints with JSON body:
|
|
145
143
|
|
|
146
144
|
| Parameter | Description |
|
|
147
145
|
|-----------|-------------|
|
|
@@ -149,6 +147,112 @@ Endpoint<TData, TBody?, TError?>
|
|
|
149
147
|
| `TBody` | Request body type (optional) |
|
|
150
148
|
| `TError` | Error response type (optional) |
|
|
151
149
|
|
|
150
|
+
### `EndpointWithQuery<TData, TQuery, TError?>`
|
|
151
|
+
|
|
152
|
+
For endpoints with typed query parameters:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { EndpointWithQuery } from "enlace-core";
|
|
156
|
+
|
|
157
|
+
type ApiSchema = {
|
|
158
|
+
users: {
|
|
159
|
+
$get: EndpointWithQuery<User[], { page: number; limit: number; search?: string }>;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Generated OpenAPI:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"/users": {
|
|
169
|
+
"get": {
|
|
170
|
+
"parameters": [
|
|
171
|
+
{ "name": "page", "in": "query", "required": true, "schema": { "type": "number" } },
|
|
172
|
+
{ "name": "limit", "in": "query", "required": true, "schema": { "type": "number" } },
|
|
173
|
+
{ "name": "search", "in": "query", "required": false, "schema": { "type": "string" } }
|
|
174
|
+
],
|
|
175
|
+
"responses": { "200": { "..." } }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `EndpointWithFormData<TData, TFormData, TError?>`
|
|
182
|
+
|
|
183
|
+
For file upload endpoints (multipart/form-data):
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { EndpointWithFormData } from "enlace-core";
|
|
187
|
+
|
|
188
|
+
type ApiSchema = {
|
|
189
|
+
uploads: {
|
|
190
|
+
$post: EndpointWithFormData<Upload, { file: Blob | File; name: string }>;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Generated OpenAPI:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"/uploads": {
|
|
200
|
+
"post": {
|
|
201
|
+
"requestBody": {
|
|
202
|
+
"required": true,
|
|
203
|
+
"content": {
|
|
204
|
+
"multipart/form-data": {
|
|
205
|
+
"schema": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"properties": {
|
|
208
|
+
"file": { "type": "string", "format": "binary" },
|
|
209
|
+
"name": { "type": "string" }
|
|
210
|
+
},
|
|
211
|
+
"required": ["file", "name"]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"responses": { "200": { "..." } }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `EndpointFull<T>`
|
|
223
|
+
|
|
224
|
+
Object-style for complex endpoints with multiple options:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { EndpointFull } from "enlace-core";
|
|
228
|
+
|
|
229
|
+
type ApiSchema = {
|
|
230
|
+
products: {
|
|
231
|
+
$post: EndpointFull<{
|
|
232
|
+
data: Product;
|
|
233
|
+
body: CreateProduct;
|
|
234
|
+
query: { categoryId: string };
|
|
235
|
+
error: ValidationError;
|
|
236
|
+
}>;
|
|
237
|
+
};
|
|
238
|
+
files: {
|
|
239
|
+
$post: EndpointFull<{
|
|
240
|
+
data: FileUpload;
|
|
241
|
+
formData: { file: File; description: string };
|
|
242
|
+
query: { folder: string };
|
|
243
|
+
}>;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
| Property | Description | OpenAPI Mapping |
|
|
249
|
+
|----------|-------------|-----------------|
|
|
250
|
+
| `data` | Response data type | `responses.200.content` |
|
|
251
|
+
| `body` | JSON request body | `requestBody` with `application/json` |
|
|
252
|
+
| `query` | Query parameters | `parameters` with `in: "query"` |
|
|
253
|
+
| `formData` | FormData fields | `requestBody` with `multipart/form-data` |
|
|
254
|
+
| `error` | Error response type | `responses.400.content` |
|
|
255
|
+
|
|
152
256
|
## Path Parameters
|
|
153
257
|
|
|
154
258
|
Use `_` to define dynamic path segments:
|
package/dist/cli.js
CHANGED
|
@@ -33,14 +33,19 @@ var import_path = __toESM(require("path"));
|
|
|
33
33
|
|
|
34
34
|
// src/type-to-schema.ts
|
|
35
35
|
var import_typescript = __toESM(require("typescript"));
|
|
36
|
+
var MAX_DEPTH = 50;
|
|
36
37
|
function createSchemaContext(checker) {
|
|
37
38
|
return {
|
|
38
39
|
checker,
|
|
39
40
|
schemas: /* @__PURE__ */ new Map(),
|
|
40
|
-
visitedTypes: /* @__PURE__ */ new Set()
|
|
41
|
+
visitedTypes: /* @__PURE__ */ new Set(),
|
|
42
|
+
depth: 0
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
function typeToSchema(type, ctx) {
|
|
46
|
+
if (ctx.depth > MAX_DEPTH) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
44
49
|
const { checker } = ctx;
|
|
45
50
|
if (type.flags & import_typescript.default.TypeFlags.String) {
|
|
46
51
|
return { type: "string" };
|
|
@@ -79,7 +84,7 @@ function typeToSchema(type, ctx) {
|
|
|
79
84
|
);
|
|
80
85
|
const hasNull = type.types.some((t) => t.flags & import_typescript.default.TypeFlags.Null);
|
|
81
86
|
if (nonNullTypes.length === 1 && hasNull) {
|
|
82
|
-
const schema = typeToSchema(nonNullTypes[0], ctx);
|
|
87
|
+
const schema = typeToSchema(nonNullTypes[0], { ...ctx, depth: ctx.depth + 1 });
|
|
83
88
|
return { ...schema, nullable: true };
|
|
84
89
|
}
|
|
85
90
|
if (nonNullTypes.every((t) => t.isStringLiteral())) {
|
|
@@ -94,19 +99,20 @@ function typeToSchema(type, ctx) {
|
|
|
94
99
|
enum: nonNullTypes.map((t) => t.value)
|
|
95
100
|
};
|
|
96
101
|
}
|
|
102
|
+
const nextCtx = { ...ctx, depth: ctx.depth + 1 };
|
|
97
103
|
return {
|
|
98
|
-
oneOf: nonNullTypes.map((t) => typeToSchema(t,
|
|
104
|
+
oneOf: nonNullTypes.map((t) => typeToSchema(t, nextCtx))
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
107
|
if (type.isIntersection()) {
|
|
102
|
-
return intersectionTypeToSchema(type, ctx);
|
|
108
|
+
return intersectionTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
103
109
|
}
|
|
104
110
|
if (checker.isArrayType(type)) {
|
|
105
111
|
const typeArgs = type.typeArguments;
|
|
106
112
|
if (typeArgs?.[0]) {
|
|
107
113
|
return {
|
|
108
114
|
type: "array",
|
|
109
|
-
items: typeToSchema(typeArgs[0], ctx)
|
|
115
|
+
items: typeToSchema(typeArgs[0], { ...ctx, depth: ctx.depth + 1 })
|
|
110
116
|
};
|
|
111
117
|
}
|
|
112
118
|
return { type: "array" };
|
|
@@ -118,18 +124,18 @@ function typeToSchema(type, ctx) {
|
|
|
118
124
|
return { type: "string", format: "date-time" };
|
|
119
125
|
}
|
|
120
126
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
121
|
-
if (ctx.visitedTypes.has(
|
|
127
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
122
128
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
123
129
|
}
|
|
124
130
|
if (!ctx.schemas.has(typeName)) {
|
|
125
|
-
ctx.visitedTypes.add(
|
|
126
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
131
|
+
ctx.visitedTypes.add(typeName);
|
|
132
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
127
133
|
ctx.schemas.set(typeName, schema);
|
|
128
|
-
ctx.visitedTypes.delete(
|
|
134
|
+
ctx.visitedTypes.delete(typeName);
|
|
129
135
|
}
|
|
130
136
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
131
137
|
}
|
|
132
|
-
return objectTypeToSchema(type, ctx);
|
|
138
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
133
139
|
}
|
|
134
140
|
return {};
|
|
135
141
|
}
|
|
@@ -142,7 +148,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
142
148
|
const propName = prop.getName();
|
|
143
149
|
const propType = checker.getTypeOfSymbol(prop);
|
|
144
150
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
145
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
151
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
146
152
|
if (!isOptional) {
|
|
147
153
|
required.push(propName);
|
|
148
154
|
}
|
|
@@ -167,7 +173,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
167
173
|
if (propName.startsWith("__")) continue;
|
|
168
174
|
const propType = checker.getTypeOfSymbol(prop);
|
|
169
175
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
170
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
176
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
171
177
|
if (!isOptional && !required.includes(propName)) {
|
|
172
178
|
required.push(propName);
|
|
173
179
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -10,14 +10,19 @@ import path from "path";
|
|
|
10
10
|
|
|
11
11
|
// src/type-to-schema.ts
|
|
12
12
|
import ts from "typescript";
|
|
13
|
+
var MAX_DEPTH = 50;
|
|
13
14
|
function createSchemaContext(checker) {
|
|
14
15
|
return {
|
|
15
16
|
checker,
|
|
16
17
|
schemas: /* @__PURE__ */ new Map(),
|
|
17
|
-
visitedTypes: /* @__PURE__ */ new Set()
|
|
18
|
+
visitedTypes: /* @__PURE__ */ new Set(),
|
|
19
|
+
depth: 0
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
function typeToSchema(type, ctx) {
|
|
23
|
+
if (ctx.depth > MAX_DEPTH) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
21
26
|
const { checker } = ctx;
|
|
22
27
|
if (type.flags & ts.TypeFlags.String) {
|
|
23
28
|
return { type: "string" };
|
|
@@ -56,7 +61,7 @@ function typeToSchema(type, ctx) {
|
|
|
56
61
|
);
|
|
57
62
|
const hasNull = type.types.some((t) => t.flags & ts.TypeFlags.Null);
|
|
58
63
|
if (nonNullTypes.length === 1 && hasNull) {
|
|
59
|
-
const schema = typeToSchema(nonNullTypes[0], ctx);
|
|
64
|
+
const schema = typeToSchema(nonNullTypes[0], { ...ctx, depth: ctx.depth + 1 });
|
|
60
65
|
return { ...schema, nullable: true };
|
|
61
66
|
}
|
|
62
67
|
if (nonNullTypes.every((t) => t.isStringLiteral())) {
|
|
@@ -71,19 +76,20 @@ function typeToSchema(type, ctx) {
|
|
|
71
76
|
enum: nonNullTypes.map((t) => t.value)
|
|
72
77
|
};
|
|
73
78
|
}
|
|
79
|
+
const nextCtx = { ...ctx, depth: ctx.depth + 1 };
|
|
74
80
|
return {
|
|
75
|
-
oneOf: nonNullTypes.map((t) => typeToSchema(t,
|
|
81
|
+
oneOf: nonNullTypes.map((t) => typeToSchema(t, nextCtx))
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
84
|
if (type.isIntersection()) {
|
|
79
|
-
return intersectionTypeToSchema(type, ctx);
|
|
85
|
+
return intersectionTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
80
86
|
}
|
|
81
87
|
if (checker.isArrayType(type)) {
|
|
82
88
|
const typeArgs = type.typeArguments;
|
|
83
89
|
if (typeArgs?.[0]) {
|
|
84
90
|
return {
|
|
85
91
|
type: "array",
|
|
86
|
-
items: typeToSchema(typeArgs[0], ctx)
|
|
92
|
+
items: typeToSchema(typeArgs[0], { ...ctx, depth: ctx.depth + 1 })
|
|
87
93
|
};
|
|
88
94
|
}
|
|
89
95
|
return { type: "array" };
|
|
@@ -95,18 +101,18 @@ function typeToSchema(type, ctx) {
|
|
|
95
101
|
return { type: "string", format: "date-time" };
|
|
96
102
|
}
|
|
97
103
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
98
|
-
if (ctx.visitedTypes.has(
|
|
104
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
99
105
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
100
106
|
}
|
|
101
107
|
if (!ctx.schemas.has(typeName)) {
|
|
102
|
-
ctx.visitedTypes.add(
|
|
103
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
108
|
+
ctx.visitedTypes.add(typeName);
|
|
109
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
104
110
|
ctx.schemas.set(typeName, schema);
|
|
105
|
-
ctx.visitedTypes.delete(
|
|
111
|
+
ctx.visitedTypes.delete(typeName);
|
|
106
112
|
}
|
|
107
113
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
108
114
|
}
|
|
109
|
-
return objectTypeToSchema(type, ctx);
|
|
115
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
110
116
|
}
|
|
111
117
|
return {};
|
|
112
118
|
}
|
|
@@ -119,7 +125,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
119
125
|
const propName = prop.getName();
|
|
120
126
|
const propType = checker.getTypeOfSymbol(prop);
|
|
121
127
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
122
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
128
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
123
129
|
if (!isOptional) {
|
|
124
130
|
required.push(propName);
|
|
125
131
|
}
|
|
@@ -144,7 +150,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
144
150
|
if (propName.startsWith("__")) continue;
|
|
145
151
|
const propType = checker.getTypeOfSymbol(prop);
|
|
146
152
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
147
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
153
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
148
154
|
if (!isOptional && !required.includes(propName)) {
|
|
149
155
|
required.push(propName);
|
|
150
156
|
}
|
package/dist/index.js
CHANGED
|
@@ -41,14 +41,19 @@ var import_path = __toESM(require("path"));
|
|
|
41
41
|
|
|
42
42
|
// src/type-to-schema.ts
|
|
43
43
|
var import_typescript = __toESM(require("typescript"));
|
|
44
|
+
var MAX_DEPTH = 50;
|
|
44
45
|
function createSchemaContext(checker) {
|
|
45
46
|
return {
|
|
46
47
|
checker,
|
|
47
48
|
schemas: /* @__PURE__ */ new Map(),
|
|
48
|
-
visitedTypes: /* @__PURE__ */ new Set()
|
|
49
|
+
visitedTypes: /* @__PURE__ */ new Set(),
|
|
50
|
+
depth: 0
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
function typeToSchema(type, ctx) {
|
|
54
|
+
if (ctx.depth > MAX_DEPTH) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
52
57
|
const { checker } = ctx;
|
|
53
58
|
if (type.flags & import_typescript.default.TypeFlags.String) {
|
|
54
59
|
return { type: "string" };
|
|
@@ -87,7 +92,7 @@ function typeToSchema(type, ctx) {
|
|
|
87
92
|
);
|
|
88
93
|
const hasNull = type.types.some((t) => t.flags & import_typescript.default.TypeFlags.Null);
|
|
89
94
|
if (nonNullTypes.length === 1 && hasNull) {
|
|
90
|
-
const schema = typeToSchema(nonNullTypes[0], ctx);
|
|
95
|
+
const schema = typeToSchema(nonNullTypes[0], { ...ctx, depth: ctx.depth + 1 });
|
|
91
96
|
return { ...schema, nullable: true };
|
|
92
97
|
}
|
|
93
98
|
if (nonNullTypes.every((t) => t.isStringLiteral())) {
|
|
@@ -102,19 +107,20 @@ function typeToSchema(type, ctx) {
|
|
|
102
107
|
enum: nonNullTypes.map((t) => t.value)
|
|
103
108
|
};
|
|
104
109
|
}
|
|
110
|
+
const nextCtx = { ...ctx, depth: ctx.depth + 1 };
|
|
105
111
|
return {
|
|
106
|
-
oneOf: nonNullTypes.map((t) => typeToSchema(t,
|
|
112
|
+
oneOf: nonNullTypes.map((t) => typeToSchema(t, nextCtx))
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
115
|
if (type.isIntersection()) {
|
|
110
|
-
return intersectionTypeToSchema(type, ctx);
|
|
116
|
+
return intersectionTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
111
117
|
}
|
|
112
118
|
if (checker.isArrayType(type)) {
|
|
113
119
|
const typeArgs = type.typeArguments;
|
|
114
120
|
if (typeArgs?.[0]) {
|
|
115
121
|
return {
|
|
116
122
|
type: "array",
|
|
117
|
-
items: typeToSchema(typeArgs[0], ctx)
|
|
123
|
+
items: typeToSchema(typeArgs[0], { ...ctx, depth: ctx.depth + 1 })
|
|
118
124
|
};
|
|
119
125
|
}
|
|
120
126
|
return { type: "array" };
|
|
@@ -126,18 +132,18 @@ function typeToSchema(type, ctx) {
|
|
|
126
132
|
return { type: "string", format: "date-time" };
|
|
127
133
|
}
|
|
128
134
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
129
|
-
if (ctx.visitedTypes.has(
|
|
135
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
130
136
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
131
137
|
}
|
|
132
138
|
if (!ctx.schemas.has(typeName)) {
|
|
133
|
-
ctx.visitedTypes.add(
|
|
134
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
139
|
+
ctx.visitedTypes.add(typeName);
|
|
140
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
135
141
|
ctx.schemas.set(typeName, schema);
|
|
136
|
-
ctx.visitedTypes.delete(
|
|
142
|
+
ctx.visitedTypes.delete(typeName);
|
|
137
143
|
}
|
|
138
144
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
139
145
|
}
|
|
140
|
-
return objectTypeToSchema(type, ctx);
|
|
146
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
141
147
|
}
|
|
142
148
|
return {};
|
|
143
149
|
}
|
|
@@ -150,7 +156,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
150
156
|
const propName = prop.getName();
|
|
151
157
|
const propType = checker.getTypeOfSymbol(prop);
|
|
152
158
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
153
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
159
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
154
160
|
if (!isOptional) {
|
|
155
161
|
required.push(propName);
|
|
156
162
|
}
|
|
@@ -175,7 +181,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
175
181
|
if (propName.startsWith("__")) continue;
|
|
176
182
|
const propType = checker.getTypeOfSymbol(prop);
|
|
177
183
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
178
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
184
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
179
185
|
if (!isOptional && !required.includes(propName)) {
|
|
180
186
|
required.push(propName);
|
|
181
187
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -4,14 +4,19 @@ import path from "path";
|
|
|
4
4
|
|
|
5
5
|
// src/type-to-schema.ts
|
|
6
6
|
import ts from "typescript";
|
|
7
|
+
var MAX_DEPTH = 50;
|
|
7
8
|
function createSchemaContext(checker) {
|
|
8
9
|
return {
|
|
9
10
|
checker,
|
|
10
11
|
schemas: /* @__PURE__ */ new Map(),
|
|
11
|
-
visitedTypes: /* @__PURE__ */ new Set()
|
|
12
|
+
visitedTypes: /* @__PURE__ */ new Set(),
|
|
13
|
+
depth: 0
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
function typeToSchema(type, ctx) {
|
|
17
|
+
if (ctx.depth > MAX_DEPTH) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
15
20
|
const { checker } = ctx;
|
|
16
21
|
if (type.flags & ts.TypeFlags.String) {
|
|
17
22
|
return { type: "string" };
|
|
@@ -50,7 +55,7 @@ function typeToSchema(type, ctx) {
|
|
|
50
55
|
);
|
|
51
56
|
const hasNull = type.types.some((t) => t.flags & ts.TypeFlags.Null);
|
|
52
57
|
if (nonNullTypes.length === 1 && hasNull) {
|
|
53
|
-
const schema = typeToSchema(nonNullTypes[0], ctx);
|
|
58
|
+
const schema = typeToSchema(nonNullTypes[0], { ...ctx, depth: ctx.depth + 1 });
|
|
54
59
|
return { ...schema, nullable: true };
|
|
55
60
|
}
|
|
56
61
|
if (nonNullTypes.every((t) => t.isStringLiteral())) {
|
|
@@ -65,19 +70,20 @@ function typeToSchema(type, ctx) {
|
|
|
65
70
|
enum: nonNullTypes.map((t) => t.value)
|
|
66
71
|
};
|
|
67
72
|
}
|
|
73
|
+
const nextCtx = { ...ctx, depth: ctx.depth + 1 };
|
|
68
74
|
return {
|
|
69
|
-
oneOf: nonNullTypes.map((t) => typeToSchema(t,
|
|
75
|
+
oneOf: nonNullTypes.map((t) => typeToSchema(t, nextCtx))
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
78
|
if (type.isIntersection()) {
|
|
73
|
-
return intersectionTypeToSchema(type, ctx);
|
|
79
|
+
return intersectionTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
74
80
|
}
|
|
75
81
|
if (checker.isArrayType(type)) {
|
|
76
82
|
const typeArgs = type.typeArguments;
|
|
77
83
|
if (typeArgs?.[0]) {
|
|
78
84
|
return {
|
|
79
85
|
type: "array",
|
|
80
|
-
items: typeToSchema(typeArgs[0], ctx)
|
|
86
|
+
items: typeToSchema(typeArgs[0], { ...ctx, depth: ctx.depth + 1 })
|
|
81
87
|
};
|
|
82
88
|
}
|
|
83
89
|
return { type: "array" };
|
|
@@ -89,18 +95,18 @@ function typeToSchema(type, ctx) {
|
|
|
89
95
|
return { type: "string", format: "date-time" };
|
|
90
96
|
}
|
|
91
97
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
92
|
-
if (ctx.visitedTypes.has(
|
|
98
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
93
99
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
94
100
|
}
|
|
95
101
|
if (!ctx.schemas.has(typeName)) {
|
|
96
|
-
ctx.visitedTypes.add(
|
|
97
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
102
|
+
ctx.visitedTypes.add(typeName);
|
|
103
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
98
104
|
ctx.schemas.set(typeName, schema);
|
|
99
|
-
ctx.visitedTypes.delete(
|
|
105
|
+
ctx.visitedTypes.delete(typeName);
|
|
100
106
|
}
|
|
101
107
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
102
108
|
}
|
|
103
|
-
return objectTypeToSchema(type, ctx);
|
|
109
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
104
110
|
}
|
|
105
111
|
return {};
|
|
106
112
|
}
|
|
@@ -113,7 +119,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
113
119
|
const propName = prop.getName();
|
|
114
120
|
const propType = checker.getTypeOfSymbol(prop);
|
|
115
121
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
116
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
122
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
117
123
|
if (!isOptional) {
|
|
118
124
|
required.push(propName);
|
|
119
125
|
}
|
|
@@ -138,7 +144,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
138
144
|
if (propName.startsWith("__")) continue;
|
|
139
145
|
const propType = checker.getTypeOfSymbol(prop);
|
|
140
146
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
141
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
147
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
142
148
|
if (!isOptional && !required.includes(propName)) {
|
|
143
149
|
required.push(propName);
|
|
144
150
|
}
|