@tahminator/sapling 2.0.3 → 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 +337 -137
- package/dist/index.d.cts +478 -13
- package/dist/index.d.mts +478 -13
- package/dist/index.mjs +322 -138
- 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,7 +370,304 @@ var Sapling = class Sapling {
|
|
|
369
370
|
static setDeserializeFn(fn) {
|
|
370
371
|
settings.deserialize = fn;
|
|
371
372
|
}
|
|
373
|
+
static setOpenApiPath(path) {
|
|
374
|
+
settings.openapi.path = path;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/annotation/request.ts
|
|
379
|
+
const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
380
|
+
/**
|
|
381
|
+
* Apply to a route method to have `request.body` be parsed by `schema`.
|
|
382
|
+
*
|
|
383
|
+
* This annotation will parse `request.body` & then override `request.body`.
|
|
384
|
+
* You can then just simply cast `request.body` for your use
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
|
|
389
|
+
* name: z.string(),
|
|
390
|
+
* description: z.string().optional(),
|
|
391
|
+
* });
|
|
392
|
+
*
|
|
393
|
+
* ⠀@Controller({ prefix: "/api/book" })
|
|
394
|
+
* class BookController {
|
|
395
|
+
* ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
|
|
396
|
+
* ⠀@POST()
|
|
397
|
+
* public createBook(request: e.Request) {
|
|
398
|
+
* const { name, description } = request.body as unknown as z.infer<
|
|
399
|
+
* typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
|
|
400
|
+
* >;
|
|
401
|
+
* }
|
|
402
|
+
* }
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
function RequestBody(schema) {
|
|
406
|
+
return (target, propertyKey) => {
|
|
407
|
+
const ctor = target.constructor;
|
|
408
|
+
const fnName = String(propertyKey);
|
|
409
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Apply to a route method to have `request.param` be parsed by `schema`.
|
|
414
|
+
*
|
|
415
|
+
* This annotation will parse `request.param` & then override `request.param`.
|
|
416
|
+
* You can then just simply cast `request.param` for your use
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
|
|
421
|
+
* bookId: z.string(),
|
|
422
|
+
* });
|
|
423
|
+
*
|
|
424
|
+
* ⠀@Controller({ prefix: "/api/book" })
|
|
425
|
+
* class BookController {
|
|
426
|
+
* ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
|
|
427
|
+
* ⠀@GET("/:bookId")
|
|
428
|
+
* public getBook(request: e.Request) {
|
|
429
|
+
* const { bookId } = request.param as unknown as z.infer<
|
|
430
|
+
* typeof GET_BOOK_REQUEST_PARAM_SCHEMA
|
|
431
|
+
* >;
|
|
432
|
+
* }
|
|
433
|
+
* }
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
function RequestParam(schema) {
|
|
437
|
+
return (target, propertyKey) => {
|
|
438
|
+
const ctor = target.constructor;
|
|
439
|
+
const fnName = String(propertyKey);
|
|
440
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Apply to a route method to have `request.query` be parsed by `schema`.
|
|
445
|
+
*
|
|
446
|
+
* This annotation will parse `request.query` & then override `request.query`.
|
|
447
|
+
* You can then just simply cast `request.query` for your use
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```ts
|
|
451
|
+
* const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
|
|
452
|
+
* sort: z.enum(["name", "createdAt"]).optional(),
|
|
453
|
+
* q: z.string().optional(),
|
|
454
|
+
* });
|
|
455
|
+
*
|
|
456
|
+
* ⠀@Controller({ prefix: "/api/book" })
|
|
457
|
+
* class BookController {
|
|
458
|
+
* ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
|
|
459
|
+
* ⠀@GET()
|
|
460
|
+
* public listBooks(request: e.Request) {
|
|
461
|
+
* const { sort, q } = request.query as unknown as z.infer<
|
|
462
|
+
* typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
|
|
463
|
+
* >;
|
|
464
|
+
* }
|
|
465
|
+
* }
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
function RequestQuery(schema) {
|
|
469
|
+
return (target, propertyKey) => {
|
|
470
|
+
const ctor = target.constructor;
|
|
471
|
+
const fnName = String(propertyKey);
|
|
472
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
|
|
476
|
+
const byFn = (() => {
|
|
477
|
+
const fn = _requestSchemaStore.get(ctor);
|
|
478
|
+
if (fn) return fn;
|
|
479
|
+
const newFn = /* @__PURE__ */ new Map();
|
|
480
|
+
_requestSchemaStore.set(ctor, newFn);
|
|
481
|
+
return newFn;
|
|
482
|
+
})();
|
|
483
|
+
const existing = byFn.get(fnName);
|
|
484
|
+
if (existing) return existing;
|
|
485
|
+
const created = {};
|
|
486
|
+
byFn.set(fnName, created);
|
|
487
|
+
return created;
|
|
488
|
+
}
|
|
489
|
+
function _setOnce(def, key, schema, fnName) {
|
|
490
|
+
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
491
|
+
def[key] = schema;
|
|
492
|
+
}
|
|
493
|
+
function _getRequestSchemas(ctor, fnName) {
|
|
494
|
+
return _requestSchemaStore.get(ctor)?.get(fnName);
|
|
495
|
+
}
|
|
496
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
497
|
+
const result = await schema["~standard"].validate(input);
|
|
498
|
+
if (result.issues) {
|
|
499
|
+
console.debug(`Failed to parse a schema`);
|
|
500
|
+
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
501
|
+
}
|
|
502
|
+
return result.value;
|
|
503
|
+
}
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/annotation/route.ts
|
|
506
|
+
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
507
|
+
/**
|
|
508
|
+
* Custom annotation that will store all routes inside of a map,
|
|
509
|
+
* which can then be used to initialize all the routes to the router.
|
|
510
|
+
*/
|
|
511
|
+
function _Route({ method, path = "" }) {
|
|
512
|
+
return (target, propertyKey) => {
|
|
513
|
+
const ctor = target.constructor;
|
|
514
|
+
const list = _routeStore.get(ctor) ?? [];
|
|
515
|
+
list.push({
|
|
516
|
+
method,
|
|
517
|
+
path: path ?? "",
|
|
518
|
+
fnName: String(propertyKey)
|
|
519
|
+
});
|
|
520
|
+
_routeStore.set(ctor, list);
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Register GET route on the given path (default "") for the given controller.
|
|
525
|
+
*/
|
|
526
|
+
const GET = (path = "") => _Route({
|
|
527
|
+
method: "GET",
|
|
528
|
+
path
|
|
529
|
+
});
|
|
530
|
+
/**
|
|
531
|
+
* Register POST route on the given path (default "") for the given controller.
|
|
532
|
+
*/
|
|
533
|
+
const POST = (path = "") => _Route({
|
|
534
|
+
method: "POST",
|
|
535
|
+
path
|
|
536
|
+
});
|
|
537
|
+
/**
|
|
538
|
+
* Register PUT route on the given path (default "") for the given controller.
|
|
539
|
+
*/
|
|
540
|
+
const PUT = (path = "") => _Route({
|
|
541
|
+
method: "PUT",
|
|
542
|
+
path
|
|
543
|
+
});
|
|
544
|
+
/**
|
|
545
|
+
* Register DELETE route on the given path (default "") for the given controller.
|
|
546
|
+
*/
|
|
547
|
+
const DELETE = (path = "") => _Route({
|
|
548
|
+
method: "DELETE",
|
|
549
|
+
path
|
|
550
|
+
});
|
|
551
|
+
/**
|
|
552
|
+
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
553
|
+
*/
|
|
554
|
+
const OPTIONS = (path = "") => _Route({
|
|
555
|
+
method: "OPTIONS",
|
|
556
|
+
path
|
|
557
|
+
});
|
|
558
|
+
/**
|
|
559
|
+
* Register PATCH route on the given path (default "") for the given controller.
|
|
560
|
+
*/
|
|
561
|
+
const PATCH = (path = "") => _Route({
|
|
562
|
+
method: "PATCH",
|
|
563
|
+
path
|
|
564
|
+
});
|
|
565
|
+
/**
|
|
566
|
+
* Register HEAD route on the given path (default "") for the given controller.
|
|
567
|
+
*/
|
|
568
|
+
const HEAD = (path = "") => _Route({
|
|
569
|
+
method: "HEAD",
|
|
570
|
+
path
|
|
571
|
+
});
|
|
572
|
+
/**
|
|
573
|
+
* Register a middleware route on the given path (default "") for the given controller.
|
|
574
|
+
*/
|
|
575
|
+
const Middleware = (path = "") => _Route({
|
|
576
|
+
method: "USE",
|
|
577
|
+
path
|
|
578
|
+
});
|
|
579
|
+
/**
|
|
580
|
+
* Given a class constructor, fetch all the routes attached.
|
|
581
|
+
*/
|
|
582
|
+
function _getRoutes(ctor) {
|
|
583
|
+
return _routeStore.get(ctor) ?? [];
|
|
584
|
+
}
|
|
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
|
+
}
|
|
372
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
|
+
}
|
|
373
671
|
//#endregion
|
|
374
672
|
//#region src/types.ts
|
|
375
673
|
const methodResolve = {
|
|
@@ -503,140 +801,6 @@ function _resolve(ctor) {
|
|
|
503
801
|
return _InjectableRegistry.get(ctor);
|
|
504
802
|
}
|
|
505
803
|
//#endregion
|
|
506
|
-
//#region src/annotation/request.ts
|
|
507
|
-
const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
508
|
-
function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
|
|
509
|
-
const byFn = (() => {
|
|
510
|
-
const fn = _requestSchemaStore.get(ctor);
|
|
511
|
-
if (fn) return fn;
|
|
512
|
-
const newFn = /* @__PURE__ */ new Map();
|
|
513
|
-
_requestSchemaStore.set(ctor, newFn);
|
|
514
|
-
return newFn;
|
|
515
|
-
})();
|
|
516
|
-
const existing = byFn.get(fnName);
|
|
517
|
-
if (existing) return existing;
|
|
518
|
-
const created = {};
|
|
519
|
-
byFn.set(fnName, created);
|
|
520
|
-
return created;
|
|
521
|
-
}
|
|
522
|
-
function _setOnce(def, key, schema, fnName) {
|
|
523
|
-
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
524
|
-
def[key] = schema;
|
|
525
|
-
}
|
|
526
|
-
function RequestBody(schema) {
|
|
527
|
-
return (target, propertyKey) => {
|
|
528
|
-
const ctor = target.constructor;
|
|
529
|
-
const fnName = String(propertyKey);
|
|
530
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
function RequestParam(schema) {
|
|
534
|
-
return (target, propertyKey) => {
|
|
535
|
-
const ctor = target.constructor;
|
|
536
|
-
const fnName = String(propertyKey);
|
|
537
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
function RequestQuery(schema) {
|
|
541
|
-
return (target, propertyKey) => {
|
|
542
|
-
const ctor = target.constructor;
|
|
543
|
-
const fnName = String(propertyKey);
|
|
544
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
function _getRequestSchemas(ctor, fnName) {
|
|
548
|
-
return _requestSchemaStore.get(ctor)?.get(fnName);
|
|
549
|
-
}
|
|
550
|
-
async function _parseOrThrow(schema, input, kind) {
|
|
551
|
-
const result = await schema["~standard"].validate(input);
|
|
552
|
-
if (result.issues) {
|
|
553
|
-
console.debug(`Failed to parse a schema`);
|
|
554
|
-
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
555
|
-
}
|
|
556
|
-
return result.value;
|
|
557
|
-
}
|
|
558
|
-
//#endregion
|
|
559
|
-
//#region src/annotation/route.ts
|
|
560
|
-
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
561
|
-
/**
|
|
562
|
-
* Custom annotation that will store all routes inside of a map,
|
|
563
|
-
* which can then be used to initialize all the routes to the router.
|
|
564
|
-
*/
|
|
565
|
-
function _Route({ method, path = "" }) {
|
|
566
|
-
return (target, propertyKey) => {
|
|
567
|
-
const ctor = target.constructor;
|
|
568
|
-
const list = _routeStore.get(ctor) ?? [];
|
|
569
|
-
list.push({
|
|
570
|
-
method,
|
|
571
|
-
path: path ?? "",
|
|
572
|
-
fnName: String(propertyKey)
|
|
573
|
-
});
|
|
574
|
-
_routeStore.set(ctor, list);
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Register GET route on the given path (default "") for the given controller.
|
|
579
|
-
*/
|
|
580
|
-
const GET = (path = "") => _Route({
|
|
581
|
-
method: "GET",
|
|
582
|
-
path
|
|
583
|
-
});
|
|
584
|
-
/**
|
|
585
|
-
* Register POST route on the given path (default "") for the given controller.
|
|
586
|
-
*/
|
|
587
|
-
const POST = (path = "") => _Route({
|
|
588
|
-
method: "POST",
|
|
589
|
-
path
|
|
590
|
-
});
|
|
591
|
-
/**
|
|
592
|
-
* Register PUT route on the given path (default "") for the given controller.
|
|
593
|
-
*/
|
|
594
|
-
const PUT = (path = "") => _Route({
|
|
595
|
-
method: "PUT",
|
|
596
|
-
path
|
|
597
|
-
});
|
|
598
|
-
/**
|
|
599
|
-
* Register DELETE route on the given path (default "") for the given controller.
|
|
600
|
-
*/
|
|
601
|
-
const DELETE = (path = "") => _Route({
|
|
602
|
-
method: "DELETE",
|
|
603
|
-
path
|
|
604
|
-
});
|
|
605
|
-
/**
|
|
606
|
-
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
607
|
-
*/
|
|
608
|
-
const OPTIONS = (path = "") => _Route({
|
|
609
|
-
method: "OPTIONS",
|
|
610
|
-
path
|
|
611
|
-
});
|
|
612
|
-
/**
|
|
613
|
-
* Register PATCH route on the given path (default "") for the given controller.
|
|
614
|
-
*/
|
|
615
|
-
const PATCH = (path = "") => _Route({
|
|
616
|
-
method: "PATCH",
|
|
617
|
-
path
|
|
618
|
-
});
|
|
619
|
-
/**
|
|
620
|
-
* Register HEAD route on the given path (default "") for the given controller.
|
|
621
|
-
*/
|
|
622
|
-
const HEAD = (path = "") => _Route({
|
|
623
|
-
method: "HEAD",
|
|
624
|
-
path
|
|
625
|
-
});
|
|
626
|
-
/**
|
|
627
|
-
* Register a middleware route on the given path (default "") for the given controller.
|
|
628
|
-
*/
|
|
629
|
-
const Middleware = (path = "") => _Route({
|
|
630
|
-
method: "USE",
|
|
631
|
-
path
|
|
632
|
-
});
|
|
633
|
-
/**
|
|
634
|
-
* Given a class constructor, fetch all the routes attached.
|
|
635
|
-
*/
|
|
636
|
-
function _getRoutes(ctor) {
|
|
637
|
-
return _routeStore.get(ctor) ?? [];
|
|
638
|
-
}
|
|
639
|
-
//#endregion
|
|
640
804
|
//#region src/annotation/controller.ts
|
|
641
805
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
642
806
|
/**
|
|
@@ -648,6 +812,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
648
812
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
649
813
|
return (target) => {
|
|
650
814
|
const targetClass = target;
|
|
815
|
+
_registerControllerClass(target, prefix);
|
|
651
816
|
const router = (0, express.Router)();
|
|
652
817
|
const routes = _getRoutes(target);
|
|
653
818
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -739,7 +904,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
739
904
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
740
905
|
}
|
|
741
906
|
//#endregion
|
|
742
|
-
//#region src/middleware/default/base.ts
|
|
907
|
+
//#region src/middleware/default/error/base.ts
|
|
743
908
|
let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
744
909
|
handle(err, _request, _response, _next) {
|
|
745
910
|
console.error("[Error]", err);
|
|
@@ -749,7 +914,17 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
|
749
914
|
__decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
|
|
750
915
|
DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
|
|
751
916
|
//#endregion
|
|
752
|
-
//#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
|
|
753
928
|
let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
|
|
754
929
|
handle(err, _request, _response, next) {
|
|
755
930
|
if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
@@ -759,6 +934,15 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
|
|
|
759
934
|
__decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
|
|
760
935
|
DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
|
|
761
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
|
|
762
946
|
exports.Controller = Controller;
|
|
763
947
|
exports.DELETE = DELETE;
|
|
764
948
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
@@ -767,6 +951,18 @@ Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
|
767
951
|
return DefaultBaseErrorMiddleware;
|
|
768
952
|
}
|
|
769
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
|
+
});
|
|
770
966
|
Object.defineProperty(exports, "DefaultResponseStatusErrorMiddleware", {
|
|
771
967
|
enumerable: true,
|
|
772
968
|
get: function() {
|
|
@@ -800,5 +996,9 @@ exports._Route = _Route;
|
|
|
800
996
|
exports._getRequestSchemas = _getRequestSchemas;
|
|
801
997
|
exports._getRoutes = _getRoutes;
|
|
802
998
|
exports._parseOrThrow = _parseOrThrow;
|
|
999
|
+
exports._registerControllerClass = _registerControllerClass;
|
|
803
1000
|
exports._resolve = _resolve;
|
|
1001
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
804
1002
|
exports.methodResolve = methodResolve;
|
|
1003
|
+
exports.openApiGenerator = openApiGenerator;
|
|
1004
|
+
exports.setOpenApiConfig = setOpenApiConfig;
|