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