@tahminator/sapling 2.0.4 → 2.0.5-beta.2f539758
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -4
- package/dist/index.cjs +260 -134
- package/dist/index.d.cts +404 -13
- package/dist/index.d.mts +404 -13
- package/dist/index.mjs +245 -135
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -200,12 +200,71 @@ class UserController {
|
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
Sapling ships with default error middlewares, and you can also write your own.
|
|
204
|
+
Register error middlewares after your regular middlewares and controllers:
|
|
204
205
|
|
|
205
206
|
```typescript
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
import {
|
|
208
|
+
DefaultBaseErrorMiddleware,
|
|
209
|
+
DefaultResponseStatusErrorMiddleware,
|
|
210
|
+
} from "@tahminator/sapling";
|
|
211
|
+
|
|
212
|
+
// regular middlewares & controllers first
|
|
213
|
+
const middlewares: Class<any>[] = [CookieParserMiddleware];
|
|
214
|
+
middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
215
|
+
|
|
216
|
+
const controllers: Class<any>[] = [UserController];
|
|
217
|
+
controllers.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
218
|
+
|
|
219
|
+
// error middlewares last
|
|
220
|
+
const errorMiddlewares: Class<any>[] = [
|
|
221
|
+
DefaultResponseStatusErrorMiddleware,
|
|
222
|
+
DefaultBaseErrorMiddleware,
|
|
223
|
+
];
|
|
224
|
+
errorMiddlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
You can also write your own error middlewares. A specific handler should call
|
|
228
|
+
`next(err)` when it does not handle the error, and a base handler should be last
|
|
229
|
+
and return a response:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
@MiddlewareClass()
|
|
233
|
+
class ResponseStatusErrorMiddleware {
|
|
234
|
+
@Middleware()
|
|
235
|
+
handle(
|
|
236
|
+
err: unknown,
|
|
237
|
+
_request: Request,
|
|
238
|
+
_response: Response,
|
|
239
|
+
next: NextFunction,
|
|
240
|
+
) {
|
|
241
|
+
if (err instanceof ResponseStatusError) {
|
|
242
|
+
return ResponseEntity.status(err.status).body({ message: err.message });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// MUST call next(err) to continue the chain
|
|
246
|
+
next(err);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@MiddlewareClass()
|
|
251
|
+
class BaseErrorMiddleware {
|
|
252
|
+
@Middleware()
|
|
253
|
+
handle(
|
|
254
|
+
err: unknown,
|
|
255
|
+
_request: Request,
|
|
256
|
+
_response: Response,
|
|
257
|
+
_next: NextFunction,
|
|
258
|
+
) {
|
|
259
|
+
console.error("[Error]", err);
|
|
260
|
+
|
|
261
|
+
return ResponseEntity.status(500).body({
|
|
262
|
+
message: "Internal Server Error",
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// no next(err) since last middleware in chain, we are done propagating
|
|
266
|
+
}
|
|
267
|
+
}
|
|
209
268
|
```
|
|
210
269
|
|
|
211
270
|
### Middleware
|
|
@@ -234,10 +293,41 @@ class CookieParserMiddleware {
|
|
|
234
293
|
// Register it like any controller
|
|
235
294
|
app.use(Sapling.resolve(CookieParserMiddleware));
|
|
236
295
|
|
|
296
|
+
// Register middlewares before controllers
|
|
297
|
+
app.use(Sapling.resolve(UserController));
|
|
298
|
+
|
|
237
299
|
// You can also still choose to load plugins the Express.js way
|
|
238
300
|
app.use(cookieParser());
|
|
239
301
|
```
|
|
240
302
|
|
|
303
|
+
You can also write custom middlewares as well. It is functionally the same way as Express: call `next()` explicitly to
|
|
304
|
+
continue down the chain:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { MiddlewareClass, Middleware } from "@tahminator/sapling";
|
|
308
|
+
import { NextFunction, Request, Response } from "express";
|
|
309
|
+
|
|
310
|
+
@MiddlewareClass()
|
|
311
|
+
class RequestTimerMiddleware {
|
|
312
|
+
@Middleware()
|
|
313
|
+
handle(request: Request, _response: Response, next: NextFunction) {
|
|
314
|
+
const start = Date.now();
|
|
315
|
+
|
|
316
|
+
request.on("finish", () => {
|
|
317
|
+
const elapsedMs = Date.now() - start;
|
|
318
|
+
console.log(`[Request] ${request.method} ${request.path} ${elapsedMs}ms`);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// MUST call next() to continue the chain
|
|
322
|
+
next();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Register middlewares before controllers
|
|
327
|
+
app.use(Sapling.resolve(RequestTimerMiddleware));
|
|
328
|
+
app.use(Sapling.resolve(UserController));
|
|
329
|
+
```
|
|
330
|
+
|
|
241
331
|
### Request Validation
|
|
242
332
|
|
|
243
333
|
Validate and transform request bodies, route params, and query strings at the controller level using `@RequestBody`, `@RequestParam`, and `@RequestQuery`. These decorators accept any [Standard Schema](https://github.com/standard-schema/standard-schema) compatible validator (Zod, Valibot, ArkType, etc.).
|
package/dist/index.cjs
CHANGED
|
@@ -264,7 +264,8 @@ var ParserError = class ParserError extends ResponseStatusError {
|
|
|
264
264
|
//#region src/helper/sapling.ts
|
|
265
265
|
const settings = {
|
|
266
266
|
serialize: JSON.stringify,
|
|
267
|
-
deserialize: JSON.parse
|
|
267
|
+
deserialize: JSON.parse,
|
|
268
|
+
openapi: { path: "/openapi.json" }
|
|
268
269
|
};
|
|
269
270
|
/**
|
|
270
271
|
* Collection of utility functions which are essential for Sapling to function.
|
|
@@ -369,140 +370,11 @@ var Sapling = class Sapling {
|
|
|
369
370
|
static setDeserializeFn(fn) {
|
|
370
371
|
settings.deserialize = fn;
|
|
371
372
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
//#region src/types.ts
|
|
375
|
-
const methodResolve = {
|
|
376
|
-
GET: "get",
|
|
377
|
-
PUT: "put",
|
|
378
|
-
POST: "post",
|
|
379
|
-
DELETE: "delete",
|
|
380
|
-
OPTIONS: "options",
|
|
381
|
-
PATCH: "patch",
|
|
382
|
-
HEAD: "head",
|
|
383
|
-
USE: "use"
|
|
384
|
-
};
|
|
385
|
-
//#endregion
|
|
386
|
-
//#region lib/weakmap.ts
|
|
387
|
-
/**
|
|
388
|
-
* WeakMap that is iterable.
|
|
389
|
-
*/
|
|
390
|
-
var IterableWeakMap = class IterableWeakMap {
|
|
391
|
-
#weakMap = /* @__PURE__ */ new WeakMap();
|
|
392
|
-
#refSet = /* @__PURE__ */ new Set();
|
|
393
|
-
#finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
|
|
394
|
-
static #cleanup(heldValue) {
|
|
395
|
-
heldValue.set.delete(heldValue.ref);
|
|
396
|
-
}
|
|
397
|
-
constructor(iterable) {
|
|
398
|
-
if (iterable) for (const [key, value] of iterable) this.set(key, value);
|
|
399
|
-
}
|
|
400
|
-
set(key, value) {
|
|
401
|
-
const ref = new WeakRef(key);
|
|
402
|
-
this.#weakMap.set(key, {
|
|
403
|
-
value,
|
|
404
|
-
ref
|
|
405
|
-
});
|
|
406
|
-
this.#refSet.add(ref);
|
|
407
|
-
this.#finalizationGroup.register(key, {
|
|
408
|
-
set: this.#refSet,
|
|
409
|
-
ref
|
|
410
|
-
}, ref);
|
|
411
|
-
return this;
|
|
412
|
-
}
|
|
413
|
-
get(key) {
|
|
414
|
-
return this.#weakMap.get(key)?.value;
|
|
415
|
-
}
|
|
416
|
-
delete(key) {
|
|
417
|
-
const entry = this.#weakMap.get(key);
|
|
418
|
-
if (!entry) return false;
|
|
419
|
-
this.#weakMap.delete(key);
|
|
420
|
-
this.#refSet.delete(entry.ref);
|
|
421
|
-
this.#finalizationGroup.unregister(entry.ref);
|
|
422
|
-
return true;
|
|
423
|
-
}
|
|
424
|
-
*[Symbol.iterator]() {
|
|
425
|
-
for (const ref of this.#refSet) {
|
|
426
|
-
const key = ref.deref();
|
|
427
|
-
if (!key) continue;
|
|
428
|
-
const entry = this.#weakMap.get(key);
|
|
429
|
-
if (entry) yield [key, entry.value];
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
entries() {
|
|
433
|
-
return this[Symbol.iterator]();
|
|
434
|
-
}
|
|
435
|
-
*keys() {
|
|
436
|
-
for (const [key] of this) yield key;
|
|
437
|
-
}
|
|
438
|
-
*values() {
|
|
439
|
-
for (const [, value] of this) yield value;
|
|
440
|
-
}
|
|
441
|
-
forEach(callback, thisArg) {
|
|
442
|
-
for (const [key, value] of this) callback.call(thisArg, value, key, this);
|
|
373
|
+
static setOpenApiPath(path) {
|
|
374
|
+
settings.openapi.path = path;
|
|
443
375
|
}
|
|
444
376
|
};
|
|
445
377
|
//#endregion
|
|
446
|
-
//#region src/annotation/injectable.ts
|
|
447
|
-
const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
|
|
448
|
-
const _InjectableDeps = new IterableWeakMap();
|
|
449
|
-
/**
|
|
450
|
-
* Mark the class as an injectable to be handled by Sapling. The class can now be
|
|
451
|
-
* be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
|
|
452
|
-
*
|
|
453
|
-
* @argument deps - An optional array to define any dependencies that this class may require.
|
|
454
|
-
*/
|
|
455
|
-
function Injectable(deps = []) {
|
|
456
|
-
return function(target) {
|
|
457
|
-
_InjectableRegistry.set(target, null);
|
|
458
|
-
_InjectableDeps.set(target, deps);
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Resolves and instantiates a class along with all of it's transitive dependencies.
|
|
463
|
-
*
|
|
464
|
-
* Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
|
|
465
|
-
* in a correct order.
|
|
466
|
-
*
|
|
467
|
-
* When `resolve` is first called (usually during controller registration),
|
|
468
|
-
* it will compute the dependency graph of all `@Injectable` classes and instantiates
|
|
469
|
-
* them in the correct order.
|
|
470
|
-
*
|
|
471
|
-
* Subsequent calls to dependencies that have already been resolved are cached, so they will
|
|
472
|
-
* re-use the created singletons instead of re-instantiation.
|
|
473
|
-
*/
|
|
474
|
-
function _resolve(ctor) {
|
|
475
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
476
|
-
const graph = /* @__PURE__ */ new Map();
|
|
477
|
-
_InjectableDeps.forEach((deps, node) => {
|
|
478
|
-
inDegree.set(node, inDegree.get(node) || 0);
|
|
479
|
-
deps.forEach((dep) => {
|
|
480
|
-
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.`);
|
|
481
|
-
inDegree.set(dep, inDegree.get(dep) || 0);
|
|
482
|
-
inDegree.set(node, inDegree.get(node) + 1);
|
|
483
|
-
if (!graph.has(dep)) graph.set(dep, []);
|
|
484
|
-
graph.get(dep).push(node);
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
const queue = [];
|
|
488
|
-
inDegree.forEach((deg, node) => {
|
|
489
|
-
if (deg === 0) queue.push(node);
|
|
490
|
-
});
|
|
491
|
-
while (queue.length) {
|
|
492
|
-
const current = queue.shift();
|
|
493
|
-
if (!_InjectableRegistry.get(current)) {
|
|
494
|
-
const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
|
|
495
|
-
_InjectableRegistry.set(current, instance);
|
|
496
|
-
}
|
|
497
|
-
(graph.get(current) || []).forEach((neighbor) => {
|
|
498
|
-
inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
|
|
499
|
-
if (inDegree.get(neighbor) === 0) queue.push(neighbor);
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
|
|
503
|
-
return _InjectableRegistry.get(ctor);
|
|
504
|
-
}
|
|
505
|
-
//#endregion
|
|
506
378
|
//#region src/annotation/request.ts
|
|
507
379
|
const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
508
380
|
/**
|
|
@@ -711,6 +583,224 @@ function _getRoutes(ctor) {
|
|
|
711
583
|
return _routeStore.get(ctor) ?? [];
|
|
712
584
|
}
|
|
713
585
|
//#endregion
|
|
586
|
+
//#region src/helper/openapi.ts
|
|
587
|
+
var OpenAPIGenerator = class {
|
|
588
|
+
constructor() {
|
|
589
|
+
this.controllers = /* @__PURE__ */ new Set();
|
|
590
|
+
this.config = {
|
|
591
|
+
title: "API",
|
|
592
|
+
version: "1.0.0"
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
setConfig(config) {
|
|
596
|
+
this.config = config;
|
|
597
|
+
}
|
|
598
|
+
registerController(controllerClass, prefix) {
|
|
599
|
+
this.controllers.add({
|
|
600
|
+
class: controllerClass,
|
|
601
|
+
prefix
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
generateSpec() {
|
|
605
|
+
const config = this.config;
|
|
606
|
+
const paths = {};
|
|
607
|
+
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
608
|
+
const routes = _getRoutes(controllerClass);
|
|
609
|
+
for (const route of routes) {
|
|
610
|
+
if (route.method === "USE") continue;
|
|
611
|
+
const schemas = _getRequestSchemas(controllerClass, route.fnName);
|
|
612
|
+
const fullPath = route.path instanceof RegExp ? route.path.source : prefix + route.path;
|
|
613
|
+
const openApiPath = typeof fullPath === "string" ? fullPath.replace(/:(\w+)/g, "{$1}") : fullPath;
|
|
614
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
615
|
+
const operation = { responses: { "200": { description: "Successful response" } } };
|
|
616
|
+
const parameters = [];
|
|
617
|
+
if (schemas?.param) {
|
|
618
|
+
const paramSchema = this.toJsonSchema(schemas.param);
|
|
619
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
|
|
620
|
+
name,
|
|
621
|
+
in: "path",
|
|
622
|
+
required: true,
|
|
623
|
+
schema
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (schemas?.query) {
|
|
627
|
+
const querySchema = this.toJsonSchema(schemas.query);
|
|
628
|
+
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
629
|
+
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
630
|
+
parameters.push({
|
|
631
|
+
name,
|
|
632
|
+
in: "query",
|
|
633
|
+
required: isRequired,
|
|
634
|
+
schema
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
639
|
+
if (schemas?.body) operation.requestBody = {
|
|
640
|
+
required: true,
|
|
641
|
+
content: { "application/json": { schema: this.toJsonSchema(schemas.body) } }
|
|
642
|
+
};
|
|
643
|
+
const method = route.method.toLowerCase();
|
|
644
|
+
paths[openApiPath][method] = operation;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
openapi: "3.0.0",
|
|
649
|
+
info: {
|
|
650
|
+
title: config.title,
|
|
651
|
+
version: config.version,
|
|
652
|
+
description: config.description
|
|
653
|
+
},
|
|
654
|
+
paths
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
toJsonSchema(schema) {
|
|
658
|
+
return schema["~standard"].jsonSchema.output({ target: "openapi-3.0" });
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const openApiGenerator = new OpenAPIGenerator();
|
|
662
|
+
function _registerControllerClass(controllerClass, prefix) {
|
|
663
|
+
openApiGenerator.registerController(controllerClass, prefix);
|
|
664
|
+
}
|
|
665
|
+
function setOpenApiConfig(config) {
|
|
666
|
+
openApiGenerator.setConfig(config);
|
|
667
|
+
}
|
|
668
|
+
function generateOpenApiSpec() {
|
|
669
|
+
return openApiGenerator.generateSpec();
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/types.ts
|
|
673
|
+
const methodResolve = {
|
|
674
|
+
GET: "get",
|
|
675
|
+
PUT: "put",
|
|
676
|
+
POST: "post",
|
|
677
|
+
DELETE: "delete",
|
|
678
|
+
OPTIONS: "options",
|
|
679
|
+
PATCH: "patch",
|
|
680
|
+
HEAD: "head",
|
|
681
|
+
USE: "use"
|
|
682
|
+
};
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region lib/weakmap.ts
|
|
685
|
+
/**
|
|
686
|
+
* WeakMap that is iterable.
|
|
687
|
+
*/
|
|
688
|
+
var IterableWeakMap = class IterableWeakMap {
|
|
689
|
+
#weakMap = /* @__PURE__ */ new WeakMap();
|
|
690
|
+
#refSet = /* @__PURE__ */ new Set();
|
|
691
|
+
#finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
|
|
692
|
+
static #cleanup(heldValue) {
|
|
693
|
+
heldValue.set.delete(heldValue.ref);
|
|
694
|
+
}
|
|
695
|
+
constructor(iterable) {
|
|
696
|
+
if (iterable) for (const [key, value] of iterable) this.set(key, value);
|
|
697
|
+
}
|
|
698
|
+
set(key, value) {
|
|
699
|
+
const ref = new WeakRef(key);
|
|
700
|
+
this.#weakMap.set(key, {
|
|
701
|
+
value,
|
|
702
|
+
ref
|
|
703
|
+
});
|
|
704
|
+
this.#refSet.add(ref);
|
|
705
|
+
this.#finalizationGroup.register(key, {
|
|
706
|
+
set: this.#refSet,
|
|
707
|
+
ref
|
|
708
|
+
}, ref);
|
|
709
|
+
return this;
|
|
710
|
+
}
|
|
711
|
+
get(key) {
|
|
712
|
+
return this.#weakMap.get(key)?.value;
|
|
713
|
+
}
|
|
714
|
+
delete(key) {
|
|
715
|
+
const entry = this.#weakMap.get(key);
|
|
716
|
+
if (!entry) return false;
|
|
717
|
+
this.#weakMap.delete(key);
|
|
718
|
+
this.#refSet.delete(entry.ref);
|
|
719
|
+
this.#finalizationGroup.unregister(entry.ref);
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
*[Symbol.iterator]() {
|
|
723
|
+
for (const ref of this.#refSet) {
|
|
724
|
+
const key = ref.deref();
|
|
725
|
+
if (!key) continue;
|
|
726
|
+
const entry = this.#weakMap.get(key);
|
|
727
|
+
if (entry) yield [key, entry.value];
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
entries() {
|
|
731
|
+
return this[Symbol.iterator]();
|
|
732
|
+
}
|
|
733
|
+
*keys() {
|
|
734
|
+
for (const [key] of this) yield key;
|
|
735
|
+
}
|
|
736
|
+
*values() {
|
|
737
|
+
for (const [, value] of this) yield value;
|
|
738
|
+
}
|
|
739
|
+
forEach(callback, thisArg) {
|
|
740
|
+
for (const [key, value] of this) callback.call(thisArg, value, key, this);
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/annotation/injectable.ts
|
|
745
|
+
const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
|
|
746
|
+
const _InjectableDeps = new IterableWeakMap();
|
|
747
|
+
/**
|
|
748
|
+
* Mark the class as an injectable to be handled by Sapling. The class can now be
|
|
749
|
+
* be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
|
|
750
|
+
*
|
|
751
|
+
* @argument deps - An optional array to define any dependencies that this class may require.
|
|
752
|
+
*/
|
|
753
|
+
function Injectable(deps = []) {
|
|
754
|
+
return function(target) {
|
|
755
|
+
_InjectableRegistry.set(target, null);
|
|
756
|
+
_InjectableDeps.set(target, deps);
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Resolves and instantiates a class along with all of it's transitive dependencies.
|
|
761
|
+
*
|
|
762
|
+
* Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
|
|
763
|
+
* in a correct order.
|
|
764
|
+
*
|
|
765
|
+
* When `resolve` is first called (usually during controller registration),
|
|
766
|
+
* it will compute the dependency graph of all `@Injectable` classes and instantiates
|
|
767
|
+
* them in the correct order.
|
|
768
|
+
*
|
|
769
|
+
* Subsequent calls to dependencies that have already been resolved are cached, so they will
|
|
770
|
+
* re-use the created singletons instead of re-instantiation.
|
|
771
|
+
*/
|
|
772
|
+
function _resolve(ctor) {
|
|
773
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
774
|
+
const graph = /* @__PURE__ */ new Map();
|
|
775
|
+
_InjectableDeps.forEach((deps, node) => {
|
|
776
|
+
inDegree.set(node, inDegree.get(node) || 0);
|
|
777
|
+
deps.forEach((dep) => {
|
|
778
|
+
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.`);
|
|
779
|
+
inDegree.set(dep, inDegree.get(dep) || 0);
|
|
780
|
+
inDegree.set(node, inDegree.get(node) + 1);
|
|
781
|
+
if (!graph.has(dep)) graph.set(dep, []);
|
|
782
|
+
graph.get(dep).push(node);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
const queue = [];
|
|
786
|
+
inDegree.forEach((deg, node) => {
|
|
787
|
+
if (deg === 0) queue.push(node);
|
|
788
|
+
});
|
|
789
|
+
while (queue.length) {
|
|
790
|
+
const current = queue.shift();
|
|
791
|
+
if (!_InjectableRegistry.get(current)) {
|
|
792
|
+
const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
|
|
793
|
+
_InjectableRegistry.set(current, instance);
|
|
794
|
+
}
|
|
795
|
+
(graph.get(current) || []).forEach((neighbor) => {
|
|
796
|
+
inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
|
|
797
|
+
if (inDegree.get(neighbor) === 0) queue.push(neighbor);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
|
|
801
|
+
return _InjectableRegistry.get(ctor);
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
714
804
|
//#region src/annotation/controller.ts
|
|
715
805
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
716
806
|
/**
|
|
@@ -722,6 +812,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
722
812
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
723
813
|
return (target) => {
|
|
724
814
|
const targetClass = target;
|
|
815
|
+
_registerControllerClass(target, prefix);
|
|
725
816
|
const router = (0, express.Router)();
|
|
726
817
|
const routes = _getRoutes(target);
|
|
727
818
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -813,7 +904,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
813
904
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
814
905
|
}
|
|
815
906
|
//#endregion
|
|
816
|
-
//#region src/middleware/default/base.ts
|
|
907
|
+
//#region src/middleware/default/error/base.ts
|
|
817
908
|
let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
818
909
|
handle(err, _request, _response, _next) {
|
|
819
910
|
console.error("[Error]", err);
|
|
@@ -823,7 +914,17 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
|
823
914
|
__decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
|
|
824
915
|
DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
|
|
825
916
|
//#endregion
|
|
826
|
-
//#region src/middleware/default/
|
|
917
|
+
//#region src/middleware/default/error/parse.ts
|
|
918
|
+
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
919
|
+
handle(err, _request, _response, next) {
|
|
920
|
+
if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
921
|
+
next(err);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
__decorate([Middleware()], DefaultParserErrorMiddleware.prototype, "handle", null);
|
|
925
|
+
DefaultParserErrorMiddleware = __decorate([MiddlewareClass()], DefaultParserErrorMiddleware);
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region src/middleware/default/error/responsestatus.ts
|
|
827
928
|
let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
|
|
828
929
|
handle(err, _request, _response, next) {
|
|
829
930
|
if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
@@ -833,6 +934,15 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
|
|
|
833
934
|
__decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
|
|
834
935
|
DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
|
|
835
936
|
//#endregion
|
|
937
|
+
//#region src/middleware/default/openapi/index.ts
|
|
938
|
+
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
939
|
+
handle(_request, _response, _next) {
|
|
940
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
__decorate([GET("/openapi.json")], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
944
|
+
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
945
|
+
//#endregion
|
|
836
946
|
exports.Controller = Controller;
|
|
837
947
|
exports.DELETE = DELETE;
|
|
838
948
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
@@ -841,6 +951,18 @@ Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
|
841
951
|
return DefaultBaseErrorMiddleware;
|
|
842
952
|
}
|
|
843
953
|
});
|
|
954
|
+
Object.defineProperty(exports, "DefaultOpenApiMiddleware", {
|
|
955
|
+
enumerable: true,
|
|
956
|
+
get: function() {
|
|
957
|
+
return DefaultOpenApiMiddleware;
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
Object.defineProperty(exports, "DefaultParserErrorMiddleware", {
|
|
961
|
+
enumerable: true,
|
|
962
|
+
get: function() {
|
|
963
|
+
return DefaultParserErrorMiddleware;
|
|
964
|
+
}
|
|
965
|
+
});
|
|
844
966
|
Object.defineProperty(exports, "DefaultResponseStatusErrorMiddleware", {
|
|
845
967
|
enumerable: true,
|
|
846
968
|
get: function() {
|
|
@@ -874,5 +996,9 @@ exports._Route = _Route;
|
|
|
874
996
|
exports._getRequestSchemas = _getRequestSchemas;
|
|
875
997
|
exports._getRoutes = _getRoutes;
|
|
876
998
|
exports._parseOrThrow = _parseOrThrow;
|
|
999
|
+
exports._registerControllerClass = _registerControllerClass;
|
|
877
1000
|
exports._resolve = _resolve;
|
|
1001
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
878
1002
|
exports.methodResolve = methodResolve;
|
|
1003
|
+
exports.openApiGenerator = openApiGenerator;
|
|
1004
|
+
exports.setOpenApiConfig = setOpenApiConfig;
|