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 CHANGED
@@ -135,13 +135,11 @@ This generates:
135
135
  }
136
136
  ```
137
137
 
138
- ## Endpoint Type
138
+ ## Endpoint Types
139
139
 
140
- The `Endpoint` type accepts three generic parameters:
140
+ ### `Endpoint<TData, TBody?, TError?>`
141
141
 
142
- ```typescript
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(type)) {
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(type);
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(type);
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(type)) {
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(type);
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(type);
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(type)) {
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(type);
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(type);
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(type)) {
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(type);
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(type);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enlace-openapi",
3
- "version": "0.0.1-beta.3",
3
+ "version": "0.0.1-beta.4",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "enlace-openapi": "./dist/cli.mjs"