balda-js 0.0.2 → 0.0.3
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/package.json +1 -6
- package/lib/cli.d.ts +0 -6
- package/lib/cli.js +0 -929
- package/lib/cli.js.map +0 -1
- package/lib/index.cjs +0 -3384
- package/lib/index.cjs.map +0 -1
- package/lib/index.d.cts +0 -1492
- package/lib/index.d.ts +0 -1492
- package/lib/index.js +0 -3327
- package/lib/index.js.map +0 -1
package/lib/index.cjs
DELETED
@@ -1,3384 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __create = Object.create;
|
3
|
-
var __defProp = Object.defineProperty;
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
8
|
-
var __export = (target, all) => {
|
9
|
-
for (var name in all)
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
11
|
-
};
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
14
|
-
for (let key of __getOwnPropNames(from))
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
17
|
-
}
|
18
|
-
return to;
|
19
|
-
};
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
26
|
-
mod
|
27
|
-
));
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
29
|
-
|
30
|
-
// src/index.ts
|
31
|
-
var index_exports = {};
|
32
|
-
__export(index_exports, {
|
33
|
-
BasePlugin: () => BasePlugin,
|
34
|
-
Request: () => Request2,
|
35
|
-
Response: () => Response2,
|
36
|
-
Server: () => Server,
|
37
|
-
controller: () => controller,
|
38
|
-
cookie: () => cookie,
|
39
|
-
cors: () => cors,
|
40
|
-
del: () => del,
|
41
|
-
fileParser: () => fileParser,
|
42
|
-
get: () => get,
|
43
|
-
getContentType: () => getContentType,
|
44
|
-
helmet: () => helmet,
|
45
|
-
json: () => json,
|
46
|
-
log: () => log,
|
47
|
-
middleware: () => middleware,
|
48
|
-
patch: () => patch,
|
49
|
-
post: () => post,
|
50
|
-
put: () => put,
|
51
|
-
rateLimiter: () => rateLimiter,
|
52
|
-
router: () => router2,
|
53
|
-
serveStatic: () => serveStatic,
|
54
|
-
urlencoded: () => urlencoded,
|
55
|
-
validate: () => validate
|
56
|
-
});
|
57
|
-
module.exports = __toCommonJS(index_exports);
|
58
|
-
|
59
|
-
// src/decorators/controller/controller.ts
|
60
|
-
var import_node_path = require("path");
|
61
|
-
|
62
|
-
// src/metadata_store.ts
|
63
|
-
var MetadataStore = class {
|
64
|
-
static metadata = /* @__PURE__ */ new WeakMap();
|
65
|
-
/**
|
66
|
-
* Set the metadata for the given target and property key
|
67
|
-
*/
|
68
|
-
static set(target, propertyKey, value) {
|
69
|
-
if (!this.metadata.has(target)) {
|
70
|
-
this.metadata.set(target, /* @__PURE__ */ new Map());
|
71
|
-
}
|
72
|
-
this.metadata.get(target).set(propertyKey, value);
|
73
|
-
}
|
74
|
-
/**
|
75
|
-
* Get the metadata for the given target and property key
|
76
|
-
*/
|
77
|
-
static get(target, propertyKey) {
|
78
|
-
return this.metadata.get(target)?.get(propertyKey);
|
79
|
-
}
|
80
|
-
/**
|
81
|
-
* Get all the metadata for the given target
|
82
|
-
*/
|
83
|
-
static getAll(target) {
|
84
|
-
return this.metadata.get(target) || /* @__PURE__ */ new Map();
|
85
|
-
}
|
86
|
-
/**
|
87
|
-
* Delete the metadata for the given target and property key
|
88
|
-
*/
|
89
|
-
static delete(target, propertyKey) {
|
90
|
-
this.metadata.get(target)?.delete(propertyKey.toString());
|
91
|
-
}
|
92
|
-
/**
|
93
|
-
* Clear all the metadata for the given target
|
94
|
-
*/
|
95
|
-
static clear(target) {
|
96
|
-
this.metadata.delete(target);
|
97
|
-
}
|
98
|
-
};
|
99
|
-
|
100
|
-
// src/server/router/router.ts
|
101
|
-
var Node = class {
|
102
|
-
staticChildren;
|
103
|
-
paramChild;
|
104
|
-
wildcardChild;
|
105
|
-
middleware;
|
106
|
-
handler;
|
107
|
-
constructor() {
|
108
|
-
this.staticChildren = /* @__PURE__ */ new Map();
|
109
|
-
this.paramChild = null;
|
110
|
-
this.wildcardChild = null;
|
111
|
-
this.middleware = null;
|
112
|
-
this.handler = null;
|
113
|
-
}
|
114
|
-
};
|
115
|
-
var Router = class {
|
116
|
-
trees;
|
117
|
-
routes;
|
118
|
-
constructor() {
|
119
|
-
this.trees = /* @__PURE__ */ new Map();
|
120
|
-
this.routes = [];
|
121
|
-
}
|
122
|
-
getRoutes() {
|
123
|
-
return this.routes.slice();
|
124
|
-
}
|
125
|
-
addOrUpdate(method, path, middleware2, handler, swaggerOptions) {
|
126
|
-
method = method.toUpperCase();
|
127
|
-
let root = this.trees.get(method);
|
128
|
-
if (!root) {
|
129
|
-
root = new Node();
|
130
|
-
this.trees.set(method, root);
|
131
|
-
}
|
132
|
-
const clean = path.split("?")[0];
|
133
|
-
const segments = clean.replace(/^\/+|\/+$/g, "").split("/");
|
134
|
-
let node = root;
|
135
|
-
for (const seg of segments) {
|
136
|
-
if (seg === "*") {
|
137
|
-
if (!node.wildcardChild) {
|
138
|
-
node.wildcardChild = new Node();
|
139
|
-
}
|
140
|
-
node = node.wildcardChild;
|
141
|
-
break;
|
142
|
-
}
|
143
|
-
if (seg.startsWith(":")) {
|
144
|
-
const name = seg.slice(1);
|
145
|
-
if (!node.paramChild) {
|
146
|
-
node.paramChild = { node: new Node(), name };
|
147
|
-
}
|
148
|
-
node = node.paramChild.node;
|
149
|
-
continue;
|
150
|
-
}
|
151
|
-
if (!node.staticChildren.has(seg)) {
|
152
|
-
node.staticChildren.set(seg, new Node());
|
153
|
-
}
|
154
|
-
node = node.staticChildren.get(seg);
|
155
|
-
}
|
156
|
-
node.middleware = middleware2;
|
157
|
-
node.handler = handler;
|
158
|
-
const idx = this.routes.findIndex(
|
159
|
-
(r) => r.method === method && r.path === path
|
160
|
-
);
|
161
|
-
if (idx !== -1) {
|
162
|
-
this.routes[idx].middleware = middleware2;
|
163
|
-
this.routes[idx].handler = handler;
|
164
|
-
return;
|
165
|
-
}
|
166
|
-
this.routes.push({ method, path, middleware: middleware2, handler, swaggerOptions });
|
167
|
-
}
|
168
|
-
find(method, rawPath) {
|
169
|
-
method = method.toUpperCase();
|
170
|
-
const root = this.trees.get(method);
|
171
|
-
if (!root) {
|
172
|
-
return null;
|
173
|
-
}
|
174
|
-
const clean = rawPath.split("?")[0];
|
175
|
-
const segments = clean.replace(/^\/+|\/+$/g, "").split("/");
|
176
|
-
const params = {};
|
177
|
-
let node = root;
|
178
|
-
for (let i = 0; i < segments.length; i++) {
|
179
|
-
const seg = segments[i];
|
180
|
-
if (node.staticChildren.has(seg)) {
|
181
|
-
node = node.staticChildren.get(seg);
|
182
|
-
continue;
|
183
|
-
}
|
184
|
-
if (node.paramChild) {
|
185
|
-
params[node.paramChild.name] = seg;
|
186
|
-
node = node.paramChild.node;
|
187
|
-
continue;
|
188
|
-
}
|
189
|
-
if (node.wildcardChild) {
|
190
|
-
params["*"] = segments.slice(i).join("/");
|
191
|
-
node = node.wildcardChild;
|
192
|
-
break;
|
193
|
-
}
|
194
|
-
return null;
|
195
|
-
}
|
196
|
-
if (!node.handler || !node.middleware) {
|
197
|
-
return null;
|
198
|
-
}
|
199
|
-
return { middleware: node.middleware, handler: node.handler, params };
|
200
|
-
}
|
201
|
-
/**
|
202
|
-
* Apply global middlewares to all routes
|
203
|
-
* @param middlewares - The middlewares to apply
|
204
|
-
* @internal
|
205
|
-
*/
|
206
|
-
applyGlobalMiddlewaresToAllRoutes(middlewares) {
|
207
|
-
for (const route of this.routes) {
|
208
|
-
this.addOrUpdate(
|
209
|
-
route.method,
|
210
|
-
route.path,
|
211
|
-
[...middlewares, ...route.middleware || []],
|
212
|
-
route.handler
|
213
|
-
);
|
214
|
-
}
|
215
|
-
}
|
216
|
-
};
|
217
|
-
var router = new Router();
|
218
|
-
|
219
|
-
// src/decorators/controller/controller.ts
|
220
|
-
var controller = (path, swaggerOptions) => {
|
221
|
-
return (target) => {
|
222
|
-
const classMeta = MetadataStore.get(target.prototype, "__class__");
|
223
|
-
const classMiddlewares = classMeta?.middlewares || [];
|
224
|
-
const metaMap = MetadataStore.getAll(target.prototype);
|
225
|
-
for (const [propertyKey, meta] of metaMap.entries()) {
|
226
|
-
if (!meta.route) {
|
227
|
-
continue;
|
228
|
-
}
|
229
|
-
const handler = target.prototype[propertyKey];
|
230
|
-
const fullPath = path ? (0, import_node_path.join)(path, meta.route.path) : meta.route.path;
|
231
|
-
const allMiddlewares = [...classMiddlewares, ...meta.middlewares || []];
|
232
|
-
router.addOrUpdate(
|
233
|
-
meta.route.method,
|
234
|
-
fullPath,
|
235
|
-
allMiddlewares,
|
236
|
-
handler,
|
237
|
-
{
|
238
|
-
// default service name
|
239
|
-
service: target.name.replace(/Controller$/, ""),
|
240
|
-
// controller options
|
241
|
-
...swaggerOptions,
|
242
|
-
// route options
|
243
|
-
...meta.documentation
|
244
|
-
}
|
245
|
-
);
|
246
|
-
}
|
247
|
-
MetadataStore.clear(target.prototype);
|
248
|
-
};
|
249
|
-
};
|
250
|
-
|
251
|
-
// src/decorators/handlers/del.ts
|
252
|
-
var del = (path, options) => {
|
253
|
-
return (target, propertyKey, descriptor) => {
|
254
|
-
let meta = MetadataStore.get(target, propertyKey);
|
255
|
-
if (!meta) {
|
256
|
-
meta = { middlewares: [], route: { path, method: "DELETE" } };
|
257
|
-
}
|
258
|
-
meta.documentation = {
|
259
|
-
...meta.documentation || {},
|
260
|
-
name: propertyKey,
|
261
|
-
...options
|
262
|
-
};
|
263
|
-
meta.route = { path, method: "DELETE" };
|
264
|
-
MetadataStore.set(target, propertyKey, meta);
|
265
|
-
return descriptor;
|
266
|
-
};
|
267
|
-
};
|
268
|
-
|
269
|
-
// src/decorators/handlers/get.ts
|
270
|
-
var get = (path, options) => {
|
271
|
-
return (target, propertyKey, descriptor) => {
|
272
|
-
let meta = MetadataStore.get(target, propertyKey);
|
273
|
-
if (!meta) {
|
274
|
-
meta = { middlewares: [], route: { path, method: "GET" } };
|
275
|
-
}
|
276
|
-
meta.documentation = {
|
277
|
-
...meta.documentation || {},
|
278
|
-
name: propertyKey,
|
279
|
-
...options
|
280
|
-
};
|
281
|
-
meta.route = { path, method: "GET" };
|
282
|
-
MetadataStore.set(target, propertyKey, meta);
|
283
|
-
return descriptor;
|
284
|
-
};
|
285
|
-
};
|
286
|
-
|
287
|
-
// src/decorators/handlers/patch.ts
|
288
|
-
var patch = (path, options) => {
|
289
|
-
return (target, propertyKey, descriptor) => {
|
290
|
-
let meta = MetadataStore.get(target, propertyKey);
|
291
|
-
if (!meta) {
|
292
|
-
meta = { middlewares: [], route: { path, method: "PATCH" } };
|
293
|
-
}
|
294
|
-
meta.documentation = {
|
295
|
-
...meta.documentation || {},
|
296
|
-
name: propertyKey,
|
297
|
-
...options
|
298
|
-
};
|
299
|
-
meta.route = { path, method: "PATCH" };
|
300
|
-
MetadataStore.set(target, propertyKey, meta);
|
301
|
-
return descriptor;
|
302
|
-
};
|
303
|
-
};
|
304
|
-
|
305
|
-
// src/decorators/handlers/post.ts
|
306
|
-
var post = (path, options) => {
|
307
|
-
return (target, propertyKey, descriptor) => {
|
308
|
-
let meta = MetadataStore.get(target, propertyKey);
|
309
|
-
if (!meta) {
|
310
|
-
meta = { middlewares: [], route: { path, method: "POST" } };
|
311
|
-
}
|
312
|
-
meta.documentation = {
|
313
|
-
...meta.documentation || {},
|
314
|
-
name: propertyKey,
|
315
|
-
...options
|
316
|
-
};
|
317
|
-
meta.route = { path, method: "POST" };
|
318
|
-
MetadataStore.set(target, propertyKey, meta);
|
319
|
-
return descriptor;
|
320
|
-
};
|
321
|
-
};
|
322
|
-
|
323
|
-
// src/decorators/handlers/put.ts
|
324
|
-
var put = (path, options) => {
|
325
|
-
return (target, propertyKey, descriptor) => {
|
326
|
-
let meta = MetadataStore.get(target, propertyKey);
|
327
|
-
if (!meta) {
|
328
|
-
meta = { middlewares: [], route: { path, method: "PUT" } };
|
329
|
-
}
|
330
|
-
meta.documentation = {
|
331
|
-
...meta.documentation || {},
|
332
|
-
name: propertyKey,
|
333
|
-
...options
|
334
|
-
};
|
335
|
-
meta.route = { path, method: "PUT" };
|
336
|
-
MetadataStore.set(target, propertyKey, meta);
|
337
|
-
return descriptor;
|
338
|
-
};
|
339
|
-
};
|
340
|
-
|
341
|
-
// src/decorators/middleware/middleware.ts
|
342
|
-
var middleware = (middleware2) => {
|
343
|
-
return (target, propertyKey, descriptor) => {
|
344
|
-
if (typeof propertyKey === "undefined") {
|
345
|
-
let meta2 = MetadataStore.get(target.prototype, "__class__");
|
346
|
-
if (!meta2) {
|
347
|
-
meta2 = { middlewares: [] };
|
348
|
-
}
|
349
|
-
if (!meta2.middlewares) {
|
350
|
-
meta2.middlewares = [];
|
351
|
-
}
|
352
|
-
if (!middleware2) {
|
353
|
-
throw new Error(
|
354
|
-
`Middleware ${String(
|
355
|
-
middleware2
|
356
|
-
)} not found, are you sure you defined it before using it?`
|
357
|
-
);
|
358
|
-
}
|
359
|
-
if (!Array.isArray(middleware2)) {
|
360
|
-
middleware2 = [middleware2];
|
361
|
-
}
|
362
|
-
meta2.middlewares.push(...middleware2);
|
363
|
-
MetadataStore.set(target.prototype, "__class__", meta2);
|
364
|
-
return target;
|
365
|
-
}
|
366
|
-
let meta = MetadataStore.get(target, propertyKey);
|
367
|
-
if (!meta) {
|
368
|
-
meta = { middlewares: [] };
|
369
|
-
}
|
370
|
-
if (!meta.middlewares) {
|
371
|
-
meta.middlewares = [];
|
372
|
-
}
|
373
|
-
if (!Array.isArray(middleware2)) {
|
374
|
-
middleware2 = [middleware2];
|
375
|
-
}
|
376
|
-
meta.middlewares.push(...middleware2);
|
377
|
-
MetadataStore.set(target, propertyKey, meta);
|
378
|
-
return descriptor;
|
379
|
-
};
|
380
|
-
};
|
381
|
-
|
382
|
-
// src/decorators/validation/validate.ts
|
383
|
-
var import_ajv = require("ajv");
|
384
|
-
var validateDecorator = (options) => {
|
385
|
-
return (target, propertyKey, descriptor) => {
|
386
|
-
const originalMethod = descriptor.value;
|
387
|
-
let meta = MetadataStore.get(target, propertyKey);
|
388
|
-
if (!meta) {
|
389
|
-
meta = { middlewares: [], route: {} };
|
390
|
-
}
|
391
|
-
if (!meta.documentation) {
|
392
|
-
meta.documentation = {};
|
393
|
-
}
|
394
|
-
if (options.body) {
|
395
|
-
meta.documentation.requestBody = options.body;
|
396
|
-
}
|
397
|
-
if (options.query) {
|
398
|
-
meta.documentation.query = options.query;
|
399
|
-
}
|
400
|
-
MetadataStore.set(target, propertyKey, meta);
|
401
|
-
descriptor.value = async function(...args) {
|
402
|
-
const req = args[0];
|
403
|
-
const res = args[1];
|
404
|
-
try {
|
405
|
-
let validatedBody = void 0;
|
406
|
-
let validatedQuery = void 0;
|
407
|
-
let validatedAll = void 0;
|
408
|
-
if (options.body) {
|
409
|
-
validatedBody = req.validate(options.body, options.safe);
|
410
|
-
}
|
411
|
-
if (options.query) {
|
412
|
-
validatedQuery = req.validateQuery(options.query, options.safe);
|
413
|
-
}
|
414
|
-
if (options.all) {
|
415
|
-
validatedAll = req.validateAll(options.all, options.safe);
|
416
|
-
}
|
417
|
-
const newArgs = [...args];
|
418
|
-
if (validatedBody !== void 0) {
|
419
|
-
newArgs.push(validatedBody);
|
420
|
-
}
|
421
|
-
if (validatedQuery !== void 0) {
|
422
|
-
newArgs.push(validatedQuery);
|
423
|
-
}
|
424
|
-
if (validatedAll !== void 0) {
|
425
|
-
newArgs.push(validatedAll);
|
426
|
-
}
|
427
|
-
return originalMethod.apply(this, newArgs);
|
428
|
-
} catch (error) {
|
429
|
-
if (!(error instanceof import_ajv.ValidationError)) {
|
430
|
-
throw error;
|
431
|
-
}
|
432
|
-
if (options.customError) {
|
433
|
-
return res.status(options.customError.status || 400).json({
|
434
|
-
received: req.body,
|
435
|
-
schema: options.body,
|
436
|
-
error: error.errors
|
437
|
-
});
|
438
|
-
}
|
439
|
-
return res.badRequest(error);
|
440
|
-
}
|
441
|
-
};
|
442
|
-
return descriptor;
|
443
|
-
};
|
444
|
-
};
|
445
|
-
validateDecorator.query = (schema, customError) => {
|
446
|
-
return validateDecorator({ query: schema, customError });
|
447
|
-
};
|
448
|
-
validateDecorator.body = (schema, customError) => {
|
449
|
-
return validateDecorator({ body: schema, customError });
|
450
|
-
};
|
451
|
-
validateDecorator.all = (schema, customError) => {
|
452
|
-
return validateDecorator({ all: schema, customError });
|
453
|
-
};
|
454
|
-
var validate = validateDecorator;
|
455
|
-
|
456
|
-
// src/server/http/request.ts
|
457
|
-
var import_typebox = require("@sinclair/typebox");
|
458
|
-
|
459
|
-
// src/validator/validator.ts
|
460
|
-
var import_ajv2 = __toESM(require("ajv"), 1);
|
461
|
-
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
462
|
-
var ajv = (0, import_ajv_formats.default)(new import_ajv2.default(), [
|
463
|
-
"date-time",
|
464
|
-
"time",
|
465
|
-
"date",
|
466
|
-
"email",
|
467
|
-
"hostname",
|
468
|
-
"ipv4",
|
469
|
-
"ipv6",
|
470
|
-
"uri",
|
471
|
-
"uri-reference",
|
472
|
-
"uuid",
|
473
|
-
"uri-template",
|
474
|
-
"json-pointer",
|
475
|
-
"relative-json-pointer",
|
476
|
-
"regex",
|
477
|
-
"password",
|
478
|
-
"binary",
|
479
|
-
"byte",
|
480
|
-
"iso-date-time",
|
481
|
-
"iso-time"
|
482
|
-
]);
|
483
|
-
var validateSchema = (inputSchema, data, safe = false) => {
|
484
|
-
const validate2 = ajv.compile(inputSchema);
|
485
|
-
if (!validate2(data)) {
|
486
|
-
if (safe) {
|
487
|
-
return data;
|
488
|
-
}
|
489
|
-
throw new import_ajv2.ValidationError(validate2.errors || []);
|
490
|
-
}
|
491
|
-
return data;
|
492
|
-
};
|
493
|
-
|
494
|
-
// src/runtime/native_request.ts
|
495
|
-
var NativeRequest = class extends Request {
|
496
|
-
};
|
497
|
-
|
498
|
-
// src/server/http/request.ts
|
499
|
-
var import_node_crypto = require("crypto");
|
500
|
-
var Request2 = class _Request extends NativeRequest {
|
501
|
-
static fromRequest(request) {
|
502
|
-
return new _Request(request.url, {
|
503
|
-
method: request.method,
|
504
|
-
body: request.body,
|
505
|
-
headers: request.headers
|
506
|
-
});
|
507
|
-
}
|
508
|
-
/**
|
509
|
-
* Enrich native request with validation methods.
|
510
|
-
*/
|
511
|
-
static enrichRequest(request) {
|
512
|
-
request.validate = (inputSchema, safe = false) => {
|
513
|
-
if (typeof inputSchema === "function") {
|
514
|
-
inputSchema = inputSchema(import_typebox.Type);
|
515
|
-
}
|
516
|
-
return validateSchema(inputSchema, request.body || {}, safe);
|
517
|
-
};
|
518
|
-
request.validateQuery = (inputSchema, safe = false) => {
|
519
|
-
if (typeof inputSchema === "function") {
|
520
|
-
inputSchema = inputSchema(import_typebox.Type);
|
521
|
-
}
|
522
|
-
return validateSchema(inputSchema, request.query || {}, safe);
|
523
|
-
};
|
524
|
-
request.validateAll = (inputSchema, safe = false) => {
|
525
|
-
if (typeof inputSchema === "function") {
|
526
|
-
inputSchema = inputSchema(import_typebox.Type);
|
527
|
-
}
|
528
|
-
return validateSchema(
|
529
|
-
inputSchema,
|
530
|
-
{
|
531
|
-
...request.body ? { body: request.body } : {},
|
532
|
-
...request.query ? { query: request.query } : {}
|
533
|
-
},
|
534
|
-
safe
|
535
|
-
);
|
536
|
-
};
|
537
|
-
request.file = (fieldName) => {
|
538
|
-
return request.files.find((file) => file.formName === fieldName) ?? null;
|
539
|
-
};
|
540
|
-
request.cookies = {};
|
541
|
-
request.cookie = (name) => {
|
542
|
-
return request.cookies[name];
|
543
|
-
};
|
544
|
-
request.files = [];
|
545
|
-
return request;
|
546
|
-
}
|
547
|
-
/**
|
548
|
-
* The file of the request. Only available for multipart/form-data requests and if the file parser middleware is used.
|
549
|
-
*/
|
550
|
-
file = (fieldName) => {
|
551
|
-
return this.files.find((file) => file.formName === fieldName) ?? null;
|
552
|
-
};
|
553
|
-
/**
|
554
|
-
* The cookies of the request. Only available if the cookie middleware is used.
|
555
|
-
*/
|
556
|
-
cookies = {};
|
557
|
-
/**
|
558
|
-
* The cookie of the request. Only available if the cookie middleware is used.
|
559
|
-
*/
|
560
|
-
cookie = (name) => {
|
561
|
-
return this.cookies[name];
|
562
|
-
};
|
563
|
-
/**
|
564
|
-
* The ip address of the request.
|
565
|
-
* Tries to get the ip address from the `x-forwarded-for` header. If not available, it will use the remote address from the request.
|
566
|
-
*/
|
567
|
-
ip;
|
568
|
-
/**
|
569
|
-
* The files of the request. Only available for multipart/form-data requests and if the file parser middleware is used.
|
570
|
-
*/
|
571
|
-
files = [];
|
572
|
-
/**
|
573
|
-
* The parameters of the request.
|
574
|
-
*/
|
575
|
-
params = {};
|
576
|
-
/**
|
577
|
-
* The query parameters of the request.
|
578
|
-
*/
|
579
|
-
query = {};
|
580
|
-
/**
|
581
|
-
* The id of the request.
|
582
|
-
*/
|
583
|
-
get id() {
|
584
|
-
if (!this._id) {
|
585
|
-
this._id = (0, import_node_crypto.randomUUID)();
|
586
|
-
}
|
587
|
-
return this._id;
|
588
|
-
}
|
589
|
-
/**
|
590
|
-
* The parsed body of the request
|
591
|
-
*/
|
592
|
-
body;
|
593
|
-
/**
|
594
|
-
* The validated body of the request.
|
595
|
-
* @param inputSchema - The schema to validate the body against.
|
596
|
-
* @param safe - If true, the function will return the original body if the validation fails instead of throwing an error.
|
597
|
-
*/
|
598
|
-
validate(inputSchema, safe = false) {
|
599
|
-
if (typeof inputSchema === "function") {
|
600
|
-
inputSchema = inputSchema(import_typebox.Type);
|
601
|
-
}
|
602
|
-
return validateSchema(inputSchema, this.body || {}, safe);
|
603
|
-
}
|
604
|
-
/**
|
605
|
-
* Validates the query string of the request.
|
606
|
-
*/
|
607
|
-
validateQuery(inputSchema, safe = false) {
|
608
|
-
if (typeof inputSchema === "function") {
|
609
|
-
inputSchema = inputSchema(import_typebox.Type);
|
610
|
-
}
|
611
|
-
return validateSchema(inputSchema, this.query || {}, safe);
|
612
|
-
}
|
613
|
-
/**
|
614
|
-
* Validates the body and query string of the request.
|
615
|
-
*/
|
616
|
-
validateAll(inputSchema, safe = false) {
|
617
|
-
if (typeof inputSchema === "function") {
|
618
|
-
inputSchema = inputSchema(import_typebox.Type);
|
619
|
-
}
|
620
|
-
return validateSchema(
|
621
|
-
inputSchema,
|
622
|
-
{
|
623
|
-
...this.body ? { body: this.body } : {},
|
624
|
-
...this.query ? { query: this.query } : {}
|
625
|
-
},
|
626
|
-
safe
|
627
|
-
);
|
628
|
-
}
|
629
|
-
};
|
630
|
-
|
631
|
-
// src/runtime/runtime.ts
|
632
|
-
var RunTime = class {
|
633
|
-
type;
|
634
|
-
constructor() {
|
635
|
-
this.type = this.getRunTime();
|
636
|
-
}
|
637
|
-
getRunTime() {
|
638
|
-
if (typeof Bun !== "undefined") {
|
639
|
-
return "bun";
|
640
|
-
} else if (typeof Deno !== "undefined") {
|
641
|
-
return "deno";
|
642
|
-
} else if (typeof process !== "undefined") {
|
643
|
-
return "node";
|
644
|
-
}
|
645
|
-
throw new Error("No environment detected");
|
646
|
-
}
|
647
|
-
};
|
648
|
-
var runtime = new RunTime();
|
649
|
-
|
650
|
-
// src/runtime/native_file.ts
|
651
|
-
var import_promises = __toESM(require("fs/promises"), 1);
|
652
|
-
var NativeFile = class {
|
653
|
-
file(path) {
|
654
|
-
switch (runtime.type) {
|
655
|
-
case "bun":
|
656
|
-
return Bun.file(path);
|
657
|
-
case "node":
|
658
|
-
return import_promises.default.readFile(path);
|
659
|
-
case "deno":
|
660
|
-
return Deno.readFile(path);
|
661
|
-
default:
|
662
|
-
throw new Error("Unsupported runtime");
|
663
|
-
}
|
664
|
-
}
|
665
|
-
};
|
666
|
-
var nativeFile = new NativeFile();
|
667
|
-
|
668
|
-
// src/plugins/static/static.ts
|
669
|
-
var import_node_path2 = require("path");
|
670
|
-
|
671
|
-
// src/plugins/static/static_constants.ts
|
672
|
-
var mimeTypes = /* @__PURE__ */ new Map([
|
673
|
-
[".html", "text/html"],
|
674
|
-
[".css", "text/css"],
|
675
|
-
[".js", "application/javascript"],
|
676
|
-
[".png", "image/png"],
|
677
|
-
[".jpg", "image/jpeg"],
|
678
|
-
[".gif", "image/gif"],
|
679
|
-
[".svg", "image/svg+xml"],
|
680
|
-
[".json", "application/json"],
|
681
|
-
[".txt", "text/plain"],
|
682
|
-
[".ico", "image/x-icon"],
|
683
|
-
[".webp", "image/webp"],
|
684
|
-
[".mp4", "video/mp4"],
|
685
|
-
[".mp3", "audio/mpeg"],
|
686
|
-
[".wav", "audio/wav"],
|
687
|
-
[".ogg", "audio/ogg"],
|
688
|
-
[".webm", "video/webm"]
|
689
|
-
]);
|
690
|
-
|
691
|
-
// src/runtime/native_cwd.ts
|
692
|
-
var NativeCwd = class {
|
693
|
-
getCwd() {
|
694
|
-
switch (runtime.type) {
|
695
|
-
case "node":
|
696
|
-
case "bun":
|
697
|
-
return process.cwd();
|
698
|
-
case "deno":
|
699
|
-
return Deno.cwd();
|
700
|
-
default:
|
701
|
-
throw new Error("Unsupported runtime");
|
702
|
-
}
|
703
|
-
}
|
704
|
-
};
|
705
|
-
var nativeCwd = new NativeCwd();
|
706
|
-
|
707
|
-
// src/runtime/native_fs.ts
|
708
|
-
var NativeFs = class {
|
709
|
-
async readFile(path) {
|
710
|
-
switch (runtime.type) {
|
711
|
-
case "node":
|
712
|
-
const fs2 = await import("fs/promises");
|
713
|
-
const buffer = await fs2.readFile(path);
|
714
|
-
return new Uint8Array(buffer);
|
715
|
-
case "bun":
|
716
|
-
const arrayBuffer = await Bun.file(path).arrayBuffer();
|
717
|
-
return new Uint8Array(arrayBuffer);
|
718
|
-
case "deno":
|
719
|
-
return new Uint8Array(await Deno.readFile(path));
|
720
|
-
}
|
721
|
-
}
|
722
|
-
async writeFile(path, data) {
|
723
|
-
switch (runtime.type) {
|
724
|
-
case "node":
|
725
|
-
const fs2 = await import("fs/promises");
|
726
|
-
await fs2.writeFile(path, data);
|
727
|
-
break;
|
728
|
-
case "bun":
|
729
|
-
await Bun.write(path, data);
|
730
|
-
break;
|
731
|
-
case "deno":
|
732
|
-
await Deno.writeFile(path, data);
|
733
|
-
break;
|
734
|
-
}
|
735
|
-
}
|
736
|
-
async stat(path) {
|
737
|
-
switch (runtime.type) {
|
738
|
-
case "node":
|
739
|
-
const fs2 = await import("fs/promises");
|
740
|
-
const stats = await fs2.stat(path);
|
741
|
-
return {
|
742
|
-
isDirectory: stats.isDirectory(),
|
743
|
-
isFile: stats.isFile(),
|
744
|
-
isSymbolicLink: stats.isSymbolicLink(),
|
745
|
-
size: stats.size
|
746
|
-
};
|
747
|
-
case "bun":
|
748
|
-
const bunStats = await Bun.file(path).stat();
|
749
|
-
return {
|
750
|
-
isDirectory: bunStats.isDirectory(),
|
751
|
-
isFile: bunStats.isFile(),
|
752
|
-
isSymbolicLink: bunStats.isSymbolicLink(),
|
753
|
-
size: bunStats.size
|
754
|
-
};
|
755
|
-
case "deno":
|
756
|
-
const denoStats = await Deno.stat(path);
|
757
|
-
return {
|
758
|
-
isDirectory: denoStats.isDirectory,
|
759
|
-
isFile: denoStats.isFile,
|
760
|
-
isSymbolicLink: false,
|
761
|
-
size: denoStats.size
|
762
|
-
};
|
763
|
-
}
|
764
|
-
}
|
765
|
-
async unlink(path) {
|
766
|
-
switch (runtime.type) {
|
767
|
-
case "node":
|
768
|
-
const fs2 = await import("fs/promises");
|
769
|
-
await fs2.unlink(path);
|
770
|
-
break;
|
771
|
-
case "bun":
|
772
|
-
await Bun.file(path).delete();
|
773
|
-
break;
|
774
|
-
case "deno":
|
775
|
-
await Deno.remove(path);
|
776
|
-
break;
|
777
|
-
default:
|
778
|
-
throw new Error("Unsupported runtime");
|
779
|
-
}
|
780
|
-
}
|
781
|
-
};
|
782
|
-
var nativeFs = new NativeFs();
|
783
|
-
|
784
|
-
// src/errors/balda_error.ts
|
785
|
-
var BaldaError = class extends Error {
|
786
|
-
constructor(message) {
|
787
|
-
super(message);
|
788
|
-
}
|
789
|
-
};
|
790
|
-
|
791
|
-
// src/errors/route_not_found.ts
|
792
|
-
var RouteNotFoundError = class extends BaldaError {
|
793
|
-
constructor(path, method) {
|
794
|
-
super(`ROUTE_NOT_FOUND: Cannot ${method} ${path}`);
|
795
|
-
}
|
796
|
-
};
|
797
|
-
|
798
|
-
// src/errors/error_factory.ts
|
799
|
-
var errorFactory = (error) => {
|
800
|
-
return {
|
801
|
-
name: error.constructor.name,
|
802
|
-
cause: error.cause,
|
803
|
-
message: error.message,
|
804
|
-
stack: error.stack
|
805
|
-
};
|
806
|
-
};
|
807
|
-
|
808
|
-
// src/errors/method_not_allowed.ts
|
809
|
-
var MethodNotAllowedError = class extends BaldaError {
|
810
|
-
constructor(path, method) {
|
811
|
-
super(`METHOD_NOT_ALLOWED: Cannot ${method} ${path}`);
|
812
|
-
}
|
813
|
-
};
|
814
|
-
|
815
|
-
// src/plugins/static/static.ts
|
816
|
-
var serveStatic = (path = "public", swaggerOptions) => {
|
817
|
-
router.addOrUpdate(
|
818
|
-
"GET",
|
819
|
-
`${path}/*`,
|
820
|
-
[],
|
821
|
-
async (req, res) => {
|
822
|
-
return staticFileHandler(req, res, path);
|
823
|
-
},
|
824
|
-
{
|
825
|
-
service: "StaticFiles",
|
826
|
-
...swaggerOptions
|
827
|
-
}
|
828
|
-
);
|
829
|
-
return async (_req, _res, next) => {
|
830
|
-
return next();
|
831
|
-
};
|
832
|
-
};
|
833
|
-
async function staticFileHandler(req, res, path) {
|
834
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
835
|
-
return res.status(405).json({
|
836
|
-
...errorFactory(new MethodNotAllowedError(req.url, req.method))
|
837
|
-
});
|
838
|
-
}
|
839
|
-
const wildcardPath = req.params["*"] || "";
|
840
|
-
const filePath = (0, import_node_path2.join)(path, wildcardPath);
|
841
|
-
const resolvedPath = (0, import_node_path2.resolve)(nativeCwd.getCwd(), filePath);
|
842
|
-
const stats = await nativeFs.stat(resolvedPath);
|
843
|
-
if (!stats.isFile) {
|
844
|
-
return res.notFound({
|
845
|
-
...errorFactory(new RouteNotFoundError(req.url, req.method))
|
846
|
-
});
|
847
|
-
}
|
848
|
-
const contentType = getContentType((0, import_node_path2.extname)(resolvedPath));
|
849
|
-
res.setHeader("Content-Type", contentType);
|
850
|
-
const fileContent = await nativeFile.file(resolvedPath);
|
851
|
-
res.raw(fileContent);
|
852
|
-
}
|
853
|
-
function getContentType(ext) {
|
854
|
-
return mimeTypes.get(ext) || "application/octet-stream";
|
855
|
-
}
|
856
|
-
|
857
|
-
// src/server/http/response.ts
|
858
|
-
var Response2 = class {
|
859
|
-
/**
|
860
|
-
* The status of the response
|
861
|
-
*/
|
862
|
-
responseStatus;
|
863
|
-
/**
|
864
|
-
* The headers of the response
|
865
|
-
*/
|
866
|
-
headers;
|
867
|
-
/**
|
868
|
-
* The body of the response
|
869
|
-
*/
|
870
|
-
body;
|
871
|
-
constructor(status = 200) {
|
872
|
-
this.responseStatus = status;
|
873
|
-
this.headers = {};
|
874
|
-
}
|
875
|
-
/**
|
876
|
-
* Set a header for the response
|
877
|
-
*/
|
878
|
-
setHeader(key, value) {
|
879
|
-
this.headers[key] = value;
|
880
|
-
return this;
|
881
|
-
}
|
882
|
-
/**
|
883
|
-
* Set the status of the response, status defaults to 200
|
884
|
-
*/
|
885
|
-
status(status) {
|
886
|
-
this.responseStatus = status;
|
887
|
-
return this;
|
888
|
-
}
|
889
|
-
/**
|
890
|
-
* Send a response with the given body, tries to determine the content type based on the body type, status defaults to 200
|
891
|
-
* @warning If cannot determine the content type, it will be sent as is
|
892
|
-
*/
|
893
|
-
send(body) {
|
894
|
-
if (body === null || body === void 0) {
|
895
|
-
return this.text("");
|
896
|
-
}
|
897
|
-
if (typeof body === "string") {
|
898
|
-
return this.text(body);
|
899
|
-
}
|
900
|
-
if (typeof body === "number" || typeof body === "boolean" || typeof body === "bigint") {
|
901
|
-
return this.text(String(body));
|
902
|
-
}
|
903
|
-
if (body instanceof Date) {
|
904
|
-
return this.text(body.toISOString());
|
905
|
-
}
|
906
|
-
if (body instanceof RegExp) {
|
907
|
-
return this.text(body.toString());
|
908
|
-
}
|
909
|
-
if (typeof Buffer !== "undefined" && body instanceof Buffer) {
|
910
|
-
return this.download(new Uint8Array(body));
|
911
|
-
}
|
912
|
-
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
913
|
-
return this.download(new Uint8Array(body));
|
914
|
-
}
|
915
|
-
if (typeof body === "object" && body !== null) {
|
916
|
-
try {
|
917
|
-
return this.json(body);
|
918
|
-
} catch (error) {
|
919
|
-
return this.text(String(body));
|
920
|
-
}
|
921
|
-
}
|
922
|
-
if (typeof body === "function") {
|
923
|
-
return this.text(body.toString());
|
924
|
-
}
|
925
|
-
if (typeof body === "symbol") {
|
926
|
-
return this.text(body.toString());
|
927
|
-
}
|
928
|
-
this.body = body;
|
929
|
-
}
|
930
|
-
/**
|
931
|
-
* Send a response with the given body without any content type or encoding (as is), status defaults to 200
|
932
|
-
*/
|
933
|
-
raw(body) {
|
934
|
-
this.body = body;
|
935
|
-
}
|
936
|
-
/**
|
937
|
-
* Send a response with the given text, status defaults to 200
|
938
|
-
*/
|
939
|
-
text(body) {
|
940
|
-
this.body = body;
|
941
|
-
this.headers = {
|
942
|
-
...this.headers,
|
943
|
-
"Content-Type": "text/plain"
|
944
|
-
};
|
945
|
-
}
|
946
|
-
/**
|
947
|
-
* Send a response with the given JSON, status defaults to 200
|
948
|
-
*/
|
949
|
-
json(body) {
|
950
|
-
this.body = body;
|
951
|
-
this.headers = {
|
952
|
-
...this.headers,
|
953
|
-
"Content-Type": "application/json"
|
954
|
-
};
|
955
|
-
}
|
956
|
-
/**
|
957
|
-
* Send a response with the given HTML, status defaults to 200
|
958
|
-
*/
|
959
|
-
html(body) {
|
960
|
-
this.body = body;
|
961
|
-
this.headers = {
|
962
|
-
...this.headers,
|
963
|
-
"Content-Type": "text/html"
|
964
|
-
};
|
965
|
-
}
|
966
|
-
/**
|
967
|
-
* Send a response with the given XML, status defaults to 200
|
968
|
-
*/
|
969
|
-
xml(body) {
|
970
|
-
this.body = body;
|
971
|
-
this.headers = {
|
972
|
-
...this.headers,
|
973
|
-
"Content-Type": "application/xml"
|
974
|
-
};
|
975
|
-
}
|
976
|
-
/**
|
977
|
-
* Send a response with the given binary with Content-Type of application/octet-stream header, status defaults to 200
|
978
|
-
*/
|
979
|
-
download(body) {
|
980
|
-
this.body = body;
|
981
|
-
this.headers = {
|
982
|
-
...this.headers,
|
983
|
-
"Content-Type": "application/octet-stream"
|
984
|
-
};
|
985
|
-
}
|
986
|
-
/**
|
987
|
-
* Send a response with the given file, status defaults to 200
|
988
|
-
*/
|
989
|
-
file(pathToFile) {
|
990
|
-
const mimeType = getContentType(pathToFile);
|
991
|
-
const file = nativeFile.file(pathToFile);
|
992
|
-
this.headers = {
|
993
|
-
...this.headers,
|
994
|
-
"Content-Type": mimeType
|
995
|
-
};
|
996
|
-
}
|
997
|
-
/**
|
998
|
-
* 2XX Success
|
999
|
-
*/
|
1000
|
-
/**
|
1001
|
-
* 200 OK
|
1002
|
-
*/
|
1003
|
-
ok(body) {
|
1004
|
-
this.status(200).send(body);
|
1005
|
-
}
|
1006
|
-
/**
|
1007
|
-
* 201 Created
|
1008
|
-
*/
|
1009
|
-
created(body) {
|
1010
|
-
this.status(201).send(body);
|
1011
|
-
}
|
1012
|
-
/**
|
1013
|
-
* 202 Accepted
|
1014
|
-
*/
|
1015
|
-
accepted(body) {
|
1016
|
-
this.status(202).send(body);
|
1017
|
-
}
|
1018
|
-
/**
|
1019
|
-
* 204 No Content
|
1020
|
-
*/
|
1021
|
-
noContent() {
|
1022
|
-
this.status(204).send("");
|
1023
|
-
}
|
1024
|
-
/**
|
1025
|
-
* 206 Partial Content
|
1026
|
-
*/
|
1027
|
-
partialContent(body) {
|
1028
|
-
this.status(206).send(body);
|
1029
|
-
}
|
1030
|
-
/**
|
1031
|
-
* 3XX Redirection
|
1032
|
-
*/
|
1033
|
-
/**
|
1034
|
-
* 300 Multiple Choices
|
1035
|
-
*/
|
1036
|
-
multipleChoices(url) {
|
1037
|
-
this.status(300).setHeader("Location", url);
|
1038
|
-
}
|
1039
|
-
redirect(url) {
|
1040
|
-
this.status(302).setHeader("Location", url);
|
1041
|
-
}
|
1042
|
-
/**
|
1043
|
-
* 301 Moved Permanently
|
1044
|
-
*/
|
1045
|
-
movedPermanently(url) {
|
1046
|
-
this.status(301).setHeader("Location", url);
|
1047
|
-
}
|
1048
|
-
/**
|
1049
|
-
* 302 Found (Temporary Redirect)
|
1050
|
-
*/
|
1051
|
-
found(url) {
|
1052
|
-
this.status(302).setHeader("Location", url);
|
1053
|
-
}
|
1054
|
-
/**
|
1055
|
-
* 303 See Other
|
1056
|
-
*/
|
1057
|
-
seeOther(url) {
|
1058
|
-
this.status(303).setHeader("Location", url);
|
1059
|
-
}
|
1060
|
-
/**
|
1061
|
-
* 304 Not Modified
|
1062
|
-
*/
|
1063
|
-
notModified() {
|
1064
|
-
this.status(304).send("");
|
1065
|
-
}
|
1066
|
-
/**
|
1067
|
-
* 307 Temporary Redirect
|
1068
|
-
*/
|
1069
|
-
temporaryRedirect(url) {
|
1070
|
-
this.status(307).setHeader("Location", url);
|
1071
|
-
}
|
1072
|
-
/**
|
1073
|
-
* 308 Permanent Redirect
|
1074
|
-
*/
|
1075
|
-
permanentRedirect(url) {
|
1076
|
-
this.status(308).setHeader("Location", url);
|
1077
|
-
}
|
1078
|
-
/**
|
1079
|
-
* 4XX Client Errors
|
1080
|
-
*/
|
1081
|
-
/**
|
1082
|
-
* 400 Bad Request
|
1083
|
-
*/
|
1084
|
-
badRequest(body) {
|
1085
|
-
this.status(400).send(body);
|
1086
|
-
}
|
1087
|
-
/**
|
1088
|
-
* 401 Unauthorized
|
1089
|
-
*/
|
1090
|
-
unauthorized(body) {
|
1091
|
-
this.status(401).send(body);
|
1092
|
-
}
|
1093
|
-
/**
|
1094
|
-
* 403 Forbidden
|
1095
|
-
*/
|
1096
|
-
forbidden(body) {
|
1097
|
-
this.status(403).send(body);
|
1098
|
-
}
|
1099
|
-
/**
|
1100
|
-
* 404 Not Found
|
1101
|
-
*/
|
1102
|
-
notFound(body) {
|
1103
|
-
this.status(404).send(body);
|
1104
|
-
}
|
1105
|
-
/**
|
1106
|
-
* 405 Method Not Allowed
|
1107
|
-
*/
|
1108
|
-
methodNotAllowed(body) {
|
1109
|
-
this.status(405).send(body);
|
1110
|
-
}
|
1111
|
-
/**
|
1112
|
-
* 406 Not Acceptable
|
1113
|
-
*/
|
1114
|
-
notAcceptable(body) {
|
1115
|
-
this.status(406).send(body);
|
1116
|
-
}
|
1117
|
-
/**
|
1118
|
-
* 409 Conflict
|
1119
|
-
*/
|
1120
|
-
conflict(body) {
|
1121
|
-
this.status(409).send(body);
|
1122
|
-
}
|
1123
|
-
/**
|
1124
|
-
* 410 Gone
|
1125
|
-
*/
|
1126
|
-
gone(body) {
|
1127
|
-
this.status(410).send(body);
|
1128
|
-
}
|
1129
|
-
/**
|
1130
|
-
* 413 Payload Too Large
|
1131
|
-
*/
|
1132
|
-
payloadTooLarge(body) {
|
1133
|
-
this.status(413).send(body);
|
1134
|
-
}
|
1135
|
-
/**
|
1136
|
-
* 415 Unsupported Media Type
|
1137
|
-
*/
|
1138
|
-
unsupportedMediaType(body) {
|
1139
|
-
this.status(415).send(body);
|
1140
|
-
}
|
1141
|
-
/**
|
1142
|
-
* 422 Unprocessable Entity
|
1143
|
-
*/
|
1144
|
-
unprocessableEntity(body) {
|
1145
|
-
this.status(422).send(body);
|
1146
|
-
}
|
1147
|
-
/**
|
1148
|
-
* 429 Too Many Requests
|
1149
|
-
*/
|
1150
|
-
tooManyRequests(body) {
|
1151
|
-
this.status(429).send(body);
|
1152
|
-
}
|
1153
|
-
/**
|
1154
|
-
* 5XX Server Errors
|
1155
|
-
*/
|
1156
|
-
internalServerError(body) {
|
1157
|
-
this.status(500).send(body);
|
1158
|
-
}
|
1159
|
-
/**
|
1160
|
-
* 501 Not Implemented
|
1161
|
-
*/
|
1162
|
-
notImplemented(body) {
|
1163
|
-
this.status(501).send(body);
|
1164
|
-
}
|
1165
|
-
/**
|
1166
|
-
* 502 Bad Gateway
|
1167
|
-
*/
|
1168
|
-
badGateway(body) {
|
1169
|
-
this.status(502).send(body);
|
1170
|
-
}
|
1171
|
-
/**
|
1172
|
-
* 503 Service Unavailable
|
1173
|
-
*/
|
1174
|
-
serviceUnavailable(body) {
|
1175
|
-
this.status(503).send(body);
|
1176
|
-
}
|
1177
|
-
/**
|
1178
|
-
* 504 Gateway Timeout
|
1179
|
-
*/
|
1180
|
-
gatewayTimeout(body) {
|
1181
|
-
this.status(504).send(body);
|
1182
|
-
}
|
1183
|
-
/**
|
1184
|
-
* 505 HTTP Version Not Supported
|
1185
|
-
*/
|
1186
|
-
httpVersionNotSupported(body) {
|
1187
|
-
this.status(505).send(body);
|
1188
|
-
}
|
1189
|
-
/**
|
1190
|
-
* Set a cookie for the response, does nothing if the cookie middleware is not registered
|
1191
|
-
*/
|
1192
|
-
cookie(_name, _value, _options) {
|
1193
|
-
}
|
1194
|
-
/**
|
1195
|
-
* Clear a cookie for the response, does nothing if the cookie middleware is not registered
|
1196
|
-
*/
|
1197
|
-
clearCookie(_name, _options) {
|
1198
|
-
}
|
1199
|
-
/**
|
1200
|
-
* Get the body of the response
|
1201
|
-
*/
|
1202
|
-
getBody() {
|
1203
|
-
return this.body;
|
1204
|
-
}
|
1205
|
-
};
|
1206
|
-
|
1207
|
-
// src/server/server.ts
|
1208
|
-
var import_glob2 = require("glob");
|
1209
|
-
var import_node_path4 = require("path");
|
1210
|
-
|
1211
|
-
// src/plugins/cookie/cookie.ts
|
1212
|
-
var cookie = (options) => {
|
1213
|
-
const opts = {
|
1214
|
-
secret: options?.secret ?? "",
|
1215
|
-
defaults: {
|
1216
|
-
path: "/",
|
1217
|
-
httpOnly: true,
|
1218
|
-
secure: false,
|
1219
|
-
sameSite: "Lax",
|
1220
|
-
...options?.defaults
|
1221
|
-
},
|
1222
|
-
parse: options?.parse ?? true,
|
1223
|
-
sign: options?.sign ?? false
|
1224
|
-
};
|
1225
|
-
return async (req, res, next) => {
|
1226
|
-
if (opts.parse) {
|
1227
|
-
const rawCookies = parseCookies(req.headers.get("cookie") || "");
|
1228
|
-
req.cookies = {};
|
1229
|
-
for (const [name, value] of Object.entries(rawCookies)) {
|
1230
|
-
if (opts.sign && opts.secret) {
|
1231
|
-
const verified = verifySignedCookie(value, opts.secret);
|
1232
|
-
if (verified !== false) {
|
1233
|
-
req.cookies[name] = verified;
|
1234
|
-
}
|
1235
|
-
continue;
|
1236
|
-
}
|
1237
|
-
req.cookies[name] = value;
|
1238
|
-
}
|
1239
|
-
}
|
1240
|
-
res.cookie = (name, value, cookieOptions) => {
|
1241
|
-
setCookie(res, name, value, { ...opts.defaults, ...cookieOptions }, opts);
|
1242
|
-
};
|
1243
|
-
res.clearCookie = (name, cookieOptions) => {
|
1244
|
-
clearCookie(res, name, { ...opts.defaults, ...cookieOptions });
|
1245
|
-
};
|
1246
|
-
await next();
|
1247
|
-
};
|
1248
|
-
};
|
1249
|
-
function parseCookies(cookieString) {
|
1250
|
-
const cookies = {};
|
1251
|
-
if (!cookieString) return cookies;
|
1252
|
-
const pairs = cookieString.split(";");
|
1253
|
-
for (const pair of pairs) {
|
1254
|
-
const [name, value] = pair.trim().split("=");
|
1255
|
-
if (name && value) {
|
1256
|
-
cookies[decodeURIComponent(name)] = decodeURIComponent(value);
|
1257
|
-
}
|
1258
|
-
}
|
1259
|
-
return cookies;
|
1260
|
-
}
|
1261
|
-
function setCookie(res, name, value, options, middlewareOptions) {
|
1262
|
-
let cookieValue = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
1263
|
-
if (options.domain) {
|
1264
|
-
cookieValue += `; Domain=${options.domain}`;
|
1265
|
-
}
|
1266
|
-
if (options.path) {
|
1267
|
-
cookieValue += `; Path=${options.path}`;
|
1268
|
-
}
|
1269
|
-
if (options.expires) {
|
1270
|
-
cookieValue += `; Expires=${options.expires.toUTCString()}`;
|
1271
|
-
}
|
1272
|
-
if (options.maxAge) {
|
1273
|
-
cookieValue += `; Max-Age=${options.maxAge}`;
|
1274
|
-
}
|
1275
|
-
if (options.secure) {
|
1276
|
-
cookieValue += "; Secure";
|
1277
|
-
}
|
1278
|
-
if (options.httpOnly) {
|
1279
|
-
cookieValue += "; HttpOnly";
|
1280
|
-
}
|
1281
|
-
if (options.sameSite) {
|
1282
|
-
cookieValue += `; SameSite=${options.sameSite}`;
|
1283
|
-
}
|
1284
|
-
if (options.priority) {
|
1285
|
-
cookieValue += `; Priority=${options.priority}`;
|
1286
|
-
}
|
1287
|
-
if (middlewareOptions.sign && middlewareOptions.secret) {
|
1288
|
-
cookieValue = signCookie(cookieValue, middlewareOptions.secret);
|
1289
|
-
}
|
1290
|
-
const existingCookies = res.headers["set-cookie"] || "";
|
1291
|
-
const newCookies = existingCookies ? `${existingCookies}, ${cookieValue}` : cookieValue;
|
1292
|
-
res.setHeader("Set-Cookie", newCookies);
|
1293
|
-
}
|
1294
|
-
function clearCookie(res, name, options) {
|
1295
|
-
const clearOptions = {
|
1296
|
-
...options,
|
1297
|
-
expires: /* @__PURE__ */ new Date(0),
|
1298
|
-
maxAge: 0
|
1299
|
-
};
|
1300
|
-
setCookie(res, name, "", clearOptions, {
|
1301
|
-
secret: "",
|
1302
|
-
defaults: {},
|
1303
|
-
parse: true,
|
1304
|
-
sign: false
|
1305
|
-
});
|
1306
|
-
}
|
1307
|
-
function signCookie(value, secret) {
|
1308
|
-
let hash = 0;
|
1309
|
-
for (let i = 0; i < value.length; i++) {
|
1310
|
-
const char = value.charCodeAt(i);
|
1311
|
-
hash = (hash << 5) - hash + char;
|
1312
|
-
hash = hash & hash;
|
1313
|
-
}
|
1314
|
-
const signature = Math.abs(hash).toString(36);
|
1315
|
-
return `${value}.${signature}`;
|
1316
|
-
}
|
1317
|
-
function verifySignedCookie(value, secret) {
|
1318
|
-
const parts = value.split(".");
|
1319
|
-
if (parts.length !== 2) return false;
|
1320
|
-
const [cookieValue, signature] = parts;
|
1321
|
-
const expectedSignature = signCookie(cookieValue, secret).split(".")[1];
|
1322
|
-
return signature === expectedSignature ? cookieValue : false;
|
1323
|
-
}
|
1324
|
-
|
1325
|
-
// src/logger/logger.ts
|
1326
|
-
var import_pino = __toESM(require("pino"), 1);
|
1327
|
-
var createBaseLogger = () => {
|
1328
|
-
const baseOptions = {
|
1329
|
-
level: "info",
|
1330
|
-
formatters: {
|
1331
|
-
level: (label) => {
|
1332
|
-
return { level: label };
|
1333
|
-
}
|
1334
|
-
}
|
1335
|
-
};
|
1336
|
-
return (0, import_pino.default)(baseOptions);
|
1337
|
-
};
|
1338
|
-
var logger = createBaseLogger();
|
1339
|
-
|
1340
|
-
// src/plugins/log/log.ts
|
1341
|
-
var log = (options) => {
|
1342
|
-
return async (req, res, next) => {
|
1343
|
-
try {
|
1344
|
-
const body = req.body;
|
1345
|
-
if (options?.logRequest ?? true) {
|
1346
|
-
logger.info({
|
1347
|
-
type: "request",
|
1348
|
-
requestId: req.id,
|
1349
|
-
method: options?.requestPayload?.method ?? true ? req.method : void 0,
|
1350
|
-
url: options?.requestPayload?.url ?? true ? req.url : void 0,
|
1351
|
-
ip: options?.requestPayload?.ip ?? true ? req.ip : void 0,
|
1352
|
-
headers: options?.requestPayload?.headers ?? true ? req.headers : void 0,
|
1353
|
-
body: options?.requestPayload?.body ?? false ? returnIfObjectOrString(body) : void 0
|
1354
|
-
});
|
1355
|
-
}
|
1356
|
-
await next();
|
1357
|
-
if (options?.logResponse ?? true) {
|
1358
|
-
logger.info({
|
1359
|
-
type: "response",
|
1360
|
-
requestId: req.id,
|
1361
|
-
status: options?.responsePayload?.status ?? res.responseStatus,
|
1362
|
-
body: options?.responsePayload?.body ?? false ? returnIfObjectOrString(res.getBody()) : void 0,
|
1363
|
-
headers: options?.responsePayload?.headers ?? false ? res.headers : void 0
|
1364
|
-
});
|
1365
|
-
}
|
1366
|
-
} catch (error) {
|
1367
|
-
logger.error(error);
|
1368
|
-
throw error;
|
1369
|
-
}
|
1370
|
-
};
|
1371
|
-
};
|
1372
|
-
function returnIfObjectOrString(value) {
|
1373
|
-
if (typeof value === "string") {
|
1374
|
-
return value;
|
1375
|
-
}
|
1376
|
-
if (value && typeof value === "object" && value.constructor === Object) {
|
1377
|
-
return value;
|
1378
|
-
}
|
1379
|
-
return;
|
1380
|
-
}
|
1381
|
-
|
1382
|
-
// src/plugins/rate_limiter/in_memory_storage.ts
|
1383
|
-
var InMemoryStorage = class {
|
1384
|
-
storage = /* @__PURE__ */ new Map();
|
1385
|
-
windowMs;
|
1386
|
-
constructor(windowMs) {
|
1387
|
-
this.windowMs = windowMs;
|
1388
|
-
}
|
1389
|
-
async set(key, value) {
|
1390
|
-
this.storage.set(key, value);
|
1391
|
-
setTimeout(() => {
|
1392
|
-
this.storage.delete(key);
|
1393
|
-
}, this.windowMs);
|
1394
|
-
}
|
1395
|
-
async get(key) {
|
1396
|
-
const entry = this.storage.get(key);
|
1397
|
-
if (!entry) {
|
1398
|
-
return 0;
|
1399
|
-
}
|
1400
|
-
return entry;
|
1401
|
-
}
|
1402
|
-
async delete(key) {
|
1403
|
-
this.storage.delete(key);
|
1404
|
-
}
|
1405
|
-
};
|
1406
|
-
|
1407
|
-
// src/plugins/rate_limiter/rate_limiter.ts
|
1408
|
-
var rateLimiter = (keyOptions, storageOptions) => {
|
1409
|
-
const baseKeyOptions = {
|
1410
|
-
type: "ip",
|
1411
|
-
limit: 100,
|
1412
|
-
message: "ERR_RATE_LIMIT_EXCEEDED",
|
1413
|
-
statusCode: 429,
|
1414
|
-
...keyOptions
|
1415
|
-
};
|
1416
|
-
const baseStorageOptions = {
|
1417
|
-
type: "memory",
|
1418
|
-
...storageOptions
|
1419
|
-
};
|
1420
|
-
if (baseStorageOptions.type === "memory" && !baseStorageOptions.windowMs) {
|
1421
|
-
baseStorageOptions.windowMs = 6e4;
|
1422
|
-
}
|
1423
|
-
const storage = baseStorageOptions.type === "memory" ? new InMemoryStorage(baseStorageOptions.windowMs) : {
|
1424
|
-
get: baseStorageOptions.get,
|
1425
|
-
set: baseStorageOptions.set
|
1426
|
-
};
|
1427
|
-
return async (req, res, next) => {
|
1428
|
-
const key = baseKeyOptions.type === "ip" ? req.ip : baseKeyOptions.key(req);
|
1429
|
-
const value = await storage.get(key);
|
1430
|
-
if (value >= baseKeyOptions.limit) {
|
1431
|
-
return res.status(baseKeyOptions.statusCode).json({
|
1432
|
-
message: baseKeyOptions.message
|
1433
|
-
});
|
1434
|
-
}
|
1435
|
-
await storage.set(key, value + 1);
|
1436
|
-
return next();
|
1437
|
-
};
|
1438
|
-
};
|
1439
|
-
|
1440
|
-
// src/runtime/native_server/server_utils.ts
|
1441
|
-
var executeMiddlewareChain = async (middlewares, handler, req, res = new Response2()) => {
|
1442
|
-
let currentIndex = 0;
|
1443
|
-
if (!middlewares.length) {
|
1444
|
-
await handler(req, res);
|
1445
|
-
return res;
|
1446
|
-
}
|
1447
|
-
const next = async () => {
|
1448
|
-
currentIndex++;
|
1449
|
-
if (currentIndex >= middlewares.length) {
|
1450
|
-
await handler(req, res);
|
1451
|
-
return;
|
1452
|
-
}
|
1453
|
-
const middleware2 = middlewares[currentIndex];
|
1454
|
-
await middleware2(req, res, next);
|
1455
|
-
};
|
1456
|
-
const firstMiddleware = middlewares[0];
|
1457
|
-
await firstMiddleware(req, res, next);
|
1458
|
-
return res;
|
1459
|
-
};
|
1460
|
-
var canHaveBody = (method) => {
|
1461
|
-
if (!method) {
|
1462
|
-
return true;
|
1463
|
-
}
|
1464
|
-
return ["post", "put", "patch", "delete"].includes(method.toLowerCase());
|
1465
|
-
};
|
1466
|
-
|
1467
|
-
// src/plugins/body_parser/body_parser.ts
|
1468
|
-
var bodyParser = () => {
|
1469
|
-
return async (req, _res, next) => {
|
1470
|
-
if (!canHaveBody(req.method)) {
|
1471
|
-
return next();
|
1472
|
-
}
|
1473
|
-
req.rawBody = await req.arrayBuffer();
|
1474
|
-
Object.defineProperty(req, "body", {
|
1475
|
-
value: void 0,
|
1476
|
-
writable: true,
|
1477
|
-
configurable: true,
|
1478
|
-
enumerable: true
|
1479
|
-
});
|
1480
|
-
return next();
|
1481
|
-
};
|
1482
|
-
};
|
1483
|
-
|
1484
|
-
// src/plugins/cors/cors.ts
|
1485
|
-
var cors = (options) => {
|
1486
|
-
const opts = {
|
1487
|
-
origin: "*",
|
1488
|
-
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
|
1489
|
-
allowedHeaders: "",
|
1490
|
-
exposedHeaders: "",
|
1491
|
-
credentials: false,
|
1492
|
-
maxAge: void 0,
|
1493
|
-
preflightContinue: false,
|
1494
|
-
optionsSuccessStatus: 204,
|
1495
|
-
...options
|
1496
|
-
};
|
1497
|
-
return async (req, res, next) => {
|
1498
|
-
const requestOrigin = req.headers.get("origin") || "";
|
1499
|
-
if (req.method === "OPTIONS") {
|
1500
|
-
return handlePreflightRequest(req, res, opts, requestOrigin, next);
|
1501
|
-
}
|
1502
|
-
handleRegularRequest(req, res, opts, requestOrigin);
|
1503
|
-
await next();
|
1504
|
-
};
|
1505
|
-
};
|
1506
|
-
function handlePreflightRequest(_req, res, opts, requestOrigin, next) {
|
1507
|
-
const allowOrigin = determineOrigin(opts, requestOrigin);
|
1508
|
-
if (!allowOrigin) {
|
1509
|
-
res.forbidden("CORS origin not allowed");
|
1510
|
-
return;
|
1511
|
-
}
|
1512
|
-
setCorsHeaders(res, opts, allowOrigin);
|
1513
|
-
if (opts.preflightContinue) {
|
1514
|
-
next();
|
1515
|
-
return;
|
1516
|
-
}
|
1517
|
-
res.status(opts.optionsSuccessStatus || 204);
|
1518
|
-
res.send("");
|
1519
|
-
}
|
1520
|
-
function handleRegularRequest(_req, res, opts, requestOrigin) {
|
1521
|
-
const allowOrigin = determineOrigin(opts, requestOrigin);
|
1522
|
-
if (!allowOrigin) {
|
1523
|
-
return;
|
1524
|
-
}
|
1525
|
-
setCorsHeaders(res, opts, allowOrigin);
|
1526
|
-
}
|
1527
|
-
function determineOrigin(opts, requestOrigin) {
|
1528
|
-
if (typeof opts.origin === "string") {
|
1529
|
-
return opts.origin;
|
1530
|
-
}
|
1531
|
-
if (Array.isArray(opts.origin)) {
|
1532
|
-
const matchedOrigin = opts.origin.find(
|
1533
|
-
(origin) => typeof origin === "string" ? origin === requestOrigin : origin instanceof RegExp && origin.test(requestOrigin)
|
1534
|
-
);
|
1535
|
-
return typeof matchedOrigin === "string" ? matchedOrigin : false;
|
1536
|
-
}
|
1537
|
-
return "*";
|
1538
|
-
}
|
1539
|
-
function setCorsHeaders(res, opts, allowOrigin) {
|
1540
|
-
res.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
1541
|
-
if (opts.credentials) {
|
1542
|
-
res.setHeader("Access-Control-Allow-Credentials", "true");
|
1543
|
-
}
|
1544
|
-
if (opts.exposedHeaders && opts.exposedHeaders !== "") {
|
1545
|
-
const exposedHeaders = Array.isArray(opts.exposedHeaders) ? opts.exposedHeaders.join(",") : opts.exposedHeaders;
|
1546
|
-
res.setHeader("Access-Control-Expose-Headers", exposedHeaders);
|
1547
|
-
}
|
1548
|
-
if (opts.allowedHeaders && opts.allowedHeaders !== "") {
|
1549
|
-
const allowedHeaders = Array.isArray(opts.allowedHeaders) ? opts.allowedHeaders.join(",") : opts.allowedHeaders;
|
1550
|
-
res.setHeader("Access-Control-Allow-Headers", allowedHeaders);
|
1551
|
-
}
|
1552
|
-
const methodsStr = Array.isArray(opts.methods) ? opts.methods.join(",") : opts.methods;
|
1553
|
-
res.setHeader("Access-Control-Allow-Methods", String(methodsStr || ""));
|
1554
|
-
if (typeof opts.maxAge === "number") {
|
1555
|
-
res.setHeader("Access-Control-Max-Age", opts.maxAge.toString());
|
1556
|
-
}
|
1557
|
-
}
|
1558
|
-
|
1559
|
-
// src/plugins/file/file.ts
|
1560
|
-
var import_node_os = require("os");
|
1561
|
-
var import_node_path3 = require("path");
|
1562
|
-
|
1563
|
-
// src/errors/file_too_large.ts
|
1564
|
-
var FileTooLargeError = class extends BaldaError {
|
1565
|
-
constructor(filename, size, maxSize) {
|
1566
|
-
super(
|
1567
|
-
`FILE_TOO_LARGE: "${filename}" is too large. Max size is ${maxSize} bytes, but got ${size} bytes`
|
1568
|
-
);
|
1569
|
-
}
|
1570
|
-
};
|
1571
|
-
|
1572
|
-
// src/plugins/file/file.ts
|
1573
|
-
var fileParser = (options) => {
|
1574
|
-
return async (req, res, next) => {
|
1575
|
-
const tmpPaths = [];
|
1576
|
-
try {
|
1577
|
-
const contentType = req.headers.get("content-type") ?? req.headers.get("Content-Type");
|
1578
|
-
if (!contentType || !contentType.startsWith("multipart/form-data")) {
|
1579
|
-
return next();
|
1580
|
-
}
|
1581
|
-
if (!req.rawBody) {
|
1582
|
-
return next();
|
1583
|
-
}
|
1584
|
-
const boundaryMatch = contentType.match(/boundary=(.*)(;|$)/i);
|
1585
|
-
if (!boundaryMatch) {
|
1586
|
-
return next();
|
1587
|
-
}
|
1588
|
-
const boundary = boundaryMatch[1].replace(/(^\s*"?|"?\s*$)/g, "");
|
1589
|
-
const bodyBuf = new Uint8Array(req.rawBody);
|
1590
|
-
const boundaryBuf = new TextEncoder().encode(`--${boundary}`);
|
1591
|
-
const CRLFCRLF = new Uint8Array([13, 10, 13, 10]);
|
1592
|
-
const parts = [];
|
1593
|
-
const indexOfSub = (haystack, needle, from = 0) => {
|
1594
|
-
outer: for (let i = from; i <= haystack.length - needle.length; i++) {
|
1595
|
-
for (let j = 0; j < needle.length; j++) {
|
1596
|
-
if (haystack[i + j] !== needle[j]) continue outer;
|
1597
|
-
}
|
1598
|
-
return i;
|
1599
|
-
}
|
1600
|
-
return -1;
|
1601
|
-
};
|
1602
|
-
let start = indexOfSub(bodyBuf, boundaryBuf);
|
1603
|
-
while (start !== -1) {
|
1604
|
-
start += boundaryBuf.length;
|
1605
|
-
if (bodyBuf[start] === 45 && bodyBuf[start + 1] === 45) {
|
1606
|
-
break;
|
1607
|
-
}
|
1608
|
-
if (bodyBuf[start] === 13 && bodyBuf[start + 1] === 10) {
|
1609
|
-
start += 2;
|
1610
|
-
}
|
1611
|
-
const headerEnd = indexOfSub(bodyBuf, CRLFCRLF, start);
|
1612
|
-
if (headerEnd === -1) {
|
1613
|
-
break;
|
1614
|
-
}
|
1615
|
-
const headersBuf = bodyBuf.subarray(start, headerEnd);
|
1616
|
-
const headers = new TextDecoder().decode(headersBuf);
|
1617
|
-
const dataStart = headerEnd + CRLFCRLF.length;
|
1618
|
-
const nextBoundary = indexOfSub(bodyBuf, boundaryBuf, dataStart);
|
1619
|
-
if (nextBoundary === -1) {
|
1620
|
-
break;
|
1621
|
-
}
|
1622
|
-
let dataEnd = nextBoundary - 1;
|
1623
|
-
if (bodyBuf[dataEnd] === 10) {
|
1624
|
-
dataEnd--;
|
1625
|
-
}
|
1626
|
-
if (bodyBuf[dataEnd] === 13) {
|
1627
|
-
dataEnd--;
|
1628
|
-
}
|
1629
|
-
const data = bodyBuf.subarray(dataStart, dataEnd + 1);
|
1630
|
-
parts.push({ headers, data });
|
1631
|
-
start = nextBoundary;
|
1632
|
-
}
|
1633
|
-
const files = [];
|
1634
|
-
const fields = {};
|
1635
|
-
for (const part of parts) {
|
1636
|
-
const disposition = part.headers.split("\r\n").find((h) => h.toLowerCase().startsWith("content-disposition:"));
|
1637
|
-
if (!disposition) {
|
1638
|
-
continue;
|
1639
|
-
}
|
1640
|
-
const formNameMatch = disposition.match(/name="([^"]+)"/);
|
1641
|
-
if (!formNameMatch) {
|
1642
|
-
continue;
|
1643
|
-
}
|
1644
|
-
const formName = formNameMatch[1];
|
1645
|
-
const filenameMatch = disposition.match(/filename="([^"]*)"/);
|
1646
|
-
const originalName = filenameMatch ? filenameMatch[1] : "";
|
1647
|
-
const isFile = Boolean(originalName);
|
1648
|
-
if (isFile) {
|
1649
|
-
if (options?.maxFileSize && part.data.length > options.maxFileSize) {
|
1650
|
-
return res.badRequest({
|
1651
|
-
...errorFactory(
|
1652
|
-
new FileTooLargeError(
|
1653
|
-
originalName,
|
1654
|
-
part.data.length,
|
1655
|
-
options.maxFileSize
|
1656
|
-
)
|
1657
|
-
)
|
1658
|
-
});
|
1659
|
-
}
|
1660
|
-
const contentTypeHeader = part.headers.split("\r\n").find((h) => h.toLowerCase().startsWith("content-type:"));
|
1661
|
-
const mimeType = contentTypeHeader ? contentTypeHeader.split(":")[1].trim() : "application/octet-stream";
|
1662
|
-
const extension = (0, import_node_path3.extname)(originalName);
|
1663
|
-
const tmpPath = (0, import_node_path3.join)((0, import_node_os.tmpdir)(), `${randomString(10)}${extension}`);
|
1664
|
-
await nativeFs.writeFile(tmpPath, part.data);
|
1665
|
-
tmpPaths.push(tmpPath);
|
1666
|
-
files.push({
|
1667
|
-
formName,
|
1668
|
-
mimeType,
|
1669
|
-
size: part.data.length,
|
1670
|
-
tmpPath,
|
1671
|
-
originalName
|
1672
|
-
});
|
1673
|
-
} else {
|
1674
|
-
fields[formName] = new TextDecoder().decode(part.data);
|
1675
|
-
}
|
1676
|
-
}
|
1677
|
-
req.files = files;
|
1678
|
-
req.body = fields;
|
1679
|
-
await next();
|
1680
|
-
await cleanupTmpFiles(tmpPaths);
|
1681
|
-
} catch (error) {
|
1682
|
-
await cleanupTmpFiles(tmpPaths);
|
1683
|
-
throw error;
|
1684
|
-
}
|
1685
|
-
};
|
1686
|
-
};
|
1687
|
-
async function cleanupTmpFiles(paths) {
|
1688
|
-
await Promise.allSettled(paths.map((path) => nativeFs.unlink(path)));
|
1689
|
-
}
|
1690
|
-
function randomString(length) {
|
1691
|
-
return Math.random().toString(36).substring(2, 2 + length);
|
1692
|
-
}
|
1693
|
-
|
1694
|
-
// src/plugins/helmet/helmet.ts
|
1695
|
-
var helmet = (options) => {
|
1696
|
-
const opts = {
|
1697
|
-
dnsPrefetchControl: true,
|
1698
|
-
frameguard: { action: "SAMEORIGIN" },
|
1699
|
-
hsts: { maxAge: 15552e3, includeSubDomains: true, preload: false },
|
1700
|
-
contentTypeOptions: true,
|
1701
|
-
ieNoOpen: true,
|
1702
|
-
xssFilter: true,
|
1703
|
-
referrerPolicy: "no-referrer",
|
1704
|
-
crossOriginResourcePolicy: "same-origin",
|
1705
|
-
crossOriginOpenerPolicy: "same-origin",
|
1706
|
-
crossOriginEmbedderPolicy: "require-corp",
|
1707
|
-
contentSecurityPolicy: false,
|
1708
|
-
...options
|
1709
|
-
};
|
1710
|
-
return async (_req, res, next) => {
|
1711
|
-
if (opts.dnsPrefetchControl) {
|
1712
|
-
res.setHeader("X-DNS-Prefetch-Control", "off");
|
1713
|
-
}
|
1714
|
-
if (opts.frameguard) {
|
1715
|
-
let action = "SAMEORIGIN";
|
1716
|
-
if (typeof opts.frameguard === "object") {
|
1717
|
-
action = opts.frameguard.action;
|
1718
|
-
}
|
1719
|
-
res.setHeader("X-Frame-Options", action);
|
1720
|
-
}
|
1721
|
-
if (opts.hsts) {
|
1722
|
-
let hstsRaw = {};
|
1723
|
-
if (typeof opts.hsts === "object") {
|
1724
|
-
hstsRaw = opts.hsts;
|
1725
|
-
}
|
1726
|
-
const maxAge = hstsRaw.maxAge !== void 0 ? hstsRaw.maxAge : 15552e3;
|
1727
|
-
const includeSubDomains = hstsRaw.includeSubDomains !== void 0 ? hstsRaw.includeSubDomains : true;
|
1728
|
-
const preload = hstsRaw.preload !== void 0 ? hstsRaw.preload : false;
|
1729
|
-
let hstsValue = `max-age=${maxAge}`;
|
1730
|
-
if (includeSubDomains !== false) {
|
1731
|
-
hstsValue += "; includeSubDomains";
|
1732
|
-
}
|
1733
|
-
if (preload) {
|
1734
|
-
hstsValue += "; preload";
|
1735
|
-
}
|
1736
|
-
res.setHeader("Strict-Transport-Security", hstsValue);
|
1737
|
-
}
|
1738
|
-
if (opts.contentTypeOptions) {
|
1739
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
1740
|
-
}
|
1741
|
-
if (opts.ieNoOpen) {
|
1742
|
-
res.setHeader("X-Download-Options", "noopen");
|
1743
|
-
}
|
1744
|
-
if (opts.xssFilter) {
|
1745
|
-
res.setHeader("X-XSS-Protection", "0");
|
1746
|
-
}
|
1747
|
-
if (opts.referrerPolicy) {
|
1748
|
-
res.setHeader("Referrer-Policy", opts.referrerPolicy);
|
1749
|
-
}
|
1750
|
-
if (opts.crossOriginResourcePolicy) {
|
1751
|
-
res.setHeader(
|
1752
|
-
"Cross-Origin-Resource-Policy",
|
1753
|
-
opts.crossOriginResourcePolicy
|
1754
|
-
);
|
1755
|
-
}
|
1756
|
-
if (opts.crossOriginOpenerPolicy) {
|
1757
|
-
res.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
1758
|
-
}
|
1759
|
-
if (opts.crossOriginEmbedderPolicy) {
|
1760
|
-
res.setHeader(
|
1761
|
-
"Cross-Origin-Embedder-Policy",
|
1762
|
-
opts.crossOriginEmbedderPolicy
|
1763
|
-
);
|
1764
|
-
}
|
1765
|
-
if (opts.contentSecurityPolicy) {
|
1766
|
-
res.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
1767
|
-
}
|
1768
|
-
await next();
|
1769
|
-
};
|
1770
|
-
};
|
1771
|
-
|
1772
|
-
// src/errors/json_not_valid.ts
|
1773
|
-
var JsonNotValidError = class extends BaldaError {
|
1774
|
-
constructor(json2) {
|
1775
|
-
super(`JSON_NOT_VALID: "${JSON.stringify(json2)}" is not a valid JSON`);
|
1776
|
-
}
|
1777
|
-
};
|
1778
|
-
|
1779
|
-
// src/plugins/json/json.ts
|
1780
|
-
var json = (options) => {
|
1781
|
-
return async (req, res, next) => {
|
1782
|
-
if (!isJsonRequest(req) || !canHaveBody(req.method)) {
|
1783
|
-
return next();
|
1784
|
-
}
|
1785
|
-
const sizeLimit = options?.sizeLimit ?? 5 * 1024 * 1024;
|
1786
|
-
const arrayBuffer = req.rawBody;
|
1787
|
-
if (!arrayBuffer) {
|
1788
|
-
if (options?.parseEmptyBodyAsObject) {
|
1789
|
-
req.body = {};
|
1790
|
-
}
|
1791
|
-
return next();
|
1792
|
-
}
|
1793
|
-
const byteLength = arrayBuffer.byteLength;
|
1794
|
-
if (!byteLength) {
|
1795
|
-
if (options?.parseEmptyBodyAsObject) {
|
1796
|
-
req.body = {};
|
1797
|
-
}
|
1798
|
-
return next();
|
1799
|
-
}
|
1800
|
-
if (byteLength > sizeLimit) {
|
1801
|
-
const customErrorMessage = {
|
1802
|
-
status: 413,
|
1803
|
-
message: "ERR_REQUEST_BODY_TOO_LARGE",
|
1804
|
-
...options?.customErrorMessage
|
1805
|
-
};
|
1806
|
-
return res.status(customErrorMessage.status).json({
|
1807
|
-
error: customErrorMessage.message
|
1808
|
-
});
|
1809
|
-
}
|
1810
|
-
try {
|
1811
|
-
const encoding = options?.encoding ?? "utf-8";
|
1812
|
-
const decodedBody = new TextDecoder(encoding).decode(arrayBuffer);
|
1813
|
-
req.body = JSON.parse(decodedBody);
|
1814
|
-
} catch (error) {
|
1815
|
-
if (error instanceof SyntaxError) {
|
1816
|
-
return res.badRequest({
|
1817
|
-
...errorFactory(new JsonNotValidError("Invalid JSON syntax"))
|
1818
|
-
});
|
1819
|
-
}
|
1820
|
-
return res.badRequest({
|
1821
|
-
...errorFactory(new JsonNotValidError("Invalid request body encoding"))
|
1822
|
-
});
|
1823
|
-
}
|
1824
|
-
await next();
|
1825
|
-
};
|
1826
|
-
};
|
1827
|
-
function isJsonRequest(req) {
|
1828
|
-
const contentType = getContentType2(req);
|
1829
|
-
if (!contentType) {
|
1830
|
-
return false;
|
1831
|
-
}
|
1832
|
-
const mimeType = parseMimeType(contentType);
|
1833
|
-
return mimeType === "application/json";
|
1834
|
-
}
|
1835
|
-
function getContentType2(req) {
|
1836
|
-
const contentType = req.headers.get("content-type") ?? req.headers.get("Content-Type");
|
1837
|
-
if (!contentType) {
|
1838
|
-
return null;
|
1839
|
-
}
|
1840
|
-
if (Array.isArray(contentType)) {
|
1841
|
-
return contentType[0] || null;
|
1842
|
-
}
|
1843
|
-
return contentType;
|
1844
|
-
}
|
1845
|
-
function parseMimeType(contentType) {
|
1846
|
-
const trimmed = contentType.trim();
|
1847
|
-
const semicolonIndex = trimmed.indexOf(";");
|
1848
|
-
if (semicolonIndex === -1) {
|
1849
|
-
return trimmed.toLowerCase();
|
1850
|
-
}
|
1851
|
-
return trimmed.substring(0, semicolonIndex).trim().toLowerCase();
|
1852
|
-
}
|
1853
|
-
|
1854
|
-
// src/plugins/swagger/swagger.ts
|
1855
|
-
var swagger = (globalOptions) => {
|
1856
|
-
let swaggerOptions = {
|
1857
|
-
type: "standard",
|
1858
|
-
path: "/docs",
|
1859
|
-
title: "Balda API Documentation",
|
1860
|
-
description: "API Documentation from the Balda Framework",
|
1861
|
-
version: "1.0.0",
|
1862
|
-
servers: ["http://localhost"],
|
1863
|
-
security: [],
|
1864
|
-
tags: [],
|
1865
|
-
components: {},
|
1866
|
-
securitySchemes: {},
|
1867
|
-
models: {}
|
1868
|
-
};
|
1869
|
-
if (typeof globalOptions !== "boolean") {
|
1870
|
-
swaggerOptions = {
|
1871
|
-
...swaggerOptions,
|
1872
|
-
...globalOptions
|
1873
|
-
};
|
1874
|
-
}
|
1875
|
-
const spec = generateOpenAPISpec(swaggerOptions);
|
1876
|
-
const uiPath = `${swaggerOptions.path}`;
|
1877
|
-
const jsonPath = `${uiPath}/json`;
|
1878
|
-
const uiContent = swaggerOptions.type === "redoc" ? generateRedocUI(jsonPath, swaggerOptions) : swaggerOptions.type === "rapidoc" ? generateRapiDocUI(jsonPath, swaggerOptions) : generateSwaggerUI(jsonPath, swaggerOptions);
|
1879
|
-
router.addOrUpdate("GET", uiPath, [], (_req, res) => {
|
1880
|
-
res.html(uiContent);
|
1881
|
-
});
|
1882
|
-
router.addOrUpdate("GET", jsonPath, [], (_req, res) => {
|
1883
|
-
res.json(spec);
|
1884
|
-
});
|
1885
|
-
};
|
1886
|
-
function generateOpenAPISpec(globalOptions) {
|
1887
|
-
const routes = router.getRoutes();
|
1888
|
-
const paths = {};
|
1889
|
-
const components = {
|
1890
|
-
...globalOptions.components,
|
1891
|
-
securitySchemes: globalOptions.securitySchemes || {},
|
1892
|
-
schemas: globalOptions.models ? {
|
1893
|
-
...globalOptions.components?.schemas || {},
|
1894
|
-
...globalOptions.models
|
1895
|
-
} : globalOptions.components?.schemas ? { ...globalOptions.components.schemas } : void 0
|
1896
|
-
};
|
1897
|
-
for (const route of routes) {
|
1898
|
-
const swaggerOptions = route.swaggerOptions;
|
1899
|
-
if (swaggerOptions?.excludeFromSwagger) continue;
|
1900
|
-
if (!paths[route.path]) paths[route.path] = {};
|
1901
|
-
const method = route.method.toLowerCase();
|
1902
|
-
const operation = {
|
1903
|
-
summary: swaggerOptions?.name || `${method.toUpperCase()} ${route.path}`,
|
1904
|
-
description: swaggerOptions?.description || "",
|
1905
|
-
tags: swaggerOptions?.service ? [swaggerOptions.service] : [],
|
1906
|
-
deprecated: swaggerOptions?.deprecated || false
|
1907
|
-
};
|
1908
|
-
let parameters = [];
|
1909
|
-
if (swaggerOptions?.query) {
|
1910
|
-
if (swaggerOptions.query.type === "object" && swaggerOptions.query.properties) {
|
1911
|
-
for (const [name, schema] of Object.entries(
|
1912
|
-
swaggerOptions.query.properties
|
1913
|
-
)) {
|
1914
|
-
parameters.push({
|
1915
|
-
name,
|
1916
|
-
in: "query",
|
1917
|
-
required: Array.isArray(swaggerOptions.query.required) ? swaggerOptions.query.required.includes(name) : false,
|
1918
|
-
schema: typeboxToOpenAPI(schema)
|
1919
|
-
});
|
1920
|
-
}
|
1921
|
-
}
|
1922
|
-
}
|
1923
|
-
if (swaggerOptions && swaggerOptions.params) {
|
1924
|
-
parameters = parameters.concat(
|
1925
|
-
extractPathParams(route.path, swaggerOptions.params)
|
1926
|
-
);
|
1927
|
-
} else {
|
1928
|
-
parameters = parameters.concat(extractPathParams(route.path));
|
1929
|
-
}
|
1930
|
-
if (parameters.length > 0) {
|
1931
|
-
operation.parameters = parameters;
|
1932
|
-
}
|
1933
|
-
if (swaggerOptions?.requestBody) {
|
1934
|
-
let routeBodyContentType = "application/json";
|
1935
|
-
if (swaggerOptions.bodyType === "form-data") {
|
1936
|
-
routeBodyContentType = "multipart/form-data";
|
1937
|
-
} else if (swaggerOptions.bodyType === "urlencoded") {
|
1938
|
-
routeBodyContentType = "application/x-www-form-urlencoded";
|
1939
|
-
}
|
1940
|
-
operation.requestBody = {
|
1941
|
-
content: {
|
1942
|
-
[routeBodyContentType]: {
|
1943
|
-
schema: typeboxToOpenAPI(swaggerOptions.requestBody)
|
1944
|
-
}
|
1945
|
-
},
|
1946
|
-
required: true
|
1947
|
-
};
|
1948
|
-
} else if (swaggerOptions?.bodyType && (swaggerOptions.bodyType.includes("form-data") || swaggerOptions.bodyType.includes("urlencoded"))) {
|
1949
|
-
operation.requestBody = {
|
1950
|
-
content: {
|
1951
|
-
[swaggerOptions.bodyType]: {
|
1952
|
-
schema: { type: "object" }
|
1953
|
-
}
|
1954
|
-
},
|
1955
|
-
required: true
|
1956
|
-
};
|
1957
|
-
}
|
1958
|
-
operation.responses = {};
|
1959
|
-
if (swaggerOptions?.responses) {
|
1960
|
-
for (const [statusCode, schema] of Object.entries(
|
1961
|
-
swaggerOptions.responses
|
1962
|
-
)) {
|
1963
|
-
operation.responses[statusCode] = {
|
1964
|
-
description: `Response for ${statusCode}`,
|
1965
|
-
content: {
|
1966
|
-
"application/json": {
|
1967
|
-
schema: typeboxToOpenAPI(schema)
|
1968
|
-
}
|
1969
|
-
}
|
1970
|
-
};
|
1971
|
-
}
|
1972
|
-
}
|
1973
|
-
if (swaggerOptions?.errors) {
|
1974
|
-
for (const [statusCode, schema] of Object.entries(
|
1975
|
-
swaggerOptions.errors
|
1976
|
-
)) {
|
1977
|
-
operation.responses[statusCode] = {
|
1978
|
-
description: `Error response for ${statusCode}`,
|
1979
|
-
content: {
|
1980
|
-
"application/json": {
|
1981
|
-
schema: typeboxToOpenAPI(schema)
|
1982
|
-
}
|
1983
|
-
}
|
1984
|
-
};
|
1985
|
-
}
|
1986
|
-
}
|
1987
|
-
if (Object.keys(operation.responses).length === 0) {
|
1988
|
-
operation.responses["200"] = {
|
1989
|
-
description: "Successful response",
|
1990
|
-
content: {
|
1991
|
-
"application/json": {
|
1992
|
-
schema: { type: "object" }
|
1993
|
-
}
|
1994
|
-
}
|
1995
|
-
};
|
1996
|
-
}
|
1997
|
-
if (swaggerOptions?.security) {
|
1998
|
-
const securityArr = [];
|
1999
|
-
if (!Array.isArray(swaggerOptions.security)) {
|
2000
|
-
swaggerOptions.security = [swaggerOptions.security];
|
2001
|
-
}
|
2002
|
-
for (const sec of swaggerOptions.security) {
|
2003
|
-
if (sec.type === "bearer") {
|
2004
|
-
if (!components.securitySchemes.bearer) {
|
2005
|
-
components.securitySchemes.bearer = {
|
2006
|
-
type: "http",
|
2007
|
-
scheme: "bearer",
|
2008
|
-
bearerFormat: sec.bearerFormat || "JWT",
|
2009
|
-
description: sec.description
|
2010
|
-
};
|
2011
|
-
}
|
2012
|
-
securityArr.push({ bearer: [] });
|
2013
|
-
} else if (sec.type === "apiKey") {
|
2014
|
-
if (!components.securitySchemes[sec.name]) {
|
2015
|
-
components.securitySchemes[sec.name] = {
|
2016
|
-
type: "apiKey",
|
2017
|
-
name: sec.name,
|
2018
|
-
in: sec.in,
|
2019
|
-
description: sec.description
|
2020
|
-
};
|
2021
|
-
}
|
2022
|
-
securityArr.push({ [sec.name]: [] });
|
2023
|
-
} else if (sec.type === "oauth2") {
|
2024
|
-
const schemeName = sec.name || "oauth2";
|
2025
|
-
if (!components.securitySchemes[schemeName]) {
|
2026
|
-
components.securitySchemes[schemeName] = {
|
2027
|
-
type: "oauth2",
|
2028
|
-
flows: sec.flows,
|
2029
|
-
description: sec.description
|
2030
|
-
};
|
2031
|
-
}
|
2032
|
-
securityArr.push({ [schemeName]: [] });
|
2033
|
-
} else if (sec.type === "openIdConnect") {
|
2034
|
-
const schemeName = sec.name || "openIdConnect";
|
2035
|
-
if (!components.securitySchemes[schemeName]) {
|
2036
|
-
components.securitySchemes[schemeName] = {
|
2037
|
-
type: "openIdConnect",
|
2038
|
-
openIdConnectUrl: sec.openIdConnectUrl,
|
2039
|
-
description: sec.description
|
2040
|
-
};
|
2041
|
-
}
|
2042
|
-
securityArr.push({ [schemeName]: [] });
|
2043
|
-
}
|
2044
|
-
}
|
2045
|
-
if (securityArr.length) operation.security = securityArr;
|
2046
|
-
} else if (globalOptions.security) {
|
2047
|
-
operation.security = globalOptions.security;
|
2048
|
-
}
|
2049
|
-
paths[route.path][method] = operation;
|
2050
|
-
}
|
2051
|
-
return {
|
2052
|
-
openapi: "3.0.0",
|
2053
|
-
info: {
|
2054
|
-
title: globalOptions.title,
|
2055
|
-
description: globalOptions.description,
|
2056
|
-
version: globalOptions.version,
|
2057
|
-
...globalOptions.info
|
2058
|
-
},
|
2059
|
-
servers: globalOptions.servers?.map((url) => ({ url })) || [{ url: "/" }],
|
2060
|
-
paths,
|
2061
|
-
components,
|
2062
|
-
security: globalOptions.security || [],
|
2063
|
-
tags: globalOptions.tags ? Object.entries(globalOptions.tags).map(([name, config]) => ({
|
2064
|
-
name,
|
2065
|
-
...config
|
2066
|
-
})) : []
|
2067
|
-
};
|
2068
|
-
}
|
2069
|
-
function generateSwaggerUI(specUrl, globalOptions) {
|
2070
|
-
return `
|
2071
|
-
<!DOCTYPE html>
|
2072
|
-
<html lang="en">
|
2073
|
-
<head>
|
2074
|
-
<meta charset="utf-8" />
|
2075
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
2076
|
-
<meta name="description" content="${globalOptions.description}" />
|
2077
|
-
<title>${globalOptions.title}</title>
|
2078
|
-
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
|
2079
|
-
<style>
|
2080
|
-
html {
|
2081
|
-
box-sizing: border-box;
|
2082
|
-
overflow: -moz-scrollbars-vertical;
|
2083
|
-
overflow-y: scroll;
|
2084
|
-
}
|
2085
|
-
*, *:before, *:after {
|
2086
|
-
box-sizing: inherit;
|
2087
|
-
}
|
2088
|
-
body {
|
2089
|
-
margin:0;
|
2090
|
-
background: #fafafa;
|
2091
|
-
}
|
2092
|
-
</style>
|
2093
|
-
</head>
|
2094
|
-
<body>
|
2095
|
-
<div id="swagger-ui"></div>
|
2096
|
-
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
2097
|
-
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
|
2098
|
-
<script>
|
2099
|
-
window.onload = function() {
|
2100
|
-
const ui = SwaggerUIBundle({
|
2101
|
-
url: '${specUrl}',
|
2102
|
-
dom_id: '#swagger-ui',
|
2103
|
-
deepLinking: true,
|
2104
|
-
presets: [
|
2105
|
-
SwaggerUIBundle.presets.apis,
|
2106
|
-
SwaggerUIStandalonePreset
|
2107
|
-
],
|
2108
|
-
plugins: [
|
2109
|
-
SwaggerUIBundle.plugins.DownloadUrl
|
2110
|
-
],
|
2111
|
-
layout: "StandaloneLayout",
|
2112
|
-
validatorUrl: null,
|
2113
|
-
oauth2RedirectUrl: window.location.origin + '/swagger-ui/oauth2-redirect.html'
|
2114
|
-
});
|
2115
|
-
};
|
2116
|
-
</script>
|
2117
|
-
</body>
|
2118
|
-
</html>`;
|
2119
|
-
}
|
2120
|
-
function generateRedocUI(specUrl, globalOptions) {
|
2121
|
-
return `
|
2122
|
-
<!DOCTYPE html>
|
2123
|
-
<html>
|
2124
|
-
<head>
|
2125
|
-
<title>${globalOptions.title}</title>
|
2126
|
-
<meta charset="utf-8"/>
|
2127
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
2128
|
-
<meta name="description" content="${globalOptions.description}" />
|
2129
|
-
<link rel="icon" type="image/png" href="https://redocly.github.io/redoc/favicon.ico">
|
2130
|
-
<style>
|
2131
|
-
body { margin: 0; padding: 0; }
|
2132
|
-
</style>
|
2133
|
-
</head>
|
2134
|
-
<body>
|
2135
|
-
<redoc spec-url="${specUrl}"></redoc>
|
2136
|
-
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
|
2137
|
-
</body>
|
2138
|
-
</html>
|
2139
|
-
`;
|
2140
|
-
}
|
2141
|
-
function generateRapiDocUI(specUrl, globalOptions) {
|
2142
|
-
return `
|
2143
|
-
<!DOCTYPE html>
|
2144
|
-
<html>
|
2145
|
-
<head>
|
2146
|
-
<title>${globalOptions.title}</title>
|
2147
|
-
<meta charset="utf-8"/>
|
2148
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
2149
|
-
<meta name="description" content="${globalOptions.description}" />
|
2150
|
-
<link rel="icon" type="image/png" href="https://mrin9.github.io/RapiDoc/images/favicon.png">
|
2151
|
-
<style>
|
2152
|
-
body { margin: 0; padding: 0; }
|
2153
|
-
</style>
|
2154
|
-
</head>
|
2155
|
-
<body>
|
2156
|
-
<rapi-doc
|
2157
|
-
spec-url="${specUrl}"
|
2158
|
-
render-style="read"
|
2159
|
-
layout="column"
|
2160
|
-
show-header="true"
|
2161
|
-
allow-server-selection="true"
|
2162
|
-
allow-authentication="true"
|
2163
|
-
allow-server-variables="true"
|
2164
|
-
theme="light"
|
2165
|
-
primary-color="#009688"
|
2166
|
-
regular-font="Open Sans, sans-serif"
|
2167
|
-
mono-font="Fira Mono, monospace"
|
2168
|
-
>
|
2169
|
-
</rapi-doc>
|
2170
|
-
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
2171
|
-
</body>
|
2172
|
-
</html>
|
2173
|
-
`;
|
2174
|
-
}
|
2175
|
-
function typeboxToOpenAPI(schema) {
|
2176
|
-
if (!schema) {
|
2177
|
-
return void 0;
|
2178
|
-
}
|
2179
|
-
const { $id, $schema, ...rest } = schema;
|
2180
|
-
return rest;
|
2181
|
-
}
|
2182
|
-
function extractPathParams(path, paramSchema) {
|
2183
|
-
const params = [];
|
2184
|
-
const regex = /:([a-zA-Z0-9_]+)/g;
|
2185
|
-
let match;
|
2186
|
-
while ((match = regex.exec(path)) !== null) {
|
2187
|
-
const name = match[1];
|
2188
|
-
let schema = { type: "string" };
|
2189
|
-
if (paramSchema && paramSchema.type === "object" && paramSchema.properties && paramSchema.properties[name]) {
|
2190
|
-
schema = typeboxToOpenAPI(paramSchema.properties[name]) || {
|
2191
|
-
type: "string"
|
2192
|
-
};
|
2193
|
-
}
|
2194
|
-
params.push({
|
2195
|
-
name,
|
2196
|
-
in: "path",
|
2197
|
-
required: true,
|
2198
|
-
schema
|
2199
|
-
});
|
2200
|
-
}
|
2201
|
-
return params;
|
2202
|
-
}
|
2203
|
-
|
2204
|
-
// src/runtime/native_server/server_bun.ts
|
2205
|
-
var ServerBun = class {
|
2206
|
-
port;
|
2207
|
-
hostname;
|
2208
|
-
host;
|
2209
|
-
routes;
|
2210
|
-
tapOptions;
|
2211
|
-
constructor(input) {
|
2212
|
-
this.routes = input?.routes ?? [];
|
2213
|
-
this.port = input?.port ?? 80;
|
2214
|
-
this.hostname = input?.host ?? "0.0.0.0";
|
2215
|
-
this.host = input?.host ?? "0.0.0.0";
|
2216
|
-
this.tapOptions = input?.tapOptions;
|
2217
|
-
}
|
2218
|
-
listen() {
|
2219
|
-
const tapOptions = this.tapOptions?.options;
|
2220
|
-
const { fetch, ...rest } = tapOptions ?? {};
|
2221
|
-
this.runtimeServer = Bun.serve({
|
2222
|
-
port: this.port,
|
2223
|
-
hostname: this.hostname,
|
2224
|
-
fetch: async (req, server) => {
|
2225
|
-
const url = new URL(req.url);
|
2226
|
-
const match = router.find(req.method, url.pathname);
|
2227
|
-
Request2.enrichRequest(req);
|
2228
|
-
req.params = match?.params ?? {};
|
2229
|
-
req.query = Object.fromEntries(url.searchParams.entries());
|
2230
|
-
req.ip = req.headers.get("x-forwarded-for")?.split(",")[0] ?? server.requestIP(req)?.address;
|
2231
|
-
await fetch?.call(this, req, server);
|
2232
|
-
const response = await executeMiddlewareChain(
|
2233
|
-
match?.middleware ?? [],
|
2234
|
-
match?.handler ?? ((req2, res) => {
|
2235
|
-
res.notFound({
|
2236
|
-
...errorFactory(new RouteNotFoundError(req2.url, req2.method))
|
2237
|
-
});
|
2238
|
-
}),
|
2239
|
-
req
|
2240
|
-
);
|
2241
|
-
const responseHeaders = response.headers;
|
2242
|
-
if (responseHeaders["Content-Type"] === "application/json") {
|
2243
|
-
return Response.json(response.getBody(), {
|
2244
|
-
status: response.responseStatus,
|
2245
|
-
headers: response.headers
|
2246
|
-
});
|
2247
|
-
}
|
2248
|
-
return new Response(response.getBody(), {
|
2249
|
-
status: response.responseStatus,
|
2250
|
-
headers: response.headers
|
2251
|
-
});
|
2252
|
-
},
|
2253
|
-
...rest
|
2254
|
-
});
|
2255
|
-
this.url = this.runtimeServer.url.toString();
|
2256
|
-
}
|
2257
|
-
async close() {
|
2258
|
-
if (!this.runtimeServer) {
|
2259
|
-
throw new Error("Server is not listening or not initialized");
|
2260
|
-
}
|
2261
|
-
await this.runtimeServer.stop();
|
2262
|
-
}
|
2263
|
-
};
|
2264
|
-
|
2265
|
-
// src/runtime/native_server/server_deno.ts
|
2266
|
-
var ServerDeno = class {
|
2267
|
-
constructor(input) {
|
2268
|
-
this.routes = input?.routes ?? [];
|
2269
|
-
this.port = input?.port ?? 80;
|
2270
|
-
this.hostname = input?.host ?? "0.0.0.0";
|
2271
|
-
this.host = input?.host ?? "0.0.0.0";
|
2272
|
-
this.tapOptions = input?.tapOptions;
|
2273
|
-
}
|
2274
|
-
listen() {
|
2275
|
-
const tapOptions = this.tapOptions?.options;
|
2276
|
-
const { handler, ...rest } = tapOptions ?? {};
|
2277
|
-
this.runtimeServer = Deno.serve({
|
2278
|
-
port: this.port,
|
2279
|
-
hostname: this.hostname,
|
2280
|
-
handler: async (req, info) => {
|
2281
|
-
const url = new URL(req.url);
|
2282
|
-
const match = router.find(req.method, url.pathname);
|
2283
|
-
Request2.enrichRequest(req);
|
2284
|
-
req.params = match?.params ?? {};
|
2285
|
-
req.query = Object.fromEntries(url.searchParams.entries());
|
2286
|
-
req.ip = req.headers.get("x-forwarded-for")?.split(",")[0] ?? info.remoteAddr?.hostname;
|
2287
|
-
await handler?.(req, info);
|
2288
|
-
const res = await executeMiddlewareChain(
|
2289
|
-
match?.middleware ?? [],
|
2290
|
-
match?.handler ?? ((req2, res2) => {
|
2291
|
-
res2.notFound({
|
2292
|
-
...errorFactory(new RouteNotFoundError(req2.url, req2.method))
|
2293
|
-
});
|
2294
|
-
}),
|
2295
|
-
req
|
2296
|
-
);
|
2297
|
-
const responseHeaders = res.headers;
|
2298
|
-
if (responseHeaders["Content-Type"] === "application/json") {
|
2299
|
-
return Response.json(res.getBody(), {
|
2300
|
-
status: res.responseStatus,
|
2301
|
-
headers: res.headers
|
2302
|
-
});
|
2303
|
-
}
|
2304
|
-
return new Response(res.getBody(), {
|
2305
|
-
status: res.responseStatus,
|
2306
|
-
headers: res.headers
|
2307
|
-
});
|
2308
|
-
},
|
2309
|
-
...rest
|
2310
|
-
});
|
2311
|
-
this.url = `http://${this.host}:${this.port}`;
|
2312
|
-
}
|
2313
|
-
async close() {
|
2314
|
-
if (!this.runtimeServer) {
|
2315
|
-
throw new Error("Server is not listening or not initialized");
|
2316
|
-
}
|
2317
|
-
await this.runtimeServer.shutdown();
|
2318
|
-
}
|
2319
|
-
};
|
2320
|
-
|
2321
|
-
// src/runtime/native_server/server_node.ts
|
2322
|
-
var import_node_http = require("http");
|
2323
|
-
async function pipeReadableStreamToNodeResponse(stream, res) {
|
2324
|
-
const reader = stream.getReader();
|
2325
|
-
try {
|
2326
|
-
while (true) {
|
2327
|
-
const { done, value } = await reader.read();
|
2328
|
-
if (done) {
|
2329
|
-
res.end();
|
2330
|
-
break;
|
2331
|
-
}
|
2332
|
-
res.write(value);
|
2333
|
-
}
|
2334
|
-
} catch (error) {
|
2335
|
-
res.destroy(error);
|
2336
|
-
}
|
2337
|
-
}
|
2338
|
-
var ServerNode = class {
|
2339
|
-
port;
|
2340
|
-
host;
|
2341
|
-
url;
|
2342
|
-
routes;
|
2343
|
-
tapOptions;
|
2344
|
-
runtimeServer;
|
2345
|
-
constructor(input) {
|
2346
|
-
this.routes = input?.routes ?? [];
|
2347
|
-
this.port = input?.port ?? 80;
|
2348
|
-
this.host = input?.host ?? "0.0.0.0";
|
2349
|
-
this.url = `http://${this.host}:${this.port}`;
|
2350
|
-
this.tapOptions = input?.tapOptions;
|
2351
|
-
this.runtimeServer = (0, import_node_http.createServer)(
|
2352
|
-
async (req, httpResponse) => {
|
2353
|
-
if (this.tapOptions) {
|
2354
|
-
const { options } = this.tapOptions;
|
2355
|
-
await options?.(req);
|
2356
|
-
}
|
2357
|
-
const match = router.find(req.method, req.url);
|
2358
|
-
const request = new Request2(`${this.url}${req.url}`, {
|
2359
|
-
method: req.method,
|
2360
|
-
body: canHaveBody(req.method) ? await this.readRequestBody(req) : void 0,
|
2361
|
-
headers: req.headers
|
2362
|
-
});
|
2363
|
-
let forwardedFor = req.headers["x-forwarded-for"];
|
2364
|
-
if (Array.isArray(forwardedFor)) {
|
2365
|
-
forwardedFor = forwardedFor[0];
|
2366
|
-
}
|
2367
|
-
request.ip = forwardedFor ?? req.socket.remoteAddress;
|
2368
|
-
const [_, search = ""] = req.url?.split("?", 2) ?? [];
|
2369
|
-
request.query = Object.fromEntries(new URLSearchParams(search));
|
2370
|
-
request.params = match?.params ?? {};
|
2371
|
-
const response = await executeMiddlewareChain(
|
2372
|
-
match?.middleware ?? [],
|
2373
|
-
match?.handler ?? ((req2, res) => {
|
2374
|
-
res.notFound({
|
2375
|
-
...errorFactory(new RouteNotFoundError(req2.url, req2.method))
|
2376
|
-
});
|
2377
|
-
}),
|
2378
|
-
request
|
2379
|
-
);
|
2380
|
-
let body = response.getBody();
|
2381
|
-
if (body instanceof ReadableStream) {
|
2382
|
-
pipeReadableStreamToNodeResponse(body, httpResponse);
|
2383
|
-
return;
|
2384
|
-
}
|
2385
|
-
if (response.headers["Content-Type"] === "application/json") {
|
2386
|
-
body = JSON.stringify(body);
|
2387
|
-
} else if (typeof body === "string") {
|
2388
|
-
body = body;
|
2389
|
-
} else if (body instanceof Buffer || body instanceof Uint8Array) {
|
2390
|
-
body = body;
|
2391
|
-
} else {
|
2392
|
-
body = String(body);
|
2393
|
-
}
|
2394
|
-
httpResponse.writeHead(response.responseStatus, response.headers);
|
2395
|
-
httpResponse.end(body);
|
2396
|
-
}
|
2397
|
-
);
|
2398
|
-
}
|
2399
|
-
listen() {
|
2400
|
-
this.runtimeServer.listen(this.port, this.host);
|
2401
|
-
}
|
2402
|
-
async close() {
|
2403
|
-
return new Promise((resolve2, reject) => {
|
2404
|
-
this.runtimeServer.close((err) => {
|
2405
|
-
if (err && "code" in err && err.code !== "ERR_SERVER_NOT_RUNNING") {
|
2406
|
-
reject(err);
|
2407
|
-
} else {
|
2408
|
-
resolve2();
|
2409
|
-
}
|
2410
|
-
});
|
2411
|
-
});
|
2412
|
-
}
|
2413
|
-
async readRequestBody(req) {
|
2414
|
-
return new Promise((resolve2, reject) => {
|
2415
|
-
const chunks = [];
|
2416
|
-
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
2417
|
-
req.on("error", reject);
|
2418
|
-
req.on("end", () => resolve2(Buffer.concat(chunks).toString()));
|
2419
|
-
});
|
2420
|
-
}
|
2421
|
-
};
|
2422
|
-
|
2423
|
-
// src/runtime/native_server/server_connector.ts
|
2424
|
-
var ServerConnector = class {
|
2425
|
-
server;
|
2426
|
-
constructor(serverOptions) {
|
2427
|
-
this.server = this.getRuntimeServer(serverOptions);
|
2428
|
-
this.routes = this.server.routes;
|
2429
|
-
}
|
2430
|
-
get url() {
|
2431
|
-
return this.server.url;
|
2432
|
-
}
|
2433
|
-
get port() {
|
2434
|
-
return this.server.port;
|
2435
|
-
}
|
2436
|
-
get host() {
|
2437
|
-
return this.server.host;
|
2438
|
-
}
|
2439
|
-
/**
|
2440
|
-
* Get the server for the given runtime
|
2441
|
-
* @example "node" returns HttpServer
|
2442
|
-
* @example "bun" returns ReturnType<typeof Bun.serve>
|
2443
|
-
* @example "deno" returns ReturnType<typeof Deno.serve>
|
2444
|
-
* @param _ - The runtime to get the server for
|
2445
|
-
* @returns The server for the given runtime
|
2446
|
-
*/
|
2447
|
-
getServer(_) {
|
2448
|
-
return this.server.runtimeServer;
|
2449
|
-
}
|
2450
|
-
listen() {
|
2451
|
-
return this.server.listen();
|
2452
|
-
}
|
2453
|
-
close() {
|
2454
|
-
return this.server.close();
|
2455
|
-
}
|
2456
|
-
getRuntimeServer(serverOptions) {
|
2457
|
-
if (serverOptions?.runtime === "bun") {
|
2458
|
-
return new ServerBun(serverOptions);
|
2459
|
-
} else if (serverOptions?.runtime === "node") {
|
2460
|
-
return new ServerNode(serverOptions);
|
2461
|
-
} else if (serverOptions?.runtime === "deno") {
|
2462
|
-
return new ServerDeno(serverOptions);
|
2463
|
-
}
|
2464
|
-
throw new Error(
|
2465
|
-
"No server implementation found for runtime: " + serverOptions?.runtime
|
2466
|
-
);
|
2467
|
-
}
|
2468
|
-
};
|
2469
|
-
|
2470
|
-
// src/server/server_constants.ts
|
2471
|
-
var PROTECTED_KEYS = [
|
2472
|
-
"isListening",
|
2473
|
-
"url",
|
2474
|
-
"port",
|
2475
|
-
"host",
|
2476
|
-
"routes",
|
2477
|
-
"embed",
|
2478
|
-
"constructor",
|
2479
|
-
"get",
|
2480
|
-
"post",
|
2481
|
-
"put",
|
2482
|
-
"patch",
|
2483
|
-
"delete",
|
2484
|
-
"getNodeServer",
|
2485
|
-
"getBunServer",
|
2486
|
-
"getDenoServer",
|
2487
|
-
"use",
|
2488
|
-
"setErrorHandler",
|
2489
|
-
"listen",
|
2490
|
-
"close",
|
2491
|
-
"tapOptions",
|
2492
|
-
"startUpOptions",
|
2493
|
-
"tmpDir",
|
2494
|
-
"logger",
|
2495
|
-
"getMockServer"
|
2496
|
-
];
|
2497
|
-
|
2498
|
-
// src/mock/mock_response.ts
|
2499
|
-
var MockResponse = class {
|
2500
|
-
constructor(response) {
|
2501
|
-
this.response = response;
|
2502
|
-
}
|
2503
|
-
// base getters
|
2504
|
-
body() {
|
2505
|
-
return this.response.getBody();
|
2506
|
-
}
|
2507
|
-
statusCode() {
|
2508
|
-
return this.response.responseStatus;
|
2509
|
-
}
|
2510
|
-
headers() {
|
2511
|
-
return this.response.headers;
|
2512
|
-
}
|
2513
|
-
// assertions
|
2514
|
-
assertStatus(status) {
|
2515
|
-
if (this.response.responseStatus !== status) {
|
2516
|
-
throw new Error(
|
2517
|
-
`Expected status ${status}, but got ${this.response.responseStatus}`
|
2518
|
-
);
|
2519
|
-
}
|
2520
|
-
return this;
|
2521
|
-
}
|
2522
|
-
assertHeader(header, value) {
|
2523
|
-
if (this.response.headers[header] !== value) {
|
2524
|
-
throw new Error(
|
2525
|
-
`Expected header ${header} to be ${value}, but got ${this.response.headers[header]}`
|
2526
|
-
);
|
2527
|
-
}
|
2528
|
-
return this;
|
2529
|
-
}
|
2530
|
-
assertHeaderExists(header) {
|
2531
|
-
if (!(header in this.response.headers)) {
|
2532
|
-
throw new Error(
|
2533
|
-
`Expected header ${header} to exist, but it was not found`
|
2534
|
-
);
|
2535
|
-
}
|
2536
|
-
return this;
|
2537
|
-
}
|
2538
|
-
assertHeaderNotExists(header) {
|
2539
|
-
if (header in this.response.headers) {
|
2540
|
-
throw new Error(
|
2541
|
-
`Expected header ${header} to not exist, but it was found with value: ${this.response.headers[header]}`
|
2542
|
-
);
|
2543
|
-
}
|
2544
|
-
return this;
|
2545
|
-
}
|
2546
|
-
// TODO: body assertions
|
2547
|
-
assertBodySubset(subset) {
|
2548
|
-
this.assertSubset(this.body(), subset, "body");
|
2549
|
-
return this;
|
2550
|
-
}
|
2551
|
-
assertBodyDeepEqual(expected) {
|
2552
|
-
this.assertDeepEqual(this.body(), expected, "body");
|
2553
|
-
return this;
|
2554
|
-
}
|
2555
|
-
assertBodyNotSubset(subset) {
|
2556
|
-
this.assertNotSubset(this.body(), subset, "body");
|
2557
|
-
return this;
|
2558
|
-
}
|
2559
|
-
assertBodyNotDeepEqual(expected) {
|
2560
|
-
this.assertNotDeepEqual(this.body(), expected, "body");
|
2561
|
-
return this;
|
2562
|
-
}
|
2563
|
-
assertCustom(assertion) {
|
2564
|
-
assertion(this.response);
|
2565
|
-
return this;
|
2566
|
-
}
|
2567
|
-
assertSubset(target, subset, path) {
|
2568
|
-
for (const key in subset) {
|
2569
|
-
const currentPath = path === "" ? key : `${path}.${key}`;
|
2570
|
-
const targetValue = target[key];
|
2571
|
-
const subsetValue = subset[key];
|
2572
|
-
if (!(key in target)) {
|
2573
|
-
throw new Error(
|
2574
|
-
`Expected ${path} to have key ${key}, but it was not found`
|
2575
|
-
);
|
2576
|
-
}
|
2577
|
-
if (this.isObject(subsetValue) && this.isObject(targetValue)) {
|
2578
|
-
this.assertSubset(targetValue, subsetValue, currentPath);
|
2579
|
-
} else if (Array.isArray(subsetValue) && Array.isArray(targetValue)) {
|
2580
|
-
this.assertArraySubset(targetValue, subsetValue, currentPath);
|
2581
|
-
} else if (targetValue !== subsetValue) {
|
2582
|
-
throw new Error(
|
2583
|
-
`Expected ${currentPath} to be ${subsetValue}, but got ${targetValue}`
|
2584
|
-
);
|
2585
|
-
}
|
2586
|
-
}
|
2587
|
-
}
|
2588
|
-
assertDeepEqual(target, expected, path) {
|
2589
|
-
if (this.isObject(target) && this.isObject(expected)) {
|
2590
|
-
const targetKeys = Object.keys(target);
|
2591
|
-
const expectedKeys = Object.keys(expected);
|
2592
|
-
if (targetKeys.length !== expectedKeys.length) {
|
2593
|
-
throw new Error(
|
2594
|
-
`Expected ${path} to have ${expectedKeys.length} keys, but got ${targetKeys.length}`
|
2595
|
-
);
|
2596
|
-
}
|
2597
|
-
for (const key of expectedKeys) {
|
2598
|
-
const currentPath = path === "body" ? key : `${path}.${key}`;
|
2599
|
-
this.assertDeepEqual(target[key], expected[key], currentPath);
|
2600
|
-
}
|
2601
|
-
} else if (Array.isArray(target) && Array.isArray(expected)) {
|
2602
|
-
this.assertArrayDeepEqual(target, expected, path);
|
2603
|
-
} else if (target !== expected) {
|
2604
|
-
throw new Error(`Expected ${path} to be ${expected}, but got ${target}`);
|
2605
|
-
}
|
2606
|
-
}
|
2607
|
-
assertNotSubset(target, subset, path) {
|
2608
|
-
try {
|
2609
|
-
this.assertSubset(target, subset, path);
|
2610
|
-
throw new Error(
|
2611
|
-
`Expected ${path} to NOT contain the subset, but it does`
|
2612
|
-
);
|
2613
|
-
} catch (error) {
|
2614
|
-
if (error instanceof Error && error.message.includes("Expected")) {
|
2615
|
-
return;
|
2616
|
-
}
|
2617
|
-
throw error;
|
2618
|
-
}
|
2619
|
-
}
|
2620
|
-
assertNotDeepEqual(target, expected, path) {
|
2621
|
-
try {
|
2622
|
-
this.assertDeepEqual(target, expected, path);
|
2623
|
-
throw new Error(`Expected ${path} to NOT be deeply equal, but it is`);
|
2624
|
-
} catch (error) {
|
2625
|
-
if (error instanceof Error && error.message.includes("Expected")) {
|
2626
|
-
return;
|
2627
|
-
}
|
2628
|
-
throw error;
|
2629
|
-
}
|
2630
|
-
}
|
2631
|
-
assertArraySubset(target, subset, path) {
|
2632
|
-
if (subset.length > target.length) {
|
2633
|
-
throw new Error(
|
2634
|
-
`Expected ${path} to have at least ${subset.length} elements, but got ${target.length}`
|
2635
|
-
);
|
2636
|
-
}
|
2637
|
-
for (let i = 0; i < subset.length; i++) {
|
2638
|
-
const currentPath = `${path}[${i}]`;
|
2639
|
-
const targetValue = target[i];
|
2640
|
-
const subsetValue = subset[i];
|
2641
|
-
if (this.isObject(subsetValue) && this.isObject(targetValue)) {
|
2642
|
-
this.assertSubset(targetValue, subsetValue, currentPath);
|
2643
|
-
} else if (Array.isArray(subsetValue) && Array.isArray(targetValue)) {
|
2644
|
-
this.assertArraySubset(targetValue, subsetValue, currentPath);
|
2645
|
-
} else if (targetValue !== subsetValue) {
|
2646
|
-
throw new Error(
|
2647
|
-
`Expected ${currentPath} to be ${subsetValue}, but got ${targetValue}`
|
2648
|
-
);
|
2649
|
-
}
|
2650
|
-
}
|
2651
|
-
}
|
2652
|
-
assertArrayDeepEqual(target, expected, path) {
|
2653
|
-
if (target.length !== expected.length) {
|
2654
|
-
throw new Error(
|
2655
|
-
`Expected ${path} to have ${expected.length} elements, but got ${target.length}`
|
2656
|
-
);
|
2657
|
-
}
|
2658
|
-
for (let i = 0; i < expected.length; i++) {
|
2659
|
-
const currentPath = `${path}[${i}]`;
|
2660
|
-
this.assertDeepEqual(target[i], expected[i], currentPath);
|
2661
|
-
}
|
2662
|
-
}
|
2663
|
-
isObject(value) {
|
2664
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
2665
|
-
}
|
2666
|
-
};
|
2667
|
-
|
2668
|
-
// src/mock/mock_server.ts
|
2669
|
-
var MockServer = class {
|
2670
|
-
server;
|
2671
|
-
constructor(server) {
|
2672
|
-
this.server = server;
|
2673
|
-
}
|
2674
|
-
/**
|
2675
|
-
* Simulates an HTTP request without making an actual network call, useful for testing purposes
|
2676
|
-
* Executes the middleware chain and handler of the route
|
2677
|
-
* @param method - The HTTP method (GET, POST, PUT, DELETE, PATCH)
|
2678
|
-
* @param path - The request path
|
2679
|
-
* @param options - Request options including body, headers, query params, etc.
|
2680
|
-
* @throws {Error} - If more than one of body, formData, urlencoded is provided
|
2681
|
-
*/
|
2682
|
-
async request(method, path, options = {}) {
|
2683
|
-
const { headers = {}, query = {}, cookies = {}, ip } = options;
|
2684
|
-
this.validateOptions(options);
|
2685
|
-
const route = router.find(method.toUpperCase(), path);
|
2686
|
-
if (!route) {
|
2687
|
-
const res = new Response2(404);
|
2688
|
-
res.json({
|
2689
|
-
caller: "MockServer",
|
2690
|
-
error: "Route not found",
|
2691
|
-
path,
|
2692
|
-
method
|
2693
|
-
});
|
2694
|
-
return new MockResponse(res);
|
2695
|
-
}
|
2696
|
-
let body = options.body;
|
2697
|
-
let contentType = "application/json";
|
2698
|
-
if (body && typeof body === "object" && !(body instanceof Uint8Array) && !(body instanceof ArrayBuffer)) {
|
2699
|
-
body = JSON.stringify(body);
|
2700
|
-
}
|
2701
|
-
if (options.formData) {
|
2702
|
-
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
|
2703
|
-
contentType = `multipart/form-data; boundary=${boundary}`;
|
2704
|
-
const multipartBody = await this.formDataToMultipart(
|
2705
|
-
options.formData,
|
2706
|
-
boundary
|
2707
|
-
);
|
2708
|
-
body = multipartBody;
|
2709
|
-
}
|
2710
|
-
if (options.urlencoded) {
|
2711
|
-
contentType = "application/x-www-form-urlencoded";
|
2712
|
-
body = new URLSearchParams(options.urlencoded).toString();
|
2713
|
-
}
|
2714
|
-
const url = new URL(
|
2715
|
-
`http://${this.server.host}:${this.server.port}${path}`
|
2716
|
-
);
|
2717
|
-
url.search = new URLSearchParams(query).toString();
|
2718
|
-
const req = new Request2(url.toString(), {
|
2719
|
-
method: method.toUpperCase(),
|
2720
|
-
body: canHaveBody(method) ? body : void 0,
|
2721
|
-
headers: {
|
2722
|
-
"content-type": contentType,
|
2723
|
-
...headers
|
2724
|
-
}
|
2725
|
-
});
|
2726
|
-
req.query = { ...Object.fromEntries(url.searchParams.entries()), ...query };
|
2727
|
-
req.params = route.params;
|
2728
|
-
req.cookies = cookies;
|
2729
|
-
req.ip = ip;
|
2730
|
-
try {
|
2731
|
-
const res = await executeMiddlewareChain(
|
2732
|
-
route.middleware,
|
2733
|
-
route.handler,
|
2734
|
-
req
|
2735
|
-
);
|
2736
|
-
return new MockResponse(res);
|
2737
|
-
} catch (error) {
|
2738
|
-
logger.error(`Error processing mock request ${method} ${path}:`, error);
|
2739
|
-
const errorRes = new Response2(500);
|
2740
|
-
errorRes.json({
|
2741
|
-
error: "Internal server error",
|
2742
|
-
message: error instanceof Error ? error.message : String(error)
|
2743
|
-
});
|
2744
|
-
return new MockResponse(errorRes);
|
2745
|
-
}
|
2746
|
-
}
|
2747
|
-
async get(path, options) {
|
2748
|
-
return this.request("GET", path, options);
|
2749
|
-
}
|
2750
|
-
async post(path, options) {
|
2751
|
-
return this.request("POST", path, options);
|
2752
|
-
}
|
2753
|
-
async put(path, options) {
|
2754
|
-
return this.request("PUT", path, options);
|
2755
|
-
}
|
2756
|
-
async patch(path, options) {
|
2757
|
-
return this.request("PATCH", path, options);
|
2758
|
-
}
|
2759
|
-
async delete(path, options) {
|
2760
|
-
return this.request("DELETE", path, options);
|
2761
|
-
}
|
2762
|
-
/**
|
2763
|
-
* Converts FormData to a proper multipart/form-data body with boundaries
|
2764
|
-
*/
|
2765
|
-
async formDataToMultipart(formData, boundary) {
|
2766
|
-
const encoder = new TextEncoder();
|
2767
|
-
const buffers = [];
|
2768
|
-
for (const [name, value] of formData.entries()) {
|
2769
|
-
buffers.push(encoder.encode(`--${boundary}\r
|
2770
|
-
`));
|
2771
|
-
let disposition = `Content-Disposition: form-data; name="${name}"`;
|
2772
|
-
let contentType = "";
|
2773
|
-
if (value instanceof File) {
|
2774
|
-
disposition += `; filename="${value.name}"`;
|
2775
|
-
contentType = `Content-Type: ${value.type || "application/octet-stream"}\r
|
2776
|
-
`;
|
2777
|
-
}
|
2778
|
-
buffers.push(encoder.encode(`${disposition}\r
|
2779
|
-
${contentType}\r
|
2780
|
-
`));
|
2781
|
-
if (value instanceof File) {
|
2782
|
-
const arrayBuffer = await value.arrayBuffer();
|
2783
|
-
buffers.push(new Uint8Array(arrayBuffer));
|
2784
|
-
buffers.push(encoder.encode("\r\n"));
|
2785
|
-
} else {
|
2786
|
-
buffers.push(encoder.encode(`${String(value)}\r
|
2787
|
-
`));
|
2788
|
-
}
|
2789
|
-
}
|
2790
|
-
buffers.push(encoder.encode(`--${boundary}--\r
|
2791
|
-
`));
|
2792
|
-
const totalLength = buffers.reduce((sum, b) => sum + b.byteLength, 0);
|
2793
|
-
const multipartBody = new Uint8Array(totalLength);
|
2794
|
-
let offset = 0;
|
2795
|
-
for (const buf of buffers) {
|
2796
|
-
multipartBody.set(buf, offset);
|
2797
|
-
offset += buf.byteLength;
|
2798
|
-
}
|
2799
|
-
return multipartBody;
|
2800
|
-
}
|
2801
|
-
validateOptions(options) {
|
2802
|
-
const { body, formData, urlencoded: urlencoded2 } = options;
|
2803
|
-
if (body && (formData || urlencoded2)) {
|
2804
|
-
throw new Error("Only one of body, formData, urlencoded can be provided");
|
2805
|
-
}
|
2806
|
-
if (formData && (urlencoded2 || body)) {
|
2807
|
-
throw new Error("Only one of formData, urlencoded can be provided");
|
2808
|
-
}
|
2809
|
-
if (urlencoded2 && (body || formData)) {
|
2810
|
-
throw new Error("Only one of urlencoded, body can be provided");
|
2811
|
-
}
|
2812
|
-
}
|
2813
|
-
};
|
2814
|
-
|
2815
|
-
// src/plugins/urlencoded/urlencoded.ts
|
2816
|
-
var urlencoded = (options) => {
|
2817
|
-
const opts = {
|
2818
|
-
limit: 1024 * 1024,
|
2819
|
-
extended: false,
|
2820
|
-
charset: "utf8",
|
2821
|
-
allowEmpty: true,
|
2822
|
-
parameterLimit: 1e3,
|
2823
|
-
...options
|
2824
|
-
};
|
2825
|
-
return async (req, res, next) => {
|
2826
|
-
const contentType = req.headers.get("content-type") || "";
|
2827
|
-
if (!contentType.includes("application/x-www-form-urlencoded")) {
|
2828
|
-
return next();
|
2829
|
-
}
|
2830
|
-
try {
|
2831
|
-
await parseUrlEncodedBody(req, opts);
|
2832
|
-
await next();
|
2833
|
-
} catch (error) {
|
2834
|
-
if (error instanceof Error && error.message.includes("limit")) {
|
2835
|
-
res.status(413).json({
|
2836
|
-
error: "Payload too large",
|
2837
|
-
message: "Request body exceeds the size limit"
|
2838
|
-
});
|
2839
|
-
return;
|
2840
|
-
}
|
2841
|
-
throw error;
|
2842
|
-
}
|
2843
|
-
};
|
2844
|
-
};
|
2845
|
-
async function parseUrlEncodedBody(req, opts) {
|
2846
|
-
const arrayBuffer = req.rawBody;
|
2847
|
-
if (arrayBuffer.byteLength > opts.limit) {
|
2848
|
-
throw new Error(
|
2849
|
-
`Body size ${arrayBuffer.byteLength} exceeds limit ${opts.limit}`
|
2850
|
-
);
|
2851
|
-
}
|
2852
|
-
const decoder = new TextDecoder(opts.charset);
|
2853
|
-
const bodyString = decoder.decode(arrayBuffer);
|
2854
|
-
const parsed = parseUrlEncodedString(bodyString, opts);
|
2855
|
-
req.body = parsed;
|
2856
|
-
}
|
2857
|
-
function parseUrlEncodedString(str, opts) {
|
2858
|
-
const result = {};
|
2859
|
-
const searchParams = new URLSearchParams(str);
|
2860
|
-
if (searchParams.size > opts.parameterLimit) {
|
2861
|
-
throw new Error(
|
2862
|
-
`Too many parameters: ${searchParams.size} exceeds limit ${opts.parameterLimit}`
|
2863
|
-
);
|
2864
|
-
}
|
2865
|
-
for (const [key, value] of searchParams.entries()) {
|
2866
|
-
if (!opts.allowEmpty && value === "") {
|
2867
|
-
continue;
|
2868
|
-
}
|
2869
|
-
if (opts.extended) {
|
2870
|
-
setNestedValue(result, key, value);
|
2871
|
-
} else {
|
2872
|
-
result[key] = value;
|
2873
|
-
}
|
2874
|
-
}
|
2875
|
-
return result;
|
2876
|
-
}
|
2877
|
-
function setNestedValue(obj, key, value) {
|
2878
|
-
const keys = key.match(/\[([^\]]*)\]/g);
|
2879
|
-
if (!keys) {
|
2880
|
-
obj[key] = value;
|
2881
|
-
return;
|
2882
|
-
}
|
2883
|
-
let current = obj;
|
2884
|
-
const baseKey = key.split("[")[0];
|
2885
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
2886
|
-
const bracketKey = keys[i].slice(1, -1);
|
2887
|
-
if (!current[baseKey]) {
|
2888
|
-
current[baseKey] = {};
|
2889
|
-
}
|
2890
|
-
if (bracketKey === "") {
|
2891
|
-
if (!Array.isArray(current[baseKey])) {
|
2892
|
-
current[baseKey] = [];
|
2893
|
-
}
|
2894
|
-
current = current[baseKey];
|
2895
|
-
continue;
|
2896
|
-
}
|
2897
|
-
if (!current[baseKey][bracketKey]) {
|
2898
|
-
current[baseKey][bracketKey] = {};
|
2899
|
-
}
|
2900
|
-
current = current[baseKey][bracketKey];
|
2901
|
-
}
|
2902
|
-
const lastKey = keys[keys.length - 1].slice(1, -1);
|
2903
|
-
if (lastKey === "") {
|
2904
|
-
if (!Array.isArray(current)) {
|
2905
|
-
current = [];
|
2906
|
-
}
|
2907
|
-
current.push(value);
|
2908
|
-
return;
|
2909
|
-
}
|
2910
|
-
current[lastKey] = value;
|
2911
|
-
}
|
2912
|
-
|
2913
|
-
// src/cron/cron.ts
|
2914
|
-
var import_glob = require("glob");
|
2915
|
-
var CronService = class {
|
2916
|
-
static scheduledJobs = [];
|
2917
|
-
/**
|
2918
|
-
* @description Schedule a cron job.
|
2919
|
-
* @internal
|
2920
|
-
* @example
|
2921
|
-
* CronService.register('test', '0 0 * * *', () => {
|
2922
|
-
* console.log('test');
|
2923
|
-
* }, {
|
2924
|
-
* timezone: 'Europe/Istanbul',
|
2925
|
-
* });
|
2926
|
-
*/
|
2927
|
-
static register(name, ...args) {
|
2928
|
-
args[2] = {
|
2929
|
-
name,
|
2930
|
-
...args[2]
|
2931
|
-
};
|
2932
|
-
this.scheduledJobs.push({ name, args });
|
2933
|
-
}
|
2934
|
-
/**
|
2935
|
-
* @description Start the cron scheduler.
|
2936
|
-
*/
|
2937
|
-
static async run() {
|
2938
|
-
const nodeCronModule = (await import("node-cron").catch(() => {
|
2939
|
-
throw new BaldaError(
|
2940
|
-
"node-cron not installed as a dependency, it is required in order to run cron jobs with the @cron decorator"
|
2941
|
-
);
|
2942
|
-
})).default;
|
2943
|
-
logger.info("Scheduling cron jobs");
|
2944
|
-
if (!this.scheduledJobs.length) {
|
2945
|
-
logger.info("No cron jobs to schedule");
|
2946
|
-
return;
|
2947
|
-
}
|
2948
|
-
for (const { name, args } of this.scheduledJobs) {
|
2949
|
-
logger.info(`Scheduling cron job: ${name}`);
|
2950
|
-
const scheduledJob = nodeCronModule.schedule(...args);
|
2951
|
-
scheduledJob.on(
|
2952
|
-
"execution:failed",
|
2953
|
-
(context) => this.globalErrorHandler(context)
|
2954
|
-
);
|
2955
|
-
}
|
2956
|
-
logger.info("Cron jobs scheduled");
|
2957
|
-
}
|
2958
|
-
/**
|
2959
|
-
* @description Main error handler for cron jobs. You can write your own error handler by overriding this static method for example with sentry.
|
2960
|
-
*/
|
2961
|
-
static globalErrorHandler(context) {
|
2962
|
-
logger.error(context.execution?.error);
|
2963
|
-
}
|
2964
|
-
/**
|
2965
|
-
* @description Import all cron jobs from the app/cron/schedules directory
|
2966
|
-
*/
|
2967
|
-
static async massiveImportCronJobs(cronJobPatterns) {
|
2968
|
-
const allFiles = [];
|
2969
|
-
for (const pattern of cronJobPatterns) {
|
2970
|
-
const files = await (0, import_glob.glob)(pattern);
|
2971
|
-
allFiles.push(...files);
|
2972
|
-
}
|
2973
|
-
await Promise.all(
|
2974
|
-
allFiles.map(async (file) => {
|
2975
|
-
await import(file).catch((error) => {
|
2976
|
-
logger.error(`Error importing cron job: ${file}`);
|
2977
|
-
logger.error(error);
|
2978
|
-
});
|
2979
|
-
})
|
2980
|
-
);
|
2981
|
-
}
|
2982
|
-
};
|
2983
|
-
|
2984
|
-
// src/server/server.ts
|
2985
|
-
var Server = class {
|
2986
|
-
isListening;
|
2987
|
-
wasInitialized;
|
2988
|
-
serverConnector;
|
2989
|
-
globalMiddlewares = [];
|
2990
|
-
options;
|
2991
|
-
controllerImportBlacklistedPaths = ["node_modules"];
|
2992
|
-
/**
|
2993
|
-
* The constructor for the server
|
2994
|
-
* @warning Routes will only be defined after calling the `listen` method so you're free to define middlewares before calling it
|
2995
|
-
* @param options - The options for the server
|
2996
|
-
* @param options.port - The port to listen on, defaults to 80
|
2997
|
-
* @param options.host - The hostname to listen on, defaults to 0.0.0.0
|
2998
|
-
* @param options.controllerPatterns - The patterns to match for controllers, defaults to an empty array
|
2999
|
-
* @param options.plugins - The plugins to apply to the server, by default no plugins are applied, plugins are applied in the order they are defined in the options
|
3000
|
-
* @param options.logger - The logger to use for the server, by default a default logger is used
|
3001
|
-
* @param options.tapOptions - Options fetch to the runtime server before the server is up and running
|
3002
|
-
*/
|
3003
|
-
constructor(options) {
|
3004
|
-
this.wasInitialized = false;
|
3005
|
-
this.options = {
|
3006
|
-
port: options?.port ?? 80,
|
3007
|
-
host: options?.host ?? "0.0.0.0",
|
3008
|
-
controllerPatterns: options?.controllerPatterns ?? [],
|
3009
|
-
plugins: options?.plugins ?? {},
|
3010
|
-
tapOptions: options?.tapOptions ?? {},
|
3011
|
-
swagger: options?.swagger ?? true
|
3012
|
-
};
|
3013
|
-
this.serverConnector = new ServerConnector({
|
3014
|
-
routes: [],
|
3015
|
-
port: this.options.port,
|
3016
|
-
host: this.options.host,
|
3017
|
-
tapOptions: this.options.tapOptions,
|
3018
|
-
runtime: runtime.type
|
3019
|
-
});
|
3020
|
-
this.use(bodyParser());
|
3021
|
-
this.isListening = false;
|
3022
|
-
}
|
3023
|
-
get url() {
|
3024
|
-
return this.serverConnector.url;
|
3025
|
-
}
|
3026
|
-
get port() {
|
3027
|
-
return this.serverConnector.port;
|
3028
|
-
}
|
3029
|
-
get host() {
|
3030
|
-
return this.serverConnector.host;
|
3031
|
-
}
|
3032
|
-
tmpDir(...append) {
|
3033
|
-
const baseTmpDir = "tmp";
|
3034
|
-
if (append) {
|
3035
|
-
return (0, import_node_path4.join)(baseTmpDir, ...append);
|
3036
|
-
}
|
3037
|
-
return (0, import_node_path4.join)(nativeCwd.getCwd(), baseTmpDir);
|
3038
|
-
}
|
3039
|
-
get(path, optionsOrHandler, maybeHandler) {
|
3040
|
-
const { middlewares, handler, swaggerOptions } = this.extractOptionsAndHandlerFromRouteRegistration(
|
3041
|
-
optionsOrHandler,
|
3042
|
-
maybeHandler
|
3043
|
-
);
|
3044
|
-
router.addOrUpdate("GET", path, middlewares, handler, swaggerOptions);
|
3045
|
-
}
|
3046
|
-
post(path, optionsOrHandler, maybeHandler) {
|
3047
|
-
const { middlewares, handler, swaggerOptions } = this.extractOptionsAndHandlerFromRouteRegistration(
|
3048
|
-
optionsOrHandler,
|
3049
|
-
maybeHandler
|
3050
|
-
);
|
3051
|
-
router.addOrUpdate("POST", path, middlewares, handler, swaggerOptions);
|
3052
|
-
}
|
3053
|
-
patch(path, optionsOrHandler, maybeHandler) {
|
3054
|
-
const { middlewares, handler, swaggerOptions } = this.extractOptionsAndHandlerFromRouteRegistration(
|
3055
|
-
optionsOrHandler,
|
3056
|
-
maybeHandler
|
3057
|
-
);
|
3058
|
-
router.addOrUpdate("PATCH", path, middlewares, handler, swaggerOptions);
|
3059
|
-
}
|
3060
|
-
put(path, optionsOrHandler, maybeHandler) {
|
3061
|
-
const { middlewares, handler, swaggerOptions } = this.extractOptionsAndHandlerFromRouteRegistration(
|
3062
|
-
optionsOrHandler,
|
3063
|
-
maybeHandler
|
3064
|
-
);
|
3065
|
-
router.addOrUpdate("PUT", path, middlewares, handler, swaggerOptions);
|
3066
|
-
}
|
3067
|
-
delete(path, optionsOrHandler, maybeHandler) {
|
3068
|
-
const { middlewares, handler, swaggerOptions } = this.extractOptionsAndHandlerFromRouteRegistration(
|
3069
|
-
optionsOrHandler,
|
3070
|
-
maybeHandler
|
3071
|
-
);
|
3072
|
-
router.addOrUpdate("DELETE", path, middlewares, handler, swaggerOptions);
|
3073
|
-
}
|
3074
|
-
getNodeServer() {
|
3075
|
-
if (runtime.type !== "node") {
|
3076
|
-
throw new Error(
|
3077
|
-
"Server is not using node runtime, you can't call `.getNodeServer()`"
|
3078
|
-
);
|
3079
|
-
}
|
3080
|
-
return this.serverConnector.getServer("node");
|
3081
|
-
}
|
3082
|
-
embed(key, value) {
|
3083
|
-
if (typeof key !== "string" || key.trim() === "") {
|
3084
|
-
throw new Error(
|
3085
|
-
`Invalid key provided to embed: ${key}. Key must be a non-empty string.`
|
3086
|
-
);
|
3087
|
-
}
|
3088
|
-
if (PROTECTED_KEYS.includes(key)) {
|
3089
|
-
throw new Error(
|
3090
|
-
`Cannot embed value with key '${key}' as it conflicts with a protected server property.`
|
3091
|
-
);
|
3092
|
-
}
|
3093
|
-
Object.defineProperty(this, key, {
|
3094
|
-
value,
|
3095
|
-
writable: false,
|
3096
|
-
configurable: true,
|
3097
|
-
enumerable: true
|
3098
|
-
});
|
3099
|
-
}
|
3100
|
-
exit(code = 0) {
|
3101
|
-
switch (runtime.type) {
|
3102
|
-
case "bun":
|
3103
|
-
case "node":
|
3104
|
-
process.exit(code);
|
3105
|
-
case "deno":
|
3106
|
-
Deno.exit(code);
|
3107
|
-
default:
|
3108
|
-
throw new Error(`Unsupported runtime: ${runtime.type}`);
|
3109
|
-
}
|
3110
|
-
}
|
3111
|
-
on(event, cb) {
|
3112
|
-
switch (runtime.type) {
|
3113
|
-
case "bun":
|
3114
|
-
case "node":
|
3115
|
-
process.on(event, cb);
|
3116
|
-
break;
|
3117
|
-
case "deno":
|
3118
|
-
Deno.addSignalListener(event, cb);
|
3119
|
-
break;
|
3120
|
-
default:
|
3121
|
-
throw new Error(
|
3122
|
-
`Unsupported runtime: ${runtime.type}, only node, bun and deno are supported`
|
3123
|
-
);
|
3124
|
-
}
|
3125
|
-
}
|
3126
|
-
use(...middlewares) {
|
3127
|
-
this.globalMiddlewares.push(...middlewares);
|
3128
|
-
}
|
3129
|
-
setErrorHandler(errorHandler) {
|
3130
|
-
this.globalMiddlewares.unshift(async (req, res, next) => {
|
3131
|
-
try {
|
3132
|
-
await next();
|
3133
|
-
} catch (error) {
|
3134
|
-
await errorHandler?.(req, res, next, error);
|
3135
|
-
}
|
3136
|
-
});
|
3137
|
-
}
|
3138
|
-
setGlobalCronErrorHandler(globalErrorHandler) {
|
3139
|
-
CronService.globalErrorHandler = globalErrorHandler;
|
3140
|
-
}
|
3141
|
-
startRegisteredCrons = async (cronJobPatterns, onStart) => {
|
3142
|
-
if (cronJobPatterns?.length) {
|
3143
|
-
await CronService.massiveImportCronJobs(cronJobPatterns);
|
3144
|
-
}
|
3145
|
-
CronService.run().then(() => {
|
3146
|
-
onStart?.();
|
3147
|
-
});
|
3148
|
-
};
|
3149
|
-
listen(cb) {
|
3150
|
-
if (this.isListening) {
|
3151
|
-
throw new Error(
|
3152
|
-
"Server is already listening, you can't call `.listen()` multiple times"
|
3153
|
-
);
|
3154
|
-
}
|
3155
|
-
this.bootstrap().then(() => {
|
3156
|
-
this.serverConnector.listen();
|
3157
|
-
this.isListening = true;
|
3158
|
-
if (this.options.swagger) {
|
3159
|
-
swagger(this.options.swagger);
|
3160
|
-
}
|
3161
|
-
cb?.({
|
3162
|
-
port: this.port,
|
3163
|
-
host: this.host,
|
3164
|
-
url: this.url
|
3165
|
-
});
|
3166
|
-
});
|
3167
|
-
}
|
3168
|
-
async close() {
|
3169
|
-
await this.serverConnector.close();
|
3170
|
-
this.isListening = false;
|
3171
|
-
}
|
3172
|
-
/**
|
3173
|
-
* Returns a mock server instance that can be used to test the server without starting it
|
3174
|
-
* It will import the controllers and apply the plugins to the mock server
|
3175
|
-
*/
|
3176
|
-
async getMockServer() {
|
3177
|
-
await this.bootstrap();
|
3178
|
-
return new MockServer(this);
|
3179
|
-
}
|
3180
|
-
async importControllers() {
|
3181
|
-
const controllerPatterns = this.options.controllerPatterns;
|
3182
|
-
let controllerPaths = await Promise.all(
|
3183
|
-
controllerPatterns.map(async (pattern) => {
|
3184
|
-
return (0, import_glob2.glob)(pattern, {
|
3185
|
-
cwd: nativeCwd.getCwd()
|
3186
|
-
});
|
3187
|
-
})
|
3188
|
-
).then((paths) => paths.flat());
|
3189
|
-
controllerPaths = controllerPaths.flat();
|
3190
|
-
controllerPaths = controllerPaths.filter(
|
3191
|
-
(path) => !this.controllerImportBlacklistedPaths.some(
|
3192
|
-
(blacklistedPath) => path.includes(blacklistedPath)
|
3193
|
-
)
|
3194
|
-
);
|
3195
|
-
logger.debug(`Found ${controllerPaths.length} controllers to import`);
|
3196
|
-
await Promise.all(
|
3197
|
-
controllerPaths.map(async (controllerPath) => {
|
3198
|
-
logger.debug(`Importing controller ${controllerPath}`);
|
3199
|
-
await import(controllerPath).catch((err) => {
|
3200
|
-
logger.error(`Error importing controller ${controllerPath}: ${err}`);
|
3201
|
-
});
|
3202
|
-
})
|
3203
|
-
);
|
3204
|
-
}
|
3205
|
-
extractOptionsAndHandlerFromRouteRegistration(optionsOrHandler, maybeHandler) {
|
3206
|
-
if (typeof optionsOrHandler === "function") {
|
3207
|
-
return {
|
3208
|
-
middlewares: [],
|
3209
|
-
handler: optionsOrHandler,
|
3210
|
-
swaggerOptions: void 0
|
3211
|
-
};
|
3212
|
-
}
|
3213
|
-
const options = optionsOrHandler;
|
3214
|
-
const middlewares = Array.isArray(options.middlewares) ? options.middlewares : options.middlewares ? [options.middlewares] : [];
|
3215
|
-
return {
|
3216
|
-
middlewares,
|
3217
|
-
handler: maybeHandler,
|
3218
|
-
swaggerOptions: options.swagger
|
3219
|
-
};
|
3220
|
-
}
|
3221
|
-
applyPlugins(plugins) {
|
3222
|
-
Object.entries(plugins).forEach(([pluginName, pluginOptions]) => {
|
3223
|
-
switch (pluginName) {
|
3224
|
-
case "cors":
|
3225
|
-
this.use(cors(pluginOptions));
|
3226
|
-
break;
|
3227
|
-
case "json":
|
3228
|
-
this.use(json(pluginOptions));
|
3229
|
-
break;
|
3230
|
-
case "static":
|
3231
|
-
this.use(serveStatic(pluginOptions));
|
3232
|
-
break;
|
3233
|
-
case "fileParser":
|
3234
|
-
this.use(fileParser(pluginOptions));
|
3235
|
-
break;
|
3236
|
-
case "helmet":
|
3237
|
-
this.use(helmet(pluginOptions));
|
3238
|
-
break;
|
3239
|
-
case "cookie":
|
3240
|
-
this.use(cookie(pluginOptions));
|
3241
|
-
break;
|
3242
|
-
case "log":
|
3243
|
-
this.use(log(pluginOptions));
|
3244
|
-
break;
|
3245
|
-
case "rateLimiter":
|
3246
|
-
const { keyOptions, storageOptions } = pluginOptions;
|
3247
|
-
this.use(rateLimiter(keyOptions, storageOptions));
|
3248
|
-
break;
|
3249
|
-
case "urlencoded":
|
3250
|
-
this.use(urlencoded(pluginOptions));
|
3251
|
-
break;
|
3252
|
-
default:
|
3253
|
-
logger.warn(`Unknown plugin ${pluginName}`);
|
3254
|
-
break;
|
3255
|
-
}
|
3256
|
-
});
|
3257
|
-
}
|
3258
|
-
/**
|
3259
|
-
* Initializes the server by importing the controllers and applying the plugins, it's idempotent, it will not re-import the controllers or apply the plugins if the server was already initialized (e.g. mockServer init)
|
3260
|
-
* @internal
|
3261
|
-
*/
|
3262
|
-
async bootstrap() {
|
3263
|
-
if (this.wasInitialized) {
|
3264
|
-
return;
|
3265
|
-
}
|
3266
|
-
await this.importControllers();
|
3267
|
-
this.applyPlugins(this.options.plugins);
|
3268
|
-
this.registerNotFoundRoutes();
|
3269
|
-
if (this.globalMiddlewares.length) {
|
3270
|
-
router.applyGlobalMiddlewaresToAllRoutes(this.globalMiddlewares);
|
3271
|
-
}
|
3272
|
-
this.wasInitialized = true;
|
3273
|
-
}
|
3274
|
-
/**
|
3275
|
-
* Registers a not found route for all routes that are not defined
|
3276
|
-
* @internal
|
3277
|
-
*/
|
3278
|
-
registerNotFoundRoutes() {
|
3279
|
-
router.addOrUpdate(
|
3280
|
-
"GET",
|
3281
|
-
"*",
|
3282
|
-
[],
|
3283
|
-
(req, res) => {
|
3284
|
-
const notFoundError = new RouteNotFoundError(req.url, req.method);
|
3285
|
-
res.notFound({
|
3286
|
-
...errorFactory(notFoundError)
|
3287
|
-
});
|
3288
|
-
},
|
3289
|
-
{
|
3290
|
-
excludeFromSwagger: true
|
3291
|
-
}
|
3292
|
-
);
|
3293
|
-
router.addOrUpdate(
|
3294
|
-
"POST",
|
3295
|
-
"*",
|
3296
|
-
[],
|
3297
|
-
(req, res) => {
|
3298
|
-
const notFoundError = new RouteNotFoundError(req.url, req.method);
|
3299
|
-
res.notFound({
|
3300
|
-
...errorFactory(notFoundError)
|
3301
|
-
});
|
3302
|
-
},
|
3303
|
-
{
|
3304
|
-
excludeFromSwagger: true
|
3305
|
-
}
|
3306
|
-
);
|
3307
|
-
router.addOrUpdate(
|
3308
|
-
"PUT",
|
3309
|
-
"*",
|
3310
|
-
[],
|
3311
|
-
(req, res) => {
|
3312
|
-
const notFoundError = new RouteNotFoundError(req.url, req.method);
|
3313
|
-
res.notFound({
|
3314
|
-
...errorFactory(notFoundError)
|
3315
|
-
});
|
3316
|
-
},
|
3317
|
-
{
|
3318
|
-
excludeFromSwagger: true
|
3319
|
-
}
|
3320
|
-
);
|
3321
|
-
router.addOrUpdate(
|
3322
|
-
"PATCH",
|
3323
|
-
"*",
|
3324
|
-
[],
|
3325
|
-
(req, res) => {
|
3326
|
-
const notFoundError = new RouteNotFoundError(req.url, req.method);
|
3327
|
-
res.notFound({
|
3328
|
-
...errorFactory(notFoundError)
|
3329
|
-
});
|
3330
|
-
},
|
3331
|
-
{
|
3332
|
-
excludeFromSwagger: true
|
3333
|
-
}
|
3334
|
-
);
|
3335
|
-
router.addOrUpdate(
|
3336
|
-
"DELETE",
|
3337
|
-
"*",
|
3338
|
-
[],
|
3339
|
-
(req, res) => {
|
3340
|
-
const notFoundError = new RouteNotFoundError(req.url, req.method);
|
3341
|
-
res.notFound({
|
3342
|
-
...errorFactory(notFoundError)
|
3343
|
-
});
|
3344
|
-
},
|
3345
|
-
{
|
3346
|
-
excludeFromSwagger: true
|
3347
|
-
}
|
3348
|
-
);
|
3349
|
-
}
|
3350
|
-
};
|
3351
|
-
|
3352
|
-
// src/plugins/base_plugin.ts
|
3353
|
-
var BasePlugin = class {
|
3354
|
-
};
|
3355
|
-
|
3356
|
-
// src/index.ts
|
3357
|
-
var router2 = router;
|
3358
|
-
// Annotate the CommonJS export names for ESM import in node:
|
3359
|
-
0 && (module.exports = {
|
3360
|
-
BasePlugin,
|
3361
|
-
Request,
|
3362
|
-
Response,
|
3363
|
-
Server,
|
3364
|
-
controller,
|
3365
|
-
cookie,
|
3366
|
-
cors,
|
3367
|
-
del,
|
3368
|
-
fileParser,
|
3369
|
-
get,
|
3370
|
-
getContentType,
|
3371
|
-
helmet,
|
3372
|
-
json,
|
3373
|
-
log,
|
3374
|
-
middleware,
|
3375
|
-
patch,
|
3376
|
-
post,
|
3377
|
-
put,
|
3378
|
-
rateLimiter,
|
3379
|
-
router,
|
3380
|
-
serveStatic,
|
3381
|
-
urlencoded,
|
3382
|
-
validate
|
3383
|
-
});
|
3384
|
-
//# sourceMappingURL=index.cjs.map
|