@tahminator/sapling 1.5.27 → 1.5.28-beta.7e624925

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.
Files changed (51) hide show
  1. package/dist/index.cjs +755 -0
  2. package/dist/index.d.cts +521 -0
  3. package/dist/index.d.mts +521 -0
  4. package/dist/index.mjs +701 -0
  5. package/package.json +15 -10
  6. package/dist/eslint.config.d.ts +0 -2
  7. package/dist/eslint.config.js +0 -38
  8. package/dist/exclusions.d.ts +0 -5
  9. package/dist/exclusions.js +0 -6
  10. package/dist/index.d.ts +0 -1
  11. package/dist/index.js +0 -1
  12. package/dist/lib/weakmap.d.ts +0 -15
  13. package/dist/lib/weakmap.js +0 -77
  14. package/dist/src/__test__/first.d.ts +0 -6
  15. package/dist/src/__test__/first.js +0 -20
  16. package/dist/src/__test__/second.d.ts +0 -6
  17. package/dist/src/__test__/second.js +0 -20
  18. package/dist/src/annotation/controller.d.ts +0 -21
  19. package/dist/src/annotation/controller.js +0 -78
  20. package/dist/src/annotation/index.d.ts +0 -4
  21. package/dist/src/annotation/index.js +0 -4
  22. package/dist/src/annotation/injectable.d.ts +0 -25
  23. package/dist/src/annotation/injectable.js +0 -72
  24. package/dist/src/annotation/middleware.d.ts +0 -9
  25. package/dist/src/annotation/middleware.js +0 -11
  26. package/dist/src/annotation/route.d.ts +0 -47
  27. package/dist/src/annotation/route.js +0 -77
  28. package/dist/src/enum/http.d.ts +0 -68
  29. package/dist/src/enum/http.js +0 -71
  30. package/dist/src/enum/index.d.ts +0 -1
  31. package/dist/src/enum/index.js +0 -1
  32. package/dist/src/helper/error.d.ts +0 -10
  33. package/dist/src/helper/error.js +0 -19
  34. package/dist/src/helper/index.d.ts +0 -4
  35. package/dist/src/helper/index.js +0 -4
  36. package/dist/src/helper/redirect.d.ts +0 -14
  37. package/dist/src/helper/redirect.js +0 -19
  38. package/dist/src/helper/response.d.ts +0 -68
  39. package/dist/src/helper/response.js +0 -90
  40. package/dist/src/helper/sapling.d.ts +0 -101
  41. package/dist/src/helper/sapling.js +0 -153
  42. package/dist/src/html/404.d.ts +0 -4
  43. package/dist/src/html/404.js +0 -14
  44. package/dist/src/html/index.d.ts +0 -1
  45. package/dist/src/html/index.js +0 -1
  46. package/dist/src/index.d.ts +0 -5
  47. package/dist/src/index.js +0 -5
  48. package/dist/src/types.d.ts +0 -21
  49. package/dist/src/types.js +0 -11
  50. package/dist/vite.config.d.ts +0 -2
  51. package/dist/vite.config.js +0 -18
package/dist/index.cjs ADDED
@@ -0,0 +1,755 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let express = require("express");
25
+ express = __toESM(express);
26
+ //#region src/html/404.ts
27
+ /**
28
+ * Default Express.js 404 error page, as a string.
29
+ */
30
+ const Html404ErrorPage = (error) => `<!DOCTYPE html>
31
+ <html lang="en">
32
+ <head>
33
+ <meta charset="utf-8">
34
+ <title>Error</title>
35
+ </head>
36
+ <body>
37
+ <pre>${error}</pre>
38
+ </body>
39
+ </html>
40
+ `;
41
+ //#endregion
42
+ //#region src/helper/redirect.ts
43
+ /**
44
+ * Generic HTTP redirect wrapped modeled after Spring's `RedirectView`.
45
+ *
46
+ * You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
47
+ */
48
+ var RedirectView = class RedirectView {
49
+ constructor(url) {
50
+ this._url = url;
51
+ }
52
+ getUrl() {
53
+ return this._url;
54
+ }
55
+ /**
56
+ * Instantiate `RedirectView` with the given `url`.
57
+ */
58
+ static redirect(url) {
59
+ return new RedirectView(url);
60
+ }
61
+ };
62
+ //#endregion
63
+ //#region src/enum/http.ts
64
+ /**
65
+ * Enum of every valid HTTP status code mapped to a specific enum member.
66
+ *
67
+ * @see {@link ResponseEntity}
68
+ */
69
+ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
70
+ HttpStatus[HttpStatus["CONTINUE"] = 100] = "CONTINUE";
71
+ HttpStatus[HttpStatus["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
72
+ HttpStatus[HttpStatus["PROCESSING"] = 102] = "PROCESSING";
73
+ HttpStatus[HttpStatus["EARLY_HINTS"] = 103] = "EARLY_HINTS";
74
+ HttpStatus[HttpStatus["OK"] = 200] = "OK";
75
+ HttpStatus[HttpStatus["CREATED"] = 201] = "CREATED";
76
+ HttpStatus[HttpStatus["ACCEPTED"] = 202] = "ACCEPTED";
77
+ HttpStatus[HttpStatus["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION";
78
+ HttpStatus[HttpStatus["NO_CONTENT"] = 204] = "NO_CONTENT";
79
+ HttpStatus[HttpStatus["RESET_CONTENT"] = 205] = "RESET_CONTENT";
80
+ HttpStatus[HttpStatus["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT";
81
+ HttpStatus[HttpStatus["MULTI_STATUS"] = 207] = "MULTI_STATUS";
82
+ HttpStatus[HttpStatus["ALREADY_REPORTED"] = 208] = "ALREADY_REPORTED";
83
+ HttpStatus[HttpStatus["IM_USED"] = 226] = "IM_USED";
84
+ HttpStatus[HttpStatus["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES";
85
+ HttpStatus[HttpStatus["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
86
+ HttpStatus[HttpStatus["FOUND"] = 302] = "FOUND";
87
+ HttpStatus[HttpStatus["SEE_OTHER"] = 303] = "SEE_OTHER";
88
+ HttpStatus[HttpStatus["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
89
+ HttpStatus[HttpStatus["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT";
90
+ HttpStatus[HttpStatus["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT";
91
+ HttpStatus[HttpStatus["BAD_REQUEST"] = 400] = "BAD_REQUEST";
92
+ HttpStatus[HttpStatus["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
93
+ HttpStatus[HttpStatus["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
94
+ HttpStatus[HttpStatus["FORBIDDEN"] = 403] = "FORBIDDEN";
95
+ HttpStatus[HttpStatus["NOT_FOUND"] = 404] = "NOT_FOUND";
96
+ HttpStatus[HttpStatus["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
97
+ HttpStatus[HttpStatus["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE";
98
+ HttpStatus[HttpStatus["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED";
99
+ HttpStatus[HttpStatus["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
100
+ HttpStatus[HttpStatus["CONFLICT"] = 409] = "CONFLICT";
101
+ HttpStatus[HttpStatus["GONE"] = 410] = "GONE";
102
+ HttpStatus[HttpStatus["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED";
103
+ HttpStatus[HttpStatus["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
104
+ HttpStatus[HttpStatus["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
105
+ HttpStatus[HttpStatus["URI_TOO_LONG"] = 414] = "URI_TOO_LONG";
106
+ HttpStatus[HttpStatus["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
107
+ HttpStatus[HttpStatus["REQUESTED_RANGE_NOT_SATISFIABLE"] = 416] = "REQUESTED_RANGE_NOT_SATISFIABLE";
108
+ HttpStatus[HttpStatus["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED";
109
+ HttpStatus[HttpStatus["I_AM_A_TEAPOT"] = 418] = "I_AM_A_TEAPOT";
110
+ HttpStatus[HttpStatus["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
111
+ HttpStatus[HttpStatus["LOCKED"] = 423] = "LOCKED";
112
+ HttpStatus[HttpStatus["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY";
113
+ HttpStatus[HttpStatus["TOO_EARLY"] = 425] = "TOO_EARLY";
114
+ HttpStatus[HttpStatus["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
115
+ HttpStatus[HttpStatus["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
116
+ HttpStatus[HttpStatus["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
117
+ HttpStatus[HttpStatus["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE";
118
+ HttpStatus[HttpStatus["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS";
119
+ HttpStatus[HttpStatus["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
120
+ HttpStatus[HttpStatus["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
121
+ HttpStatus[HttpStatus["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
122
+ HttpStatus[HttpStatus["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
123
+ HttpStatus[HttpStatus["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
124
+ HttpStatus[HttpStatus["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED";
125
+ HttpStatus[HttpStatus["VARIANT_ALSO_NEGOTIATES"] = 506] = "VARIANT_ALSO_NEGOTIATES";
126
+ HttpStatus[HttpStatus["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE";
127
+ HttpStatus[HttpStatus["LOOP_DETECTED"] = 508] = "LOOP_DETECTED";
128
+ HttpStatus[HttpStatus["BANDWIDTH_LIMIT_EXCEEDED"] = 509] = "BANDWIDTH_LIMIT_EXCEEDED";
129
+ HttpStatus[HttpStatus["NOT_EXTENDED"] = 510] = "NOT_EXTENDED";
130
+ HttpStatus[HttpStatus["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED";
131
+ return HttpStatus;
132
+ }({});
133
+ //#endregion
134
+ //#region src/helper/response.ts
135
+ /**
136
+ * Generic HTTP response wrapper modeled after Spring's `ResponseEntity`.
137
+ *
138
+ * Provides status code, headers, and an optional typed body.
139
+ * The body is serialized through `Sapling.serialize`.
140
+ *
141
+ * @typeParam T - the type of the response body
142
+ */
143
+ var ResponseEntity = class {
144
+ constructor(body, headers = {}, statusCode = 200) {
145
+ this._headers = {};
146
+ this._body = body;
147
+ this._headers = headers;
148
+ this._statusCode = statusCode;
149
+ }
150
+ /**
151
+ * Create a builder with a 200 status code.
152
+ *
153
+ * @example```ts
154
+ * return ResponseEntity.ok().body({ success: true });
155
+ * ```
156
+ */
157
+ static ok() {
158
+ return new ResponseEntityBuilder(200);
159
+ }
160
+ /**
161
+ * Create a builder with a custom status code.
162
+ *
163
+ * @example```ts
164
+ * return ResponseEntity.status(HttpStatus.BAD_REQUEST).body({ success: false });
165
+ * ```
166
+ *
167
+ * @see {@link HttpStatus}
168
+ */
169
+ static status(statusCode) {
170
+ return new ResponseEntityBuilder(statusCode);
171
+ }
172
+ /**
173
+ * Return status code.
174
+ */
175
+ getStatusCode() {
176
+ return this._statusCode;
177
+ }
178
+ /**
179
+ * Return headers.
180
+ */
181
+ getHeaders() {
182
+ return this._headers;
183
+ }
184
+ /**
185
+ * Return the response body.
186
+ */
187
+ getBody() {
188
+ return this._body;
189
+ }
190
+ };
191
+ /**
192
+ * Builder for {@link ResponseEntity}.
193
+ *
194
+ * Forces the status code to be set first, then headers and body,
195
+ * ensuring type safety when constructing the response.
196
+ */
197
+ var ResponseEntityBuilder = class {
198
+ constructor(statusCode) {
199
+ this._headers = {};
200
+ this._statusCode = statusCode;
201
+ }
202
+ /**
203
+ * Set all headers as an object with keys and values.
204
+ */
205
+ headers(headers) {
206
+ this._headers = headers;
207
+ return this;
208
+ }
209
+ /**
210
+ * Add/override a single key and value to the headers.
211
+ */
212
+ setHeader(key, value) {
213
+ this._headers[key] = value;
214
+ return this;
215
+ }
216
+ /**
217
+ * Set the response body.
218
+ */
219
+ body(body) {
220
+ return new ResponseEntity(body, this._headers, this._statusCode);
221
+ }
222
+ };
223
+ //#endregion
224
+ //#region src/helper/error/responsestatus.ts
225
+ /**
226
+ * Ensure that you define a middleware that can handle this error.
227
+ *
228
+ * @see {@link Sapling.loadResponseStatusErrorMiddleware}
229
+ */
230
+ var ResponseStatusError = class ResponseStatusError extends Error {
231
+ constructor(status, message) {
232
+ super(message ?? "Something went wrong.");
233
+ this.status = status;
234
+ Object.setPrototypeOf(this, new.target.prototype);
235
+ this.name = `HttpError(${HttpStatus[status]})`;
236
+ if (Error.captureStackTrace) Error.captureStackTrace(this, ResponseStatusError);
237
+ }
238
+ };
239
+ //#endregion
240
+ //#region src/helper/sapling.ts
241
+ const settings = {
242
+ serialize: JSON.stringify,
243
+ deserialize: JSON.parse
244
+ };
245
+ /**
246
+ * Collection of utility functions which are essential for Sapling to function.
247
+ */
248
+ var Sapling = class Sapling {
249
+ /**
250
+ * If you would prefer to manually resolve your controllers instead, call resolve
251
+ * on the controller class.
252
+ *
253
+ * @example```ts
254
+ * import { Sapling } from "@tahminator/sapling";
255
+ * import TestController from "./path/to/test.controller";
256
+ *
257
+ * const app = express();
258
+ *
259
+ * const router = Sapling.resolve(TestController);
260
+ * app.use(router);
261
+ * ```
262
+ */
263
+ static resolve(clazz) {
264
+ const router = _ControllerRegistry.get(clazz);
265
+ if (!router) throw new Error("Controller cannot be found");
266
+ return router;
267
+ }
268
+ /**
269
+ * Register this function as a middleware in order to utilize Sapling's `deserialize` function.
270
+ *
271
+ * @example```ts
272
+ * import { Sapling } from "@tahminator/sapling";
273
+ * import express from "express";
274
+ *
275
+ * const app = express();
276
+ *
277
+ * app.use(Sapling.json());
278
+ * ```
279
+ */
280
+ static json() {
281
+ return (request, _response, next) => {
282
+ try {
283
+ if (!request.body) return next();
284
+ if (request.headers["content-type"] !== "application/json") return next();
285
+ if (typeof request.body === "string") request.body = Sapling.deserialize(request.body);
286
+ else if (typeof request.body === "object") {
287
+ const raw = JSON.stringify(request.body);
288
+ request.body = Sapling.deserialize(raw);
289
+ }
290
+ next();
291
+ } catch (err) {
292
+ next(err);
293
+ }
294
+ };
295
+ }
296
+ /**
297
+ * Register your application with all the necessary middlewares and logics for Sapling to function.
298
+ *
299
+ * @example```ts
300
+ * import { Sapling } from "@tahminator/sapling";
301
+ * import express from "express";
302
+ *
303
+ * const app = express();
304
+ *
305
+ * app.registerApp(app);
306
+ * ```
307
+ */
308
+ static registerApp(app) {
309
+ app.use(express.default.text({ type: "application/json" }));
310
+ app.use(Sapling.json());
311
+ }
312
+ /**
313
+ * Register a middleware that will handle {@link ResponseStatusError}.
314
+ *
315
+ * This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
316
+ * You may still define middleware to handle all other errors in a separate `app.use` call.
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * import express from "express";
321
+ * import { Sapling } from "@tahminator/sapling";
322
+ *
323
+ * const app = express();
324
+ *
325
+ * Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
326
+ * // `err` is guaranteed to be of type ResponseStatusError
327
+ * res.status(err.status).json({
328
+ * success: false,
329
+ * message: err.message,
330
+ * });
331
+ * });
332
+ * ```
333
+ */
334
+ static loadResponseStatusErrorMiddleware(app, fn) {
335
+ app.use(((err, req, res, next) => {
336
+ if (err instanceof ResponseStatusError) fn(err, req, res, next);
337
+ else next(err);
338
+ }));
339
+ }
340
+ /**
341
+ * Serialize a value into a JSON string.
342
+ *
343
+ * This function is used in {@link ResponseEntity} to serialize the `body`.
344
+ *
345
+ * Use `setSerializeFn` to override underlying implementation.
346
+ *
347
+ * @defaultValue `JSON.stringify`
348
+ */
349
+ static serialize(value) {
350
+ return settings.serialize(value);
351
+ }
352
+ /**
353
+ * Replace the function used for `serialize`.
354
+ */
355
+ static setSerializeFn(fn) {
356
+ settings.serialize = fn;
357
+ }
358
+ /**
359
+ * De-serialize a JSON string back to a JavaScript object.
360
+ *
361
+ * This function is used to de-serialize a string into a `body`.
362
+ *
363
+ * Use `setDeserializeFn` to override underlying implementation.
364
+ *
365
+ * @defaultValue `JSON.parse`
366
+ */
367
+ static deserialize(value) {
368
+ return settings.deserialize(value);
369
+ }
370
+ /**
371
+ * Replace the function used for `deserialize`
372
+ */
373
+ static setDeserializeFn(fn) {
374
+ settings.deserialize = fn;
375
+ }
376
+ };
377
+ //#endregion
378
+ //#region src/types.ts
379
+ const methodResolve = {
380
+ GET: "get",
381
+ PUT: "put",
382
+ POST: "post",
383
+ DELETE: "delete",
384
+ OPTIONS: "options",
385
+ PATCH: "patch",
386
+ HEAD: "head",
387
+ USE: "use"
388
+ };
389
+ //#endregion
390
+ //#region lib/weakmap.ts
391
+ /**
392
+ * WeakMap that is iterable.
393
+ */
394
+ var IterableWeakMap = class IterableWeakMap {
395
+ #weakMap = /* @__PURE__ */ new WeakMap();
396
+ #refSet = /* @__PURE__ */ new Set();
397
+ #finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
398
+ static #cleanup(heldValue) {
399
+ heldValue.set.delete(heldValue.ref);
400
+ }
401
+ constructor(iterable) {
402
+ if (iterable) for (const [key, value] of iterable) this.set(key, value);
403
+ }
404
+ set(key, value) {
405
+ const ref = new WeakRef(key);
406
+ this.#weakMap.set(key, {
407
+ value,
408
+ ref
409
+ });
410
+ this.#refSet.add(ref);
411
+ this.#finalizationGroup.register(key, {
412
+ set: this.#refSet,
413
+ ref
414
+ }, ref);
415
+ return this;
416
+ }
417
+ get(key) {
418
+ return this.#weakMap.get(key)?.value;
419
+ }
420
+ delete(key) {
421
+ const entry = this.#weakMap.get(key);
422
+ if (!entry) return false;
423
+ this.#weakMap.delete(key);
424
+ this.#refSet.delete(entry.ref);
425
+ this.#finalizationGroup.unregister(entry.ref);
426
+ return true;
427
+ }
428
+ *[Symbol.iterator]() {
429
+ for (const ref of this.#refSet) {
430
+ const key = ref.deref();
431
+ if (!key) continue;
432
+ const entry = this.#weakMap.get(key);
433
+ if (entry) yield [key, entry.value];
434
+ }
435
+ }
436
+ entries() {
437
+ return this[Symbol.iterator]();
438
+ }
439
+ *keys() {
440
+ for (const [key] of this) yield key;
441
+ }
442
+ *values() {
443
+ for (const [, value] of this) yield value;
444
+ }
445
+ forEach(callback, thisArg) {
446
+ for (const [key, value] of this) callback.call(thisArg, value, key, this);
447
+ }
448
+ };
449
+ //#endregion
450
+ //#region src/annotation/injectable.ts
451
+ const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
452
+ const _InjectableDeps = new IterableWeakMap();
453
+ /**
454
+ * Mark the class as an injectable to be handled by Sapling. The class can now be
455
+ * be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
456
+ *
457
+ * @argument deps - An optional array to define any dependencies that this class may require.
458
+ */
459
+ function Injectable(deps = []) {
460
+ return function(target) {
461
+ _InjectableRegistry.set(target, null);
462
+ _InjectableDeps.set(target, deps);
463
+ };
464
+ }
465
+ /**
466
+ * Resolves and instantiates a class along with all of it's transitive dependencies.
467
+ *
468
+ * Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
469
+ * in a correct order.
470
+ *
471
+ * When `resolve` is first called (usually during controller registration),
472
+ * it will compute the dependency graph of all `@Injectable` classes and instantiates
473
+ * them in the correct order.
474
+ *
475
+ * Subsequent calls to dependencies that have already been resolved are cached, so they will
476
+ * re-use the created singletons instead of re-instantiation.
477
+ */
478
+ function _resolve(ctor) {
479
+ const inDegree = /* @__PURE__ */ new Map();
480
+ const graph = /* @__PURE__ */ new Map();
481
+ _InjectableDeps.forEach((deps, node) => {
482
+ inDegree.set(node, inDegree.get(node) || 0);
483
+ deps.forEach((dep) => {
484
+ if (dep === void 0) throw new Error(`There is an @Injectable (${node.name}) which has a dependency that cannot be found. This is likely caused by a circular dependency.`);
485
+ inDegree.set(dep, inDegree.get(dep) || 0);
486
+ inDegree.set(node, inDegree.get(node) + 1);
487
+ if (!graph.has(dep)) graph.set(dep, []);
488
+ graph.get(dep).push(node);
489
+ });
490
+ });
491
+ const queue = [];
492
+ inDegree.forEach((deg, node) => {
493
+ if (deg === 0) queue.push(node);
494
+ });
495
+ while (queue.length) {
496
+ const current = queue.shift();
497
+ if (!_InjectableRegistry.get(current)) {
498
+ const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
499
+ _InjectableRegistry.set(current, instance);
500
+ }
501
+ (graph.get(current) || []).forEach((neighbor) => {
502
+ inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
503
+ if (inDegree.get(neighbor) === 0) queue.push(neighbor);
504
+ });
505
+ }
506
+ if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
507
+ return _InjectableRegistry.get(ctor);
508
+ }
509
+ //#endregion
510
+ //#region src/helper/error/exception.ts
511
+ /**
512
+ * This error should be thrown when some data cannot be parsed by a given schema.
513
+ */
514
+ var ParserError = class ParserError extends ResponseStatusError {
515
+ constructor(location, issues, vendor) {
516
+ super(400, ParserError.formatMessage(location, issues, vendor));
517
+ Object.setPrototypeOf(this, new.target.prototype);
518
+ }
519
+ static formatMessage(location, issues, vendor) {
520
+ const formatted = issues.map((i) => {
521
+ const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
522
+ return path ? `${path}: ${i.message}` : i.message;
523
+ }).join("; ");
524
+ return `${vendor} failed to parse ${(() => {
525
+ switch (location) {
526
+ case "reqbody": return "request body";
527
+ case "reqparams": return "request params";
528
+ case "reqquery": return "request query";
529
+ }
530
+ })()}: ${formatted}`;
531
+ }
532
+ };
533
+ //#endregion
534
+ //#region src/annotation/request.ts
535
+ const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
536
+ function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
537
+ const byFn = (() => {
538
+ const fn = _requestSchemaStore.get(ctor);
539
+ if (fn) return fn;
540
+ const newFn = /* @__PURE__ */ new Map();
541
+ _requestSchemaStore.set(ctor, newFn);
542
+ return newFn;
543
+ })();
544
+ const existing = byFn.get(fnName);
545
+ if (existing) return existing;
546
+ const created = {};
547
+ byFn.set(fnName, created);
548
+ return created;
549
+ }
550
+ function _setOnce(def, key, schema, fnName) {
551
+ if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
552
+ def[key] = schema;
553
+ }
554
+ function RequestBody(schema) {
555
+ return (target, propertyKey) => {
556
+ const ctor = target.constructor;
557
+ const fnName = String(propertyKey);
558
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
559
+ };
560
+ }
561
+ function RequestParam(schema) {
562
+ return (target, propertyKey) => {
563
+ const ctor = target.constructor;
564
+ const fnName = String(propertyKey);
565
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
566
+ };
567
+ }
568
+ function RequestQuery(schema) {
569
+ return (target, propertyKey) => {
570
+ const ctor = target.constructor;
571
+ const fnName = String(propertyKey);
572
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
573
+ };
574
+ }
575
+ function _getRequestSchemas(ctor, fnName) {
576
+ return _requestSchemaStore.get(ctor)?.get(fnName);
577
+ }
578
+ async function _parseOrThrow(schema, input, kind) {
579
+ const result = await schema["~standard"].validate(input);
580
+ if (result.issues) throw new ParserError(kind, result.issues, schema["~standard"].vendor);
581
+ return result.value;
582
+ }
583
+ //#endregion
584
+ //#region src/annotation/route.ts
585
+ const _routeStore = /* @__PURE__ */ new WeakMap();
586
+ /**
587
+ * Custom annotation that will store all routes inside of a map,
588
+ * which can then be used to initialize all the routes to the router.
589
+ */
590
+ function _Route({ method, path = "" }) {
591
+ return (target, propertyKey) => {
592
+ const ctor = target.constructor;
593
+ const list = _routeStore.get(ctor) ?? [];
594
+ list.push({
595
+ method,
596
+ path: path ?? "",
597
+ fnName: String(propertyKey)
598
+ });
599
+ _routeStore.set(ctor, list);
600
+ };
601
+ }
602
+ /**
603
+ * Register GET route on the given path (default "") for the given controller.
604
+ */
605
+ const GET = (path = "") => _Route({
606
+ method: "GET",
607
+ path
608
+ });
609
+ /**
610
+ * Register POST route on the given path (default "") for the given controller.
611
+ */
612
+ const POST = (path = "") => _Route({
613
+ method: "POST",
614
+ path
615
+ });
616
+ /**
617
+ * Register PUT route on the given path (default "") for the given controller.
618
+ */
619
+ const PUT = (path = "") => _Route({
620
+ method: "PUT",
621
+ path
622
+ });
623
+ /**
624
+ * Register DELETE route on the given path (default "") for the given controller.
625
+ */
626
+ const DELETE = (path = "") => _Route({
627
+ method: "DELETE",
628
+ path
629
+ });
630
+ /**
631
+ * Register OPTIONS route on the given path (default "") for the given controller.
632
+ */
633
+ const OPTIONS = (path = "") => _Route({
634
+ method: "OPTIONS",
635
+ path
636
+ });
637
+ /**
638
+ * Register PATCH route on the given path (default "") for the given controller.
639
+ */
640
+ const PATCH = (path = "") => _Route({
641
+ method: "PATCH",
642
+ path
643
+ });
644
+ /**
645
+ * Register HEAD route on the given path (default "") for the given controller.
646
+ */
647
+ const HEAD = (path = "") => _Route({
648
+ method: "HEAD",
649
+ path
650
+ });
651
+ /**
652
+ * Register a middleware route on the given path (default "") for the given controller.
653
+ */
654
+ const Middleware = (path = "") => _Route({
655
+ method: "USE",
656
+ path
657
+ });
658
+ /**
659
+ * Given a class constructor, fetch all the routes attached.
660
+ */
661
+ function _getRoutes(ctor) {
662
+ return _routeStore.get(ctor) ?? [];
663
+ }
664
+ //#endregion
665
+ //#region src/annotation/controller.ts
666
+ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
667
+ /**
668
+ * Registers a class as an HTTP controller and registers its routes.
669
+ *
670
+ * @param [prefix] Optional URL prefix applied to all routes in the controller. Defaults to "".
671
+ * @param [deps] Optional array of dependencies to be injected into the constructor that are `@Injectable`
672
+ */
673
+ function Controller({ prefix = "", deps = [] } = {}) {
674
+ return (target) => {
675
+ const targetClass = target;
676
+ const router = (0, express.Router)();
677
+ const routes = _getRoutes(target);
678
+ const usedRoutes = /* @__PURE__ */ new Set();
679
+ _InjectableDeps.set(targetClass, deps);
680
+ const controllerInstance = _resolve(targetClass);
681
+ for (const { method, path, fnName } of routes) {
682
+ const fn = controllerInstance[fnName];
683
+ if (typeof fn !== "function") continue;
684
+ const fp = path instanceof RegExp ? path : prefix + path;
685
+ const routeKey = method + " " + (path instanceof RegExp ? path.source : fp);
686
+ if (method !== "USE" && usedRoutes.has(routeKey)) throw new Error(`Duplicate route [${method}] "${path instanceof RegExp ? path.source : fp}" detected in controller "${target.name}"`);
687
+ if (method !== "USE") usedRoutes.add(routeKey);
688
+ const methodName = methodResolve[method];
689
+ router[methodName](fp, async (request, response, next) => {
690
+ const schemas = _getRequestSchemas(target, fnName);
691
+ if (schemas) {
692
+ if (schemas.body) request.body = await _parseOrThrow(schemas.body, request.body, "reqbody");
693
+ if (schemas.param) request.params = await _parseOrThrow(schemas.param, request.params, "reqparams");
694
+ if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
695
+ }
696
+ const result = await fn.bind(controllerInstance)(request, response, next);
697
+ if (method === "USE") return;
698
+ if (result instanceof ResponseEntity) {
699
+ response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
700
+ return;
701
+ }
702
+ if (result instanceof RedirectView) {
703
+ response.redirect(result.getUrl());
704
+ return;
705
+ }
706
+ if (!response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
707
+ });
708
+ }
709
+ _ControllerRegistry.set(targetClass, router);
710
+ };
711
+ }
712
+ //#endregion
713
+ //#region src/annotation/middleware.ts
714
+ /**
715
+ * Used to define a middleware-only class.
716
+ *
717
+ * __NOTE:__ `@MiddlewareClass` works exactly the same as `@Controller`. As such, you
718
+ * can still register `@Route` and `@Middleware` methods, though you very well should not
719
+ * for the sake of semantics.
720
+ */
721
+ function MiddlewareClass(...args) {
722
+ return Controller(...args);
723
+ }
724
+ //#endregion
725
+ exports.Controller = Controller;
726
+ exports.DELETE = DELETE;
727
+ exports.GET = GET;
728
+ exports.HEAD = HEAD;
729
+ exports.Html404ErrorPage = Html404ErrorPage;
730
+ exports.HttpStatus = HttpStatus;
731
+ exports.Injectable = Injectable;
732
+ exports.Middleware = Middleware;
733
+ exports.MiddlewareClass = MiddlewareClass;
734
+ exports.OPTIONS = OPTIONS;
735
+ exports.PATCH = PATCH;
736
+ exports.POST = POST;
737
+ exports.PUT = PUT;
738
+ exports.ParserError = ParserError;
739
+ exports.RedirectView = RedirectView;
740
+ exports.RequestBody = RequestBody;
741
+ exports.RequestParam = RequestParam;
742
+ exports.RequestQuery = RequestQuery;
743
+ exports.ResponseEntity = ResponseEntity;
744
+ exports.ResponseEntityBuilder = ResponseEntityBuilder;
745
+ exports.ResponseStatusError = ResponseStatusError;
746
+ exports.Sapling = Sapling;
747
+ exports._ControllerRegistry = _ControllerRegistry;
748
+ exports._InjectableDeps = _InjectableDeps;
749
+ exports._InjectableRegistry = _InjectableRegistry;
750
+ exports._Route = _Route;
751
+ exports._getRequestSchemas = _getRequestSchemas;
752
+ exports._getRoutes = _getRoutes;
753
+ exports._parseOrThrow = _parseOrThrow;
754
+ exports._resolve = _resolve;
755
+ exports.methodResolve = methodResolve;