enlace-openapi 0.0.1-beta.3 → 0.0.1-beta.4
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 +13 -8
- package/dist/cli.mjs +13 -8
- package/dist/index.js +13 -8
- package/dist/index.mjs +13 -8
- 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" };
|
|
@@ -118,18 +123,18 @@ function typeToSchema(type, ctx) {
|
|
|
118
123
|
return { type: "string", format: "date-time" };
|
|
119
124
|
}
|
|
120
125
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
121
|
-
if (ctx.visitedTypes.has(
|
|
126
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
122
127
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
123
128
|
}
|
|
124
129
|
if (!ctx.schemas.has(typeName)) {
|
|
125
|
-
ctx.visitedTypes.add(
|
|
126
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
130
|
+
ctx.visitedTypes.add(typeName);
|
|
131
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
127
132
|
ctx.schemas.set(typeName, schema);
|
|
128
|
-
ctx.visitedTypes.delete(
|
|
133
|
+
ctx.visitedTypes.delete(typeName);
|
|
129
134
|
}
|
|
130
135
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
131
136
|
}
|
|
132
|
-
return objectTypeToSchema(type, ctx);
|
|
137
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
133
138
|
}
|
|
134
139
|
return {};
|
|
135
140
|
}
|
|
@@ -142,7 +147,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
142
147
|
const propName = prop.getName();
|
|
143
148
|
const propType = checker.getTypeOfSymbol(prop);
|
|
144
149
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
145
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
150
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
146
151
|
if (!isOptional) {
|
|
147
152
|
required.push(propName);
|
|
148
153
|
}
|
|
@@ -167,7 +172,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
167
172
|
if (propName.startsWith("__")) continue;
|
|
168
173
|
const propType = checker.getTypeOfSymbol(prop);
|
|
169
174
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
170
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
175
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
171
176
|
if (!isOptional && !required.includes(propName)) {
|
|
172
177
|
required.push(propName);
|
|
173
178
|
}
|
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" };
|
|
@@ -95,18 +100,18 @@ function typeToSchema(type, ctx) {
|
|
|
95
100
|
return { type: "string", format: "date-time" };
|
|
96
101
|
}
|
|
97
102
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
98
|
-
if (ctx.visitedTypes.has(
|
|
103
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
99
104
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
100
105
|
}
|
|
101
106
|
if (!ctx.schemas.has(typeName)) {
|
|
102
|
-
ctx.visitedTypes.add(
|
|
103
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
107
|
+
ctx.visitedTypes.add(typeName);
|
|
108
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
104
109
|
ctx.schemas.set(typeName, schema);
|
|
105
|
-
ctx.visitedTypes.delete(
|
|
110
|
+
ctx.visitedTypes.delete(typeName);
|
|
106
111
|
}
|
|
107
112
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
108
113
|
}
|
|
109
|
-
return objectTypeToSchema(type, ctx);
|
|
114
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
110
115
|
}
|
|
111
116
|
return {};
|
|
112
117
|
}
|
|
@@ -119,7 +124,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
119
124
|
const propName = prop.getName();
|
|
120
125
|
const propType = checker.getTypeOfSymbol(prop);
|
|
121
126
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
122
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
127
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
123
128
|
if (!isOptional) {
|
|
124
129
|
required.push(propName);
|
|
125
130
|
}
|
|
@@ -144,7 +149,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
144
149
|
if (propName.startsWith("__")) continue;
|
|
145
150
|
const propType = checker.getTypeOfSymbol(prop);
|
|
146
151
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
147
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
152
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
148
153
|
if (!isOptional && !required.includes(propName)) {
|
|
149
154
|
required.push(propName);
|
|
150
155
|
}
|
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" };
|
|
@@ -126,18 +131,18 @@ function typeToSchema(type, ctx) {
|
|
|
126
131
|
return { type: "string", format: "date-time" };
|
|
127
132
|
}
|
|
128
133
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
129
|
-
if (ctx.visitedTypes.has(
|
|
134
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
130
135
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
131
136
|
}
|
|
132
137
|
if (!ctx.schemas.has(typeName)) {
|
|
133
|
-
ctx.visitedTypes.add(
|
|
134
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
138
|
+
ctx.visitedTypes.add(typeName);
|
|
139
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
135
140
|
ctx.schemas.set(typeName, schema);
|
|
136
|
-
ctx.visitedTypes.delete(
|
|
141
|
+
ctx.visitedTypes.delete(typeName);
|
|
137
142
|
}
|
|
138
143
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
139
144
|
}
|
|
140
|
-
return objectTypeToSchema(type, ctx);
|
|
145
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
141
146
|
}
|
|
142
147
|
return {};
|
|
143
148
|
}
|
|
@@ -150,7 +155,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
150
155
|
const propName = prop.getName();
|
|
151
156
|
const propType = checker.getTypeOfSymbol(prop);
|
|
152
157
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
153
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
158
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
154
159
|
if (!isOptional) {
|
|
155
160
|
required.push(propName);
|
|
156
161
|
}
|
|
@@ -175,7 +180,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
175
180
|
if (propName.startsWith("__")) continue;
|
|
176
181
|
const propType = checker.getTypeOfSymbol(prop);
|
|
177
182
|
const isOptional = prop.flags & import_typescript.default.SymbolFlags.Optional;
|
|
178
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
183
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
179
184
|
if (!isOptional && !required.includes(propName)) {
|
|
180
185
|
required.push(propName);
|
|
181
186
|
}
|
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" };
|
|
@@ -89,18 +94,18 @@ function typeToSchema(type, ctx) {
|
|
|
89
94
|
return { type: "string", format: "date-time" };
|
|
90
95
|
}
|
|
91
96
|
if (typeName && typeName !== "__type" && typeName !== "Array" && !typeName.startsWith("__")) {
|
|
92
|
-
if (ctx.visitedTypes.has(
|
|
97
|
+
if (ctx.visitedTypes.has(typeName)) {
|
|
93
98
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
94
99
|
}
|
|
95
100
|
if (!ctx.schemas.has(typeName)) {
|
|
96
|
-
ctx.visitedTypes.add(
|
|
97
|
-
const schema = objectTypeToSchema(type, ctx);
|
|
101
|
+
ctx.visitedTypes.add(typeName);
|
|
102
|
+
const schema = objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
98
103
|
ctx.schemas.set(typeName, schema);
|
|
99
|
-
ctx.visitedTypes.delete(
|
|
104
|
+
ctx.visitedTypes.delete(typeName);
|
|
100
105
|
}
|
|
101
106
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
102
107
|
}
|
|
103
|
-
return objectTypeToSchema(type, ctx);
|
|
108
|
+
return objectTypeToSchema(type, { ...ctx, depth: ctx.depth + 1 });
|
|
104
109
|
}
|
|
105
110
|
return {};
|
|
106
111
|
}
|
|
@@ -113,7 +118,7 @@ function objectTypeToSchema(type, ctx) {
|
|
|
113
118
|
const propName = prop.getName();
|
|
114
119
|
const propType = checker.getTypeOfSymbol(prop);
|
|
115
120
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
116
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
121
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
117
122
|
if (!isOptional) {
|
|
118
123
|
required.push(propName);
|
|
119
124
|
}
|
|
@@ -138,7 +143,7 @@ function intersectionTypeToSchema(type, ctx) {
|
|
|
138
143
|
if (propName.startsWith("__")) continue;
|
|
139
144
|
const propType = checker.getTypeOfSymbol(prop);
|
|
140
145
|
const isOptional = prop.flags & ts.SymbolFlags.Optional;
|
|
141
|
-
properties[propName] = typeToSchema(propType, ctx);
|
|
146
|
+
properties[propName] = typeToSchema(propType, { ...ctx, depth: ctx.depth + 1 });
|
|
142
147
|
if (!isOptional && !required.includes(propName)) {
|
|
143
148
|
required.push(propName);
|
|
144
149
|
}
|