balda-js 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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