tspace-spear 1.2.3 → 1.2.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 +268 -19
- package/dist/lib/core/const/index.d.ts +153 -0
- package/dist/lib/core/const/index.js +105 -0
- package/dist/lib/core/const/index.js.map +1 -0
- package/dist/lib/core/decorators/context.d.ts +16 -9
- package/dist/lib/core/decorators/context.js +85 -59
- package/dist/lib/core/decorators/context.js.map +1 -1
- package/dist/lib/core/decorators/headers.d.ts +2 -2
- package/dist/lib/core/decorators/headers.js +1 -1
- package/dist/lib/core/decorators/headers.js.map +1 -1
- package/dist/lib/core/decorators/methods.d.ts +7 -7
- package/dist/lib/core/decorators/methods.js.map +1 -1
- package/dist/lib/core/decorators/middleware.d.ts +3 -3
- package/dist/lib/core/decorators/middleware.js +2 -2
- package/dist/lib/core/decorators/middleware.js.map +1 -1
- package/dist/lib/core/decorators/statusCode.d.ts +1 -1
- package/dist/lib/core/decorators/statusCode.js.map +1 -1
- package/dist/lib/core/decorators/swagger.d.ts +1 -1
- package/dist/lib/core/decorators/swagger.js.map +1 -1
- package/dist/lib/core/server/fast-router.d.ts +133 -0
- package/dist/lib/core/server/fast-router.js +277 -0
- package/dist/lib/core/server/fast-router.js.map +1 -0
- package/dist/lib/core/server/index.d.ts +34 -37
- package/dist/lib/core/server/index.js +192 -501
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/net/index.d.ts +20 -0
- package/dist/lib/core/server/net/index.js +393 -0
- package/dist/lib/core/server/net/index.js.map +1 -0
- package/dist/lib/core/server/parser-factory.d.ts +10 -11
- package/dist/lib/core/server/parser-factory.js +259 -437
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/server/response.d.ts +6 -0
- package/dist/lib/core/server/response.js +168 -0
- package/dist/lib/core/server/response.js.map +1 -0
- package/dist/lib/core/server/router.d.ts +2 -12
- package/dist/lib/core/server/router.js +2 -13
- package/dist/lib/core/server/router.js.map +1 -1
- package/dist/lib/core/server/uWS/index.d.ts +30 -0
- package/dist/lib/core/server/uWS/index.js +357 -0
- package/dist/lib/core/server/uWS/index.js.map +1 -0
- package/dist/lib/core/types/index.d.ts +144 -43
- package/dist/lib/core/utils/index.d.ts +12 -0
- package/dist/lib/core/utils/index.js +137 -0
- package/dist/lib/core/utils/index.js.map +1 -0
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +3 -2
- package/dist/lib/index.js.map +1 -1
- package/package.json +19 -14
|
@@ -4,55 +4,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ParserFactory = void 0;
|
|
7
|
+
const busboy_1 = __importDefault(require("busboy"));
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const mime_types_1 = __importDefault(require("mime-types"));
|
|
10
11
|
const crypto_1 = __importDefault(require("crypto"));
|
|
11
12
|
const swagger_ui_dist_1 = __importDefault(require("swagger-ui-dist"));
|
|
12
|
-
const querystring_1 = __importDefault(require("querystring"));
|
|
13
13
|
const string_decoder_1 = require("string_decoder");
|
|
14
|
-
const
|
|
14
|
+
const fast_querystring_1 = __importDefault(require("fast-querystring"));
|
|
15
|
+
const utils_1 = require("../utils");
|
|
16
|
+
const uWS_1 = require("./uWS");
|
|
17
|
+
const net_1 = require("./net");
|
|
15
18
|
class ParserFactory {
|
|
16
|
-
|
|
19
|
+
uWebStockets = false;
|
|
20
|
+
net = false;
|
|
17
21
|
useAdater(adapter) {
|
|
18
|
-
if ('
|
|
19
|
-
this.
|
|
22
|
+
if (adapter.kind === 'uWS') {
|
|
23
|
+
this.uWebStockets = true;
|
|
24
|
+
}
|
|
25
|
+
if (adapter.kind === 'net') {
|
|
26
|
+
this.net = true;
|
|
20
27
|
}
|
|
28
|
+
return this;
|
|
21
29
|
}
|
|
22
30
|
queryString(url) {
|
|
23
|
-
const
|
|
24
|
-
let i = url.indexOf('?');
|
|
31
|
+
const i = url.indexOf("?");
|
|
25
32
|
if (i === -1)
|
|
26
|
-
return
|
|
27
|
-
i
|
|
28
|
-
let key = '';
|
|
29
|
-
let value = '';
|
|
30
|
-
let mode = 0;
|
|
31
|
-
for (; i < url.length; i++) {
|
|
32
|
-
const c = url[i];
|
|
33
|
-
if (c === '#')
|
|
34
|
-
break;
|
|
35
|
-
if (c === '=') {
|
|
36
|
-
mode = 1;
|
|
37
|
-
}
|
|
38
|
-
else if (c === '&') {
|
|
39
|
-
if (key)
|
|
40
|
-
query[key] = value;
|
|
41
|
-
key = '';
|
|
42
|
-
value = '';
|
|
43
|
-
mode = 0;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
mode ? (value += c) : (key += c);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (key)
|
|
50
|
-
query[key] = value;
|
|
51
|
-
return query;
|
|
33
|
+
return {};
|
|
34
|
+
return fast_querystring_1.default.parse(url.slice(i + 1));
|
|
52
35
|
}
|
|
53
|
-
async files({ req, res, options }) {
|
|
54
|
-
if (this.
|
|
55
|
-
return
|
|
36
|
+
async files({ req, res, options, }) {
|
|
37
|
+
if (this.uWebStockets) {
|
|
38
|
+
return (0, uWS_1.uWSfiles)({ req, res, options });
|
|
39
|
+
}
|
|
40
|
+
if (this.net) {
|
|
41
|
+
return (0, net_1.netFiles)(req, options);
|
|
56
42
|
}
|
|
57
43
|
const temp = options.tempFileDir;
|
|
58
44
|
if (!fs_1.default.existsSync(temp)) {
|
|
@@ -65,7 +51,7 @@ class ParserFactory {
|
|
|
65
51
|
const body = {};
|
|
66
52
|
const files = {};
|
|
67
53
|
const fileWritePromises = [];
|
|
68
|
-
const bb = (0, busboy_1.default)({ headers: req.headers, defParamCharset:
|
|
54
|
+
const bb = (0, busboy_1.default)({ headers: req.headers, defParamCharset: "utf8" });
|
|
69
55
|
const removeTemp = (fileTemp, ms) => {
|
|
70
56
|
const remove = () => {
|
|
71
57
|
try {
|
|
@@ -75,16 +61,16 @@ class ParserFactory {
|
|
|
75
61
|
};
|
|
76
62
|
setTimeout(remove, ms);
|
|
77
63
|
};
|
|
78
|
-
bb.on(
|
|
64
|
+
bb.on("file", (fieldName, fileData, info) => {
|
|
79
65
|
const { filename, mimeType } = info;
|
|
80
|
-
const extension = mime_types_1.default.extension(mimeType)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const tempFilename = crypto_1.default.randomBytes(16).toString(
|
|
66
|
+
const extension = mime_types_1.default.extension(mimeType) ||
|
|
67
|
+
path_1.default.extname(filename).replace(".", "") ||
|
|
68
|
+
"bin";
|
|
69
|
+
const tempFilename = crypto_1.default.randomBytes(16).toString("hex");
|
|
84
70
|
const filePath = path_1.default.join(path_1.default.resolve(), `${temp}/${tempFilename}`);
|
|
85
71
|
const writeStream = fs_1.default.createWriteStream(filePath);
|
|
86
72
|
let fileSize = 0;
|
|
87
|
-
fileData.on(
|
|
73
|
+
fileData.on("data", (data) => {
|
|
88
74
|
fileSize += data.length;
|
|
89
75
|
if (fileSize > options.limit) {
|
|
90
76
|
fileData.unpipe(writeStream);
|
|
@@ -94,7 +80,7 @@ class ParserFactory {
|
|
|
94
80
|
});
|
|
95
81
|
const fileWritePromise = new Promise((resolve, reject) => {
|
|
96
82
|
fileData.pipe(writeStream);
|
|
97
|
-
writeStream.on(
|
|
83
|
+
writeStream.on("finish", () => {
|
|
98
84
|
const file = {
|
|
99
85
|
name: filename,
|
|
100
86
|
tempFilePath: filePath,
|
|
@@ -106,26 +92,27 @@ class ParserFactory {
|
|
|
106
92
|
bytes: fileSize,
|
|
107
93
|
kb: fileSize / 1024,
|
|
108
94
|
mb: fileSize / 1024 / 1024,
|
|
109
|
-
gb: fileSize / 1024 / 1024 / 1024
|
|
95
|
+
gb: fileSize / 1024 / 1024 / 1024,
|
|
110
96
|
},
|
|
111
97
|
write: (to) => {
|
|
112
98
|
return new Promise((resolve, reject) => {
|
|
113
|
-
fs_1.default
|
|
99
|
+
fs_1.default
|
|
100
|
+
.createReadStream(filePath)
|
|
114
101
|
.pipe(fs_1.default.createWriteStream(to))
|
|
115
|
-
.on(
|
|
102
|
+
.on("finish", () => {
|
|
116
103
|
return resolve(null);
|
|
117
104
|
})
|
|
118
|
-
.on(
|
|
105
|
+
.on("error", (err) => {
|
|
119
106
|
return reject(err);
|
|
120
107
|
});
|
|
121
108
|
});
|
|
122
109
|
},
|
|
123
110
|
remove: () => {
|
|
124
|
-
return new Promise(resolve => setTimeout(() => {
|
|
111
|
+
return new Promise((resolve) => setTimeout(() => {
|
|
125
112
|
fs_1.default.unlinkSync(filePath);
|
|
126
113
|
return resolve(null);
|
|
127
114
|
}, 100));
|
|
128
|
-
}
|
|
115
|
+
},
|
|
129
116
|
};
|
|
130
117
|
if (files[fieldName] == null) {
|
|
131
118
|
files[fieldName] = [];
|
|
@@ -136,55 +123,56 @@ class ParserFactory {
|
|
|
136
123
|
}
|
|
137
124
|
return resolve(null);
|
|
138
125
|
});
|
|
139
|
-
writeStream.on(
|
|
126
|
+
writeStream.on("error", reject);
|
|
140
127
|
});
|
|
141
128
|
fileWritePromises.push(fileWritePromise);
|
|
142
129
|
});
|
|
143
|
-
bb.on(
|
|
130
|
+
bb.on("field", (name, value) => {
|
|
144
131
|
body[name] = value;
|
|
145
132
|
});
|
|
146
|
-
bb.on(
|
|
133
|
+
bb.on("finish", () => {
|
|
147
134
|
Promise.all(fileWritePromises)
|
|
148
135
|
.then(() => {
|
|
149
136
|
return resolve({
|
|
150
137
|
files,
|
|
151
|
-
body
|
|
138
|
+
body,
|
|
152
139
|
});
|
|
153
140
|
})
|
|
154
141
|
.catch((err) => {
|
|
155
142
|
return reject(err);
|
|
156
143
|
});
|
|
157
144
|
});
|
|
158
|
-
bb.on(
|
|
145
|
+
bb.on("error", (err) => {
|
|
159
146
|
return reject(err);
|
|
160
147
|
});
|
|
161
148
|
req.pipe(bb);
|
|
162
149
|
});
|
|
163
150
|
}
|
|
164
151
|
async body(req, res) {
|
|
165
|
-
if (this.
|
|
166
|
-
return await
|
|
152
|
+
if (this.uWebStockets) {
|
|
153
|
+
return (await (0, uWS_1.uWSBody)(req, res));
|
|
154
|
+
}
|
|
155
|
+
if (this.net) {
|
|
156
|
+
return (await (0, net_1.netBody)(req));
|
|
167
157
|
}
|
|
168
158
|
return new Promise((resolve, reject) => {
|
|
169
|
-
const decoder = new string_decoder_1.StringDecoder(
|
|
170
|
-
let payload =
|
|
171
|
-
req.on(
|
|
159
|
+
const decoder = new string_decoder_1.StringDecoder("utf-8");
|
|
160
|
+
let payload = "";
|
|
161
|
+
req.on("data", (data) => {
|
|
172
162
|
payload += decoder.write(data);
|
|
173
163
|
});
|
|
174
|
-
req.on(
|
|
164
|
+
req.on("end", async () => {
|
|
165
|
+
payload += decoder.end();
|
|
166
|
+
const contentType = req.headers["content-type"]?.toLowerCase() || null;
|
|
175
167
|
try {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
return resolve(querystring_1.default.parse(payload));
|
|
179
|
-
}
|
|
180
|
-
payload += decoder.end();
|
|
181
|
-
return resolve(JSON.parse(payload));
|
|
168
|
+
const body = await (0, utils_1.normalizeRequestBody)({ contentType, payload });
|
|
169
|
+
return resolve(body);
|
|
182
170
|
}
|
|
183
|
-
catch (
|
|
184
|
-
return
|
|
171
|
+
catch (err) {
|
|
172
|
+
return reject(err);
|
|
185
173
|
}
|
|
186
174
|
});
|
|
187
|
-
req.on(
|
|
175
|
+
req.on("error", (err) => {
|
|
188
176
|
return reject(err);
|
|
189
177
|
});
|
|
190
178
|
});
|
|
@@ -194,15 +182,15 @@ class ParserFactory {
|
|
|
194
182
|
const cookieString = req.headers?.cookie;
|
|
195
183
|
if (cookieString == null)
|
|
196
184
|
return null;
|
|
197
|
-
for (const cookie of cookieString.split(
|
|
198
|
-
const [name, value] = cookie.split(
|
|
185
|
+
for (const cookie of cookieString.split(";")) {
|
|
186
|
+
const [name, value] = cookie.split("=").map((v) => v.trim());
|
|
199
187
|
cookies[name] = decodeURIComponent(value);
|
|
200
188
|
}
|
|
201
189
|
for (const name of Object.keys(cookies)) {
|
|
202
190
|
const cookie = cookies[name];
|
|
203
|
-
if (!cookie.startsWith(
|
|
191
|
+
if (!cookie.startsWith("Expires="))
|
|
204
192
|
continue;
|
|
205
|
-
const expiresString = cookie.replace(
|
|
193
|
+
const expiresString = cookie.replace("Expires=", "");
|
|
206
194
|
const expiresDate = new Date(expiresString);
|
|
207
195
|
if (isNaN(expiresDate.getTime()) || expiresDate < new Date()) {
|
|
208
196
|
delete cookies[name];
|
|
@@ -214,9 +202,9 @@ class ParserFactory {
|
|
|
214
202
|
const spec = {
|
|
215
203
|
openapi: "3.1.0",
|
|
216
204
|
info: doc.info ?? {
|
|
217
|
-
title:
|
|
205
|
+
title: "API Documentation",
|
|
218
206
|
description: "Documentation",
|
|
219
|
-
version:
|
|
207
|
+
version: "1.0.0",
|
|
220
208
|
},
|
|
221
209
|
components: {
|
|
222
210
|
securitySchemes: {
|
|
@@ -224,14 +212,14 @@ class ParserFactory {
|
|
|
224
212
|
type: "http",
|
|
225
213
|
scheme: "bearer",
|
|
226
214
|
name: "Authorization",
|
|
227
|
-
description: "Enter your token in the format : 'Bearer {TOKEN}'"
|
|
215
|
+
description: "Enter your token in the format : 'Bearer {TOKEN}'",
|
|
228
216
|
},
|
|
229
217
|
cookies: {
|
|
230
218
|
type: "apiKey",
|
|
231
219
|
in: "header",
|
|
232
220
|
name: "Cookie",
|
|
233
|
-
description: "Enter your cookies in the headers"
|
|
234
|
-
}
|
|
221
|
+
description: "Enter your cookies in the headers",
|
|
222
|
+
},
|
|
235
223
|
},
|
|
236
224
|
},
|
|
237
225
|
servers: doc.servers,
|
|
@@ -246,25 +234,24 @@ class ParserFactory {
|
|
|
246
234
|
content: {
|
|
247
235
|
"application/json": {
|
|
248
236
|
schema: {
|
|
249
|
-
type:
|
|
237
|
+
type: "object",
|
|
250
238
|
properties: {
|
|
251
239
|
message: {
|
|
252
|
-
example: "success"
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
240
|
+
example: "success",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
259
247
|
};
|
|
260
248
|
for (const r of routes) {
|
|
261
|
-
if (r.path ===
|
|
249
|
+
if (r.path === "*")
|
|
262
250
|
continue;
|
|
263
251
|
const path = r.path.replace(/:(\w+)/g, "{$1}");
|
|
264
|
-
const method = r.method.
|
|
265
|
-
const swagger = (doc.specs ?? []).find(s => {
|
|
266
|
-
return s.path === r.path &&
|
|
267
|
-
s.method.toLocaleLowerCase() === method;
|
|
252
|
+
const method = r.method.toLowerCase();
|
|
253
|
+
const swagger = (doc.specs ?? []).find((s) => {
|
|
254
|
+
return s.path === r.path && s.method.toLowerCase() === method;
|
|
268
255
|
});
|
|
269
256
|
const decoratedOnly = doc.options?.decoratedOnly ?? false;
|
|
270
257
|
if ((swagger == null && decoratedOnly) || swagger?.disabled) {
|
|
@@ -272,15 +259,15 @@ class ParserFactory {
|
|
|
272
259
|
}
|
|
273
260
|
if (paths[path] == null) {
|
|
274
261
|
paths[path] = {
|
|
275
|
-
[method]: {}
|
|
262
|
+
[method]: {},
|
|
276
263
|
};
|
|
277
264
|
}
|
|
278
265
|
const spec = {};
|
|
279
266
|
const tags = /\/api\/v\d+/.test(r.path)
|
|
280
|
-
? r.path.split(
|
|
267
|
+
? r.path.split("/")[3]
|
|
281
268
|
: /\/api/.test(r.path)
|
|
282
|
-
? r.path.split(
|
|
283
|
-
: r.path.split(
|
|
269
|
+
? r.path.split("/")[2]
|
|
270
|
+
: r.path.split("/")[1];
|
|
284
271
|
spec.parameters = [];
|
|
285
272
|
spec.responses = {};
|
|
286
273
|
spec.tags = [];
|
|
@@ -294,135 +281,42 @@ class ParserFactory {
|
|
|
294
281
|
content: {
|
|
295
282
|
"application/json": {
|
|
296
283
|
schema: {
|
|
297
|
-
type:
|
|
284
|
+
type: "object",
|
|
298
285
|
properties: response.example == null
|
|
299
286
|
? {}
|
|
300
|
-
: Object.keys(response.example)
|
|
301
|
-
|
|
302
|
-
|
|
287
|
+
: Object.keys(response.example).reduce((prev, key) => {
|
|
288
|
+
prev[key] = {
|
|
289
|
+
example: (response?.example ?? {})[key] ?? {},
|
|
290
|
+
};
|
|
303
291
|
return prev;
|
|
304
|
-
}, {})
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
292
|
+
}, {}),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
308
296
|
};
|
|
309
297
|
}
|
|
310
298
|
spec.responses = {
|
|
311
|
-
...responses
|
|
299
|
+
...responses,
|
|
312
300
|
};
|
|
313
301
|
}
|
|
314
|
-
if (swagger
|
|
302
|
+
if (swagger == null) {
|
|
315
303
|
spec.tags = [
|
|
316
|
-
|
|
317
|
-
?
|
|
318
|
-
:
|
|
304
|
+
tags == null || tags === "" || /^:[^:]*$/.test(tags)
|
|
305
|
+
? "default"
|
|
306
|
+
: tags,
|
|
319
307
|
];
|
|
320
|
-
if (swagger.bearerToken) {
|
|
321
|
-
spec.security = [{ "BearerToken": [] }];
|
|
322
|
-
}
|
|
323
|
-
if (swagger.summary != null) {
|
|
324
|
-
spec.summary = swagger.summary;
|
|
325
|
-
}
|
|
326
|
-
if (swagger.description != null) {
|
|
327
|
-
spec.description = swagger.description;
|
|
328
|
-
}
|
|
329
308
|
if (Array.isArray(r.params) && Array.from(r.params).length) {
|
|
330
|
-
spec.parameters = Array.from(r
|
|
309
|
+
spec.parameters = Array.from(r.params).map((p) => {
|
|
331
310
|
return {
|
|
332
311
|
name: p,
|
|
333
312
|
in: "path",
|
|
334
313
|
required: true,
|
|
335
314
|
schema: {
|
|
336
|
-
type: "string"
|
|
337
|
-
}
|
|
315
|
+
type: "string",
|
|
316
|
+
},
|
|
338
317
|
};
|
|
339
318
|
});
|
|
340
319
|
}
|
|
341
|
-
if (swagger.query != null) {
|
|
342
|
-
spec.parameters = [
|
|
343
|
-
...spec.parameters,
|
|
344
|
-
...Object.entries(swagger.query).map(([k, v]) => {
|
|
345
|
-
return {
|
|
346
|
-
name: k,
|
|
347
|
-
in: "query",
|
|
348
|
-
required: v.required == null ? false : true,
|
|
349
|
-
schema: {
|
|
350
|
-
type: v.type
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
})
|
|
354
|
-
];
|
|
355
|
-
}
|
|
356
|
-
if (swagger.cookies != null) {
|
|
357
|
-
spec.parameters = [
|
|
358
|
-
...spec.parameters,
|
|
359
|
-
...[{
|
|
360
|
-
name: "Cookie",
|
|
361
|
-
in: "header",
|
|
362
|
-
required: swagger.cookies.required == null ? false : true,
|
|
363
|
-
schema: {
|
|
364
|
-
type: "string"
|
|
365
|
-
},
|
|
366
|
-
example: swagger.cookies.names.map((v, i) => `${v}={value${i + 1}}`).join(' ; '),
|
|
367
|
-
description: swagger.cookies?.description
|
|
368
|
-
}]
|
|
369
|
-
];
|
|
370
|
-
}
|
|
371
|
-
if (swagger.body != null) {
|
|
372
|
-
spec.requestBody = {
|
|
373
|
-
description: swagger.body?.description == null ? "description" : swagger.body.description,
|
|
374
|
-
required: swagger.body?.required == null ? false : true,
|
|
375
|
-
content: {
|
|
376
|
-
"application/json": {
|
|
377
|
-
schema: {
|
|
378
|
-
type: "object",
|
|
379
|
-
properties: swagger.body.properties
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
if (swagger.files != null) {
|
|
386
|
-
spec.requestBody = {
|
|
387
|
-
description: swagger.files?.description == null ? "description" : swagger.files.description,
|
|
388
|
-
required: swagger.files?.required ?? false,
|
|
389
|
-
content: {
|
|
390
|
-
"multipart/form-data": {
|
|
391
|
-
schema: {
|
|
392
|
-
type: "object",
|
|
393
|
-
properties: swagger.files.properties
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (swagger.responses != null) {
|
|
400
|
-
const responses = {};
|
|
401
|
-
for (const response of swagger.responses) {
|
|
402
|
-
if (response == null || !Object.keys(response).length)
|
|
403
|
-
continue;
|
|
404
|
-
responses[`${response.status}`] = {
|
|
405
|
-
description: response.description,
|
|
406
|
-
content: {
|
|
407
|
-
"application/json": {
|
|
408
|
-
schema: {
|
|
409
|
-
type: 'object',
|
|
410
|
-
properties: response.example == null
|
|
411
|
-
? {}
|
|
412
|
-
: Object.keys(response.example)
|
|
413
|
-
.reduce((prev, key) => {
|
|
414
|
-
prev[key] = { example: (response?.example ?? {})[key] ?? {} };
|
|
415
|
-
return prev;
|
|
416
|
-
}, {})
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
spec.responses = {
|
|
423
|
-
...responses
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
320
|
if (!Object.keys(spec.responses).length) {
|
|
427
321
|
spec.responses = defaultSpecResponse;
|
|
428
322
|
}
|
|
@@ -430,19 +324,146 @@ class ParserFactory {
|
|
|
430
324
|
continue;
|
|
431
325
|
}
|
|
432
326
|
spec.tags = [
|
|
433
|
-
tags == null
|
|
327
|
+
swagger.tags == null
|
|
328
|
+
? tags == null || tags === "" || /^:[^:]*$/.test(tags)
|
|
329
|
+
? "default"
|
|
330
|
+
: tags
|
|
331
|
+
: swagger.tags,
|
|
434
332
|
];
|
|
435
|
-
if (
|
|
436
|
-
spec.
|
|
333
|
+
if (swagger.bearerToken) {
|
|
334
|
+
spec.security = [{ BearerToken: [] }];
|
|
335
|
+
}
|
|
336
|
+
if (swagger.summary != null) {
|
|
337
|
+
spec.summary = swagger.summary;
|
|
338
|
+
}
|
|
339
|
+
if (swagger.description != null) {
|
|
340
|
+
spec.description = swagger.description;
|
|
341
|
+
}
|
|
342
|
+
if (swagger.params != null) {
|
|
343
|
+
const params = Object.entries(swagger.params).map(([k, v]) => {
|
|
344
|
+
return {
|
|
345
|
+
name: k,
|
|
346
|
+
in: "path",
|
|
347
|
+
required: v?.required === true,
|
|
348
|
+
description: v?.description,
|
|
349
|
+
example: v?.example || v?.enum,
|
|
350
|
+
schema: {
|
|
351
|
+
type: v?.type ?? "string",
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
spec.parameters = [
|
|
356
|
+
...(spec.parameters ?? []),
|
|
357
|
+
...params,
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
else if (Array.isArray(r.params) && Array.from(r.params).length) {
|
|
361
|
+
spec.parameters = Array.from(r?.params).map((p) => {
|
|
437
362
|
return {
|
|
438
363
|
name: p,
|
|
439
364
|
in: "path",
|
|
440
365
|
required: true,
|
|
441
366
|
schema: {
|
|
442
|
-
type: "string"
|
|
367
|
+
type: "string",
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (swagger.query != null) {
|
|
373
|
+
const queryParams = Object.entries(swagger.query).map(([k, v]) => {
|
|
374
|
+
return {
|
|
375
|
+
name: k,
|
|
376
|
+
in: "query",
|
|
377
|
+
required: v?.required === true,
|
|
378
|
+
description: v?.description,
|
|
379
|
+
example: v?.example || v?.enum,
|
|
380
|
+
schema: {
|
|
381
|
+
type: v?.type ?? "string",
|
|
443
382
|
}
|
|
444
383
|
};
|
|
445
384
|
});
|
|
385
|
+
spec.parameters = [
|
|
386
|
+
...(spec.parameters ?? []),
|
|
387
|
+
...queryParams,
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
if (swagger.cookies != null) {
|
|
391
|
+
spec.parameters = [
|
|
392
|
+
...spec.parameters,
|
|
393
|
+
...[
|
|
394
|
+
{
|
|
395
|
+
name: "Cookie",
|
|
396
|
+
in: "header",
|
|
397
|
+
required: swagger.cookies.required === true,
|
|
398
|
+
schema: {
|
|
399
|
+
type: "string",
|
|
400
|
+
},
|
|
401
|
+
example: swagger.cookies.names
|
|
402
|
+
.map((v, i) => `${v}={value${i + 1}}`)
|
|
403
|
+
.join(" ; "),
|
|
404
|
+
description: swagger.cookies?.description,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
if (swagger.body != null) {
|
|
410
|
+
spec.requestBody = {
|
|
411
|
+
description: swagger.body.description,
|
|
412
|
+
required: swagger.body?.required === true,
|
|
413
|
+
content: {
|
|
414
|
+
"application/json": {
|
|
415
|
+
schema: {
|
|
416
|
+
type: "object",
|
|
417
|
+
properties: swagger.body.properties,
|
|
418
|
+
required: Object.entries(swagger.body.properties)
|
|
419
|
+
.filter(([_, v]) => v.required)
|
|
420
|
+
.map(([key]) => key)
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (swagger.files != null) {
|
|
427
|
+
spec.requestBody = {
|
|
428
|
+
description: swagger.files.description,
|
|
429
|
+
required: swagger.files?.required === true,
|
|
430
|
+
content: {
|
|
431
|
+
"multipart/form-data": {
|
|
432
|
+
schema: {
|
|
433
|
+
type: "object",
|
|
434
|
+
properties: swagger.files.properties,
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (swagger.responses != null) {
|
|
441
|
+
const responses = {};
|
|
442
|
+
for (const response of swagger.responses) {
|
|
443
|
+
if (response == null || !Object.keys(response).length)
|
|
444
|
+
continue;
|
|
445
|
+
responses[`${response.status}`] = {
|
|
446
|
+
description: response.description,
|
|
447
|
+
content: {
|
|
448
|
+
"application/json": {
|
|
449
|
+
schema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: response.example == null
|
|
452
|
+
? {}
|
|
453
|
+
: Object.keys(response.example).reduce((prev, key) => {
|
|
454
|
+
prev[key] = {
|
|
455
|
+
example: (response?.example ?? {})[key] ?? {},
|
|
456
|
+
};
|
|
457
|
+
return prev;
|
|
458
|
+
}, {}),
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
spec.responses = {
|
|
465
|
+
...responses,
|
|
466
|
+
};
|
|
446
467
|
}
|
|
447
468
|
if (!Object.keys(spec.responses).length) {
|
|
448
469
|
spec.responses = defaultSpecResponse;
|
|
@@ -453,20 +474,17 @@ class ParserFactory {
|
|
|
453
474
|
};
|
|
454
475
|
spec.paths = specPaths(doc.routes ?? []);
|
|
455
476
|
const normalizePath = (...paths) => {
|
|
456
|
-
const path = paths
|
|
457
|
-
|
|
458
|
-
.replace(/\/+/g, '/')
|
|
459
|
-
.replace(/\/+$/, '');
|
|
460
|
-
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
477
|
+
const path = paths.join("/").replace(/\/+/g, "/").replace(/\/+$/, "");
|
|
478
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
461
479
|
return /\/api\/api/.test(normalizedPath)
|
|
462
480
|
? normalizedPath.replace(/\/api\/api\//, "/api/")
|
|
463
481
|
: normalizedPath;
|
|
464
482
|
};
|
|
465
|
-
const STATIC_URL =
|
|
466
|
-
const iconURL = normalizePath(doc.staticUrl ??
|
|
467
|
-
const cssURL = normalizePath(doc.staticUrl ??
|
|
468
|
-
const scriptBundle = normalizePath(doc.staticUrl ??
|
|
469
|
-
const scriptStandalonePreset = normalizePath(doc.staticUrl ??
|
|
483
|
+
const STATIC_URL = "/swagger-ui";
|
|
484
|
+
const iconURL = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/favicon-32x32.png`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
|
|
485
|
+
const cssURL = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui.css`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
|
|
486
|
+
const scriptBundle = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui-bundle.js`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
|
|
487
|
+
const scriptStandalonePreset = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui-standalone-preset.js`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
|
|
470
488
|
const html = `
|
|
471
489
|
<!DOCTYPE html>
|
|
472
490
|
<html lang="en">
|
|
@@ -494,7 +512,7 @@ class ParserFactory {
|
|
|
494
512
|
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
|
495
513
|
spec : ${JSON.stringify(spec)},
|
|
496
514
|
withCredentials: ${doc.options?.withCredentials ?? "true"},
|
|
497
|
-
layout: "${doc.options?.layout ??
|
|
515
|
+
layout: "${doc.options?.layout ?? "StandaloneLayout"}",
|
|
498
516
|
filter: "${doc.options?.filter ?? "false"}",
|
|
499
517
|
docExpansion: "${doc.options?.docExpansion ?? "list"}",
|
|
500
518
|
deepLinking: "${doc.options?.deepLinking ?? "true"}",
|
|
@@ -506,233 +524,37 @@ class ParserFactory {
|
|
|
506
524
|
</html>
|
|
507
525
|
`;
|
|
508
526
|
const staticSwaggerHandler = (req, res, params) => {
|
|
509
|
-
const swaggerUiPath = swagger_ui_dist_1.default.getAbsoluteFSPath();
|
|
510
|
-
const mimeTypes = {
|
|
511
|
-
'.html': 'text/html',
|
|
512
|
-
'.css': 'text/css',
|
|
513
|
-
'.js': 'application/javascript',
|
|
514
|
-
'.png': 'image/png',
|
|
515
|
-
'.jpg': 'image/jpeg',
|
|
516
|
-
'.gif': 'image/gif',
|
|
517
|
-
'.svg': 'image/svg+xml',
|
|
518
|
-
'.json': 'application/json'
|
|
519
|
-
};
|
|
520
|
-
const requestedFilePath = params['*'];
|
|
521
|
-
const filePath = path_1.default.join(swaggerUiPath, requestedFilePath);
|
|
522
|
-
const extname = path_1.default.extname(filePath);
|
|
523
|
-
const contentType = mimeTypes[extname] || 'text/html';
|
|
524
527
|
try {
|
|
528
|
+
const swaggerUiPath = swagger_ui_dist_1.default.getAbsoluteFSPath();
|
|
529
|
+
const mimeTypes = {
|
|
530
|
+
".html": "text/html",
|
|
531
|
+
".css": "text/css",
|
|
532
|
+
".js": "application/javascript",
|
|
533
|
+
".png": "image/png",
|
|
534
|
+
".jpg": "image/jpeg",
|
|
535
|
+
".gif": "image/gif",
|
|
536
|
+
".svg": "image/svg+xml",
|
|
537
|
+
".json": "application/json",
|
|
538
|
+
};
|
|
539
|
+
const requestedFilePath = params["*"];
|
|
540
|
+
const filePath = path_1.default.join(swaggerUiPath, requestedFilePath);
|
|
541
|
+
const extname = path_1.default.extname(filePath);
|
|
542
|
+
const contentType = mimeTypes[extname] || "text/html";
|
|
525
543
|
const content = fs_1.default.readFileSync(filePath);
|
|
526
|
-
res.writeHead(200, {
|
|
527
|
-
return res.end(content,
|
|
544
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
545
|
+
return res.end(content, "utf-8");
|
|
528
546
|
}
|
|
529
547
|
catch (err) {
|
|
530
|
-
res.writeHead(404, {
|
|
531
|
-
return res.end(
|
|
548
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
549
|
+
return res.end("Not found");
|
|
532
550
|
}
|
|
533
551
|
};
|
|
534
552
|
return {
|
|
535
553
|
path: doc.path,
|
|
536
554
|
staticUrl: `${STATIC_URL}/*`,
|
|
537
555
|
staticSwaggerHandler,
|
|
538
|
-
html
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
async uWSfiles({ req, res, options }) {
|
|
542
|
-
const temp = options.tempFileDir;
|
|
543
|
-
if (!fs_1.default.existsSync(temp)) {
|
|
544
|
-
try {
|
|
545
|
-
fs_1.default.mkdirSync(temp, { recursive: true });
|
|
546
|
-
}
|
|
547
|
-
catch { }
|
|
548
|
-
}
|
|
549
|
-
const removeTemp = (fileTemp, ms) => {
|
|
550
|
-
const remove = () => {
|
|
551
|
-
try {
|
|
552
|
-
fs_1.default.unlinkSync(fileTemp);
|
|
553
|
-
}
|
|
554
|
-
catch (err) { }
|
|
555
|
-
};
|
|
556
|
-
setTimeout(remove, ms);
|
|
556
|
+
html,
|
|
557
557
|
};
|
|
558
|
-
const contentType = req.headers['content-type'] ?? '';
|
|
559
|
-
const boundary = contentType.split('boundary=')[1];
|
|
560
|
-
if (!boundary) {
|
|
561
|
-
throw new Error('Invalid multipart/form-data (no boundary)');
|
|
562
|
-
}
|
|
563
|
-
const boundaryBuf = Buffer.from(`\r\n--${boundary}`);
|
|
564
|
-
return new Promise((resolve, reject) => {
|
|
565
|
-
let body = {};
|
|
566
|
-
let files = {};
|
|
567
|
-
let buffer = Buffer.alloc(0);
|
|
568
|
-
let currentFileStream = null;
|
|
569
|
-
let file = null;
|
|
570
|
-
let headerParsed = false;
|
|
571
|
-
let aborted = false;
|
|
572
|
-
const fail = (err) => {
|
|
573
|
-
if (aborted)
|
|
574
|
-
return;
|
|
575
|
-
aborted = true;
|
|
576
|
-
try {
|
|
577
|
-
currentFileStream?.destroy();
|
|
578
|
-
}
|
|
579
|
-
catch { }
|
|
580
|
-
try {
|
|
581
|
-
file?.tempFilePath && fs_1.default.unlinkSync(file.tempFilePath);
|
|
582
|
-
}
|
|
583
|
-
catch { }
|
|
584
|
-
//@ts-ignore
|
|
585
|
-
try {
|
|
586
|
-
res.uwsRes.close();
|
|
587
|
-
}
|
|
588
|
-
catch { }
|
|
589
|
-
return reject(err);
|
|
590
|
-
};
|
|
591
|
-
//@ts-ignore
|
|
592
|
-
res.uwsRes.onData((chunk, isLast) => {
|
|
593
|
-
if (aborted)
|
|
594
|
-
return;
|
|
595
|
-
const data = Buffer.from(new Uint8Array(chunk));
|
|
596
|
-
//@ts-ignore
|
|
597
|
-
buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
|
|
598
|
-
try {
|
|
599
|
-
while (true) {
|
|
600
|
-
if (!headerParsed) {
|
|
601
|
-
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
602
|
-
if (headerEnd === -1)
|
|
603
|
-
break;
|
|
604
|
-
const header = buffer.slice(0, headerEnd).toString();
|
|
605
|
-
buffer = buffer.slice(headerEnd + 4);
|
|
606
|
-
const disposition = header.match(/name="([^"]+)"(?:; filename="([^"]+)")?/);
|
|
607
|
-
if (!disposition)
|
|
608
|
-
continue;
|
|
609
|
-
const fieldName = disposition[1];
|
|
610
|
-
const fileName = disposition[2];
|
|
611
|
-
if (!fileName) {
|
|
612
|
-
const nextBoundary = buffer.indexOf(boundaryBuf);
|
|
613
|
-
if (nextBoundary === -1)
|
|
614
|
-
break;
|
|
615
|
-
const value = buffer.slice(0, nextBoundary).toString().trim();
|
|
616
|
-
body[fieldName] = value;
|
|
617
|
-
buffer = buffer.slice(nextBoundary + boundaryBuf.length);
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
const contentTypeMatch = header.match(/Content-Type: ([^\r\n]+)/);
|
|
621
|
-
const mimetype = contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream';
|
|
622
|
-
const extension = mime_types_1.default.extension(mimetype)
|
|
623
|
-
|| path_1.default.extname(fileName).replace('.', '')
|
|
624
|
-
|| 'bin';
|
|
625
|
-
const tempFilename = crypto_1.default.randomBytes(16).toString('hex');
|
|
626
|
-
const filePath = path_1.default.join(path_1.default.resolve(), `${temp}/${tempFilename}`);
|
|
627
|
-
currentFileStream = fs_1.default.createWriteStream(filePath);
|
|
628
|
-
file = {
|
|
629
|
-
name: fileName,
|
|
630
|
-
tempFilePath: filePath,
|
|
631
|
-
tempFileName: tempFilename,
|
|
632
|
-
mimetype: mimetype,
|
|
633
|
-
extension: extension,
|
|
634
|
-
size: 0,
|
|
635
|
-
sizes: {
|
|
636
|
-
bytes: 0,
|
|
637
|
-
kb: 0,
|
|
638
|
-
mb: 0,
|
|
639
|
-
gb: 0
|
|
640
|
-
},
|
|
641
|
-
write: (to) => {
|
|
642
|
-
return new Promise((resolve, reject) => {
|
|
643
|
-
fs_1.default.createReadStream(filePath)
|
|
644
|
-
.pipe(fs_1.default.createWriteStream(to))
|
|
645
|
-
.on('finish', () => {
|
|
646
|
-
return resolve(null);
|
|
647
|
-
})
|
|
648
|
-
.on('error', (err) => {
|
|
649
|
-
return reject(err);
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
},
|
|
653
|
-
remove: () => {
|
|
654
|
-
return new Promise(resolve => setTimeout(() => {
|
|
655
|
-
fs_1.default.unlinkSync(filePath);
|
|
656
|
-
return resolve(null);
|
|
657
|
-
}, 100));
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
if (!files[fieldName])
|
|
661
|
-
files[fieldName] = [];
|
|
662
|
-
files[fieldName].push(file);
|
|
663
|
-
if (options.removeTempFile.remove) {
|
|
664
|
-
removeTemp(filePath, options.removeTempFile.ms);
|
|
665
|
-
}
|
|
666
|
-
headerParsed = true;
|
|
667
|
-
}
|
|
668
|
-
const boundaryIndex = buffer.indexOf(boundaryBuf);
|
|
669
|
-
if (boundaryIndex === -1) {
|
|
670
|
-
const safeLength = buffer.length - boundaryBuf.length;
|
|
671
|
-
if (safeLength > 0) {
|
|
672
|
-
const writeChunk = buffer.slice(0, safeLength);
|
|
673
|
-
currentFileStream.write(writeChunk);
|
|
674
|
-
file.size += writeChunk.length;
|
|
675
|
-
file.sizes = {
|
|
676
|
-
bytes: file.size,
|
|
677
|
-
kb: file.size / 1024,
|
|
678
|
-
mb: file.size / 1024 / 1024,
|
|
679
|
-
gb: file.size / 1024 / 1024 / 1024
|
|
680
|
-
};
|
|
681
|
-
if (file.size > options.limit) {
|
|
682
|
-
return fail(new Error(`File too large (limit ${options.limit} bytes)`));
|
|
683
|
-
}
|
|
684
|
-
buffer = buffer.slice(safeLength);
|
|
685
|
-
}
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
const filePart = buffer.slice(0, boundaryIndex);
|
|
689
|
-
currentFileStream.write(filePart);
|
|
690
|
-
file.size += filePart.length;
|
|
691
|
-
file.sizes = {
|
|
692
|
-
bytes: file.size,
|
|
693
|
-
kb: file.size / 1024,
|
|
694
|
-
mb: file.size / 1024 / 1024,
|
|
695
|
-
gb: file.size / 1024 / 1024 / 1024
|
|
696
|
-
};
|
|
697
|
-
if (file.size > options.limit) {
|
|
698
|
-
return fail(new Error(`File too large (limit ${options.limit} bytes)`));
|
|
699
|
-
}
|
|
700
|
-
currentFileStream.end();
|
|
701
|
-
currentFileStream = null;
|
|
702
|
-
file = null;
|
|
703
|
-
buffer = buffer.slice(boundaryIndex + boundaryBuf.length);
|
|
704
|
-
headerParsed = false;
|
|
705
|
-
}
|
|
706
|
-
if (isLast && !aborted) {
|
|
707
|
-
if (currentFileStream)
|
|
708
|
-
currentFileStream.end();
|
|
709
|
-
return resolve({ body, files });
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
catch (err) {
|
|
713
|
-
return fail(err);
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
uWSBody(res) {
|
|
719
|
-
return new Promise((resolve, reject) => {
|
|
720
|
-
let buffer = [];
|
|
721
|
-
//@ts-ignore
|
|
722
|
-
res.uwsRes.onData((chunk, isLast) => {
|
|
723
|
-
buffer.push(Buffer.from(chunk));
|
|
724
|
-
if (isLast) {
|
|
725
|
-
const body = Buffer.concat(buffer).toString();
|
|
726
|
-
try {
|
|
727
|
-
const json = JSON.parse(body);
|
|
728
|
-
return resolve(json);
|
|
729
|
-
}
|
|
730
|
-
catch (err) {
|
|
731
|
-
return reject(err);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
});
|
|
736
558
|
}
|
|
737
559
|
}
|
|
738
560
|
exports.ParserFactory = ParserFactory;
|