@tahminator/sapling 2.0.3 → 2.0.5-beta.0b3bd061
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 +391 -119
- package/dist/index.d.cts +467 -17
- package/dist/index.d.mts +467 -17
- package/dist/index.mjs +363 -119
- package/package.json +8 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import e, { Router } from "express";
|
|
2
|
+
import swagger from "swagger-ui-express";
|
|
2
3
|
//#region src/html/404.ts
|
|
3
4
|
/**
|
|
4
5
|
* Default Express.js 404 error page, as a string.
|
|
@@ -232,15 +233,20 @@ var ParserError = class ParserError extends ResponseStatusError {
|
|
|
232
233
|
case "reqbody": return "request body";
|
|
233
234
|
case "reqparams": return "request params";
|
|
234
235
|
case "reqquery": return "request query";
|
|
236
|
+
case "resbody": return "response body";
|
|
235
237
|
}
|
|
236
238
|
})()}: ${formatted}`;
|
|
237
239
|
}
|
|
238
240
|
};
|
|
239
241
|
//#endregion
|
|
240
242
|
//#region src/helper/sapling.ts
|
|
241
|
-
const
|
|
243
|
+
const _settings = {
|
|
242
244
|
serialize: JSON.stringify,
|
|
243
|
-
deserialize: JSON.parse
|
|
245
|
+
deserialize: JSON.parse,
|
|
246
|
+
doc: {
|
|
247
|
+
openApiPath: "/openapi.json",
|
|
248
|
+
swaggerPath: "/swagger.html"
|
|
249
|
+
}
|
|
244
250
|
};
|
|
245
251
|
/**
|
|
246
252
|
* Collection of utility functions which are essential for Sapling to function.
|
|
@@ -319,13 +325,13 @@ var Sapling = class Sapling {
|
|
|
319
325
|
* @defaultValue `JSON.stringify`
|
|
320
326
|
*/
|
|
321
327
|
static serialize(value) {
|
|
322
|
-
return
|
|
328
|
+
return _settings.serialize(value);
|
|
323
329
|
}
|
|
324
330
|
/**
|
|
325
331
|
* Replace the function used for `serialize`.
|
|
326
332
|
*/
|
|
327
333
|
static setSerializeFn(fn) {
|
|
328
|
-
|
|
334
|
+
_settings.serialize = fn;
|
|
329
335
|
}
|
|
330
336
|
/**
|
|
331
337
|
* De-serialize a JSON string back to a JavaScript object.
|
|
@@ -337,15 +343,286 @@ var Sapling = class Sapling {
|
|
|
337
343
|
* @defaultValue `JSON.parse`
|
|
338
344
|
*/
|
|
339
345
|
static deserialize(value) {
|
|
340
|
-
return
|
|
346
|
+
return _settings.deserialize(value);
|
|
341
347
|
}
|
|
342
348
|
/**
|
|
343
349
|
* Replace the function used for `deserialize`
|
|
344
350
|
*/
|
|
345
351
|
static setDeserializeFn(fn) {
|
|
346
|
-
|
|
352
|
+
_settings.deserialize = fn;
|
|
353
|
+
}
|
|
354
|
+
static setOpenApiPath(path) {
|
|
355
|
+
_settings.doc.openApiPath = path;
|
|
356
|
+
}
|
|
357
|
+
static setSwaggerPath(path) {
|
|
358
|
+
_settings.doc.swaggerPath = path;
|
|
359
|
+
}
|
|
360
|
+
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
361
|
+
if (index >= handlers.length) {
|
|
362
|
+
next();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
handlers[index]?.(request, response, (err) => {
|
|
366
|
+
if (err) {
|
|
367
|
+
next(err);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
Sapling.chainHandlers(handlers, request, response, next, index + 1);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/annotation/route.ts
|
|
376
|
+
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
377
|
+
/**
|
|
378
|
+
* Custom annotation that will store all routes inside of a map,
|
|
379
|
+
* which can then be used to initialize all the routes to the router.
|
|
380
|
+
*/
|
|
381
|
+
function _Route({ method, path = "" }) {
|
|
382
|
+
return (target, propertyKey) => {
|
|
383
|
+
const ctor = target.constructor;
|
|
384
|
+
const list = _routeStore.get(ctor) ?? [];
|
|
385
|
+
list.push({
|
|
386
|
+
method,
|
|
387
|
+
path: path ?? "",
|
|
388
|
+
fnName: String(propertyKey)
|
|
389
|
+
});
|
|
390
|
+
_routeStore.set(ctor, list);
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Register GET route on the given path (default "") for the given controller.
|
|
395
|
+
*/
|
|
396
|
+
const GET = (path = "") => _Route({
|
|
397
|
+
method: "GET",
|
|
398
|
+
path
|
|
399
|
+
});
|
|
400
|
+
/**
|
|
401
|
+
* Register POST route on the given path (default "") for the given controller.
|
|
402
|
+
*/
|
|
403
|
+
const POST = (path = "") => _Route({
|
|
404
|
+
method: "POST",
|
|
405
|
+
path
|
|
406
|
+
});
|
|
407
|
+
/**
|
|
408
|
+
* Register PUT route on the given path (default "") for the given controller.
|
|
409
|
+
*/
|
|
410
|
+
const PUT = (path = "") => _Route({
|
|
411
|
+
method: "PUT",
|
|
412
|
+
path
|
|
413
|
+
});
|
|
414
|
+
/**
|
|
415
|
+
* Register DELETE route on the given path (default "") for the given controller.
|
|
416
|
+
*/
|
|
417
|
+
const DELETE = (path = "") => _Route({
|
|
418
|
+
method: "DELETE",
|
|
419
|
+
path
|
|
420
|
+
});
|
|
421
|
+
/**
|
|
422
|
+
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
423
|
+
*/
|
|
424
|
+
const OPTIONS = (path = "") => _Route({
|
|
425
|
+
method: "OPTIONS",
|
|
426
|
+
path
|
|
427
|
+
});
|
|
428
|
+
/**
|
|
429
|
+
* Register PATCH route on the given path (default "") for the given controller.
|
|
430
|
+
*/
|
|
431
|
+
const PATCH = (path = "") => _Route({
|
|
432
|
+
method: "PATCH",
|
|
433
|
+
path
|
|
434
|
+
});
|
|
435
|
+
/**
|
|
436
|
+
* Register HEAD route on the given path (default "") for the given controller.
|
|
437
|
+
*/
|
|
438
|
+
const HEAD = (path = "") => _Route({
|
|
439
|
+
method: "HEAD",
|
|
440
|
+
path
|
|
441
|
+
});
|
|
442
|
+
/**
|
|
443
|
+
* Register a middleware route on the given path (default "") for the given controller.
|
|
444
|
+
*/
|
|
445
|
+
const Middleware = (path = "") => _Route({
|
|
446
|
+
method: "USE",
|
|
447
|
+
path
|
|
448
|
+
});
|
|
449
|
+
/**
|
|
450
|
+
* Given a class constructor, fetch all the routes attached.
|
|
451
|
+
*/
|
|
452
|
+
function _getRoutes(ctor) {
|
|
453
|
+
return _routeStore.get(ctor) ?? [];
|
|
454
|
+
}
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/utils.ts
|
|
457
|
+
function _getOrCreateMap(store, ctor) {
|
|
458
|
+
const existing = store.get(ctor);
|
|
459
|
+
if (existing) return existing;
|
|
460
|
+
const created = /* @__PURE__ */ new Map();
|
|
461
|
+
store.set(ctor, created);
|
|
462
|
+
return created;
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/annotation/schema.ts
|
|
466
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
467
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
468
|
+
function ControllerSchema(options) {
|
|
469
|
+
return (target) => {
|
|
470
|
+
_setControllerSchema(target, options);
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function RouteSchema(options) {
|
|
474
|
+
return (target, propertyKey) => {
|
|
475
|
+
const ctor = target.constructor;
|
|
476
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
480
|
+
_getOrCreateMap(_routeSchemaStore, ctor).set(fnName, options);
|
|
481
|
+
}
|
|
482
|
+
function _setControllerSchema(ctor, options) {
|
|
483
|
+
_controllerSchemaStore.set(ctor, options);
|
|
484
|
+
}
|
|
485
|
+
function _getRouteSchema(ctor, fnName) {
|
|
486
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
487
|
+
}
|
|
488
|
+
function _getControllerSchema(ctor) {
|
|
489
|
+
return _controllerSchemaStore.get(ctor);
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/helper/openapi.ts
|
|
493
|
+
var OpenAPIGenerator = class {
|
|
494
|
+
constructor() {
|
|
495
|
+
this.controllers = /* @__PURE__ */ new Set();
|
|
496
|
+
this.config = {
|
|
497
|
+
title: "API",
|
|
498
|
+
version: "1.0.0"
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
setConfig(config) {
|
|
502
|
+
this.config = config;
|
|
503
|
+
}
|
|
504
|
+
registerController(controllerClass, prefix) {
|
|
505
|
+
this.controllers.add({
|
|
506
|
+
class: controllerClass,
|
|
507
|
+
prefix
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
generateSpec() {
|
|
511
|
+
const config = this.config;
|
|
512
|
+
const paths = {};
|
|
513
|
+
const tags = [];
|
|
514
|
+
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
515
|
+
const routes = _getRoutes(controllerClass);
|
|
516
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
517
|
+
if (controllerSchema?.title) tags.push({
|
|
518
|
+
name: controllerSchema.title,
|
|
519
|
+
description: controllerSchema.description
|
|
520
|
+
});
|
|
521
|
+
for (const route of routes) {
|
|
522
|
+
if (route.method === "USE") continue;
|
|
523
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
524
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
525
|
+
if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
|
|
526
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
527
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
528
|
+
const responses = {};
|
|
529
|
+
if (schemas?.responseBody) {
|
|
530
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
531
|
+
responses["200"] = {
|
|
532
|
+
description: responseSchema.description ?? "Successful response",
|
|
533
|
+
content: { "application/json": { schema: responseSchema } }
|
|
534
|
+
};
|
|
535
|
+
} else responses["200"] = { description: "Successful response" };
|
|
536
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
537
|
+
const statusCode = String(resp.statusCode);
|
|
538
|
+
const existingResponse = responses[statusCode];
|
|
539
|
+
const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
|
|
540
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
|
|
541
|
+
responses[statusCode] = {
|
|
542
|
+
...existingResponse,
|
|
543
|
+
description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
|
|
544
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const operation = {
|
|
548
|
+
responses,
|
|
549
|
+
summary: routeSchema?.summary,
|
|
550
|
+
description: routeSchema?.description,
|
|
551
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
552
|
+
};
|
|
553
|
+
const parameters = [];
|
|
554
|
+
if (schemas?.requestParam) {
|
|
555
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
556
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
557
|
+
const parameterSchema = schema;
|
|
558
|
+
parameters.push({
|
|
559
|
+
name,
|
|
560
|
+
in: "path",
|
|
561
|
+
required: true,
|
|
562
|
+
description: parameterSchema.description,
|
|
563
|
+
schema: parameterSchema
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (schemas?.requestQuery) {
|
|
568
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
569
|
+
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
570
|
+
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
571
|
+
const parameterSchema = schema;
|
|
572
|
+
parameters.push({
|
|
573
|
+
name,
|
|
574
|
+
in: "query",
|
|
575
|
+
required: isRequired,
|
|
576
|
+
description: parameterSchema.description,
|
|
577
|
+
schema: parameterSchema
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
582
|
+
if (schemas?.requestBody) {
|
|
583
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
584
|
+
operation.requestBody = {
|
|
585
|
+
required: true,
|
|
586
|
+
description: requestSchema.description,
|
|
587
|
+
content: { "application/json": { schema: requestSchema } }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const method = route.method.toLowerCase();
|
|
591
|
+
paths[openApiPath][method] = operation;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
openapi: "3.0.0",
|
|
596
|
+
info: {
|
|
597
|
+
title: config.title,
|
|
598
|
+
version: config.version,
|
|
599
|
+
description: config.description
|
|
600
|
+
},
|
|
601
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
602
|
+
paths
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
toJsonSchema(schema, direction = "output") {
|
|
606
|
+
try {
|
|
607
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
608
|
+
if (direction === "input" && jsonSchema.input) return jsonSchema.input({ target: "openapi-3.0" });
|
|
609
|
+
return jsonSchema.output({ target: "openapi-3.0" });
|
|
610
|
+
} catch (e) {
|
|
611
|
+
if (e instanceof Error && e.message.includes("Transforms cannot be represented in JSON Schema")) throw new Error(`${e.message}.\nIt appears that you are using z.transform() - it is highly recommended that you use z.codec instead - https://zod.dev/codecs`);
|
|
612
|
+
throw e;
|
|
613
|
+
}
|
|
347
614
|
}
|
|
348
615
|
};
|
|
616
|
+
const openApiGenerator = new OpenAPIGenerator();
|
|
617
|
+
function _registerControllerClass(controllerClass, prefix) {
|
|
618
|
+
openApiGenerator.registerController(controllerClass, prefix);
|
|
619
|
+
}
|
|
620
|
+
function setOpenApiConfig(config) {
|
|
621
|
+
openApiGenerator.setConfig(config);
|
|
622
|
+
}
|
|
623
|
+
function generateOpenApiSpec() {
|
|
624
|
+
return openApiGenerator.generateSpec();
|
|
625
|
+
}
|
|
349
626
|
//#endregion
|
|
350
627
|
//#region src/types.ts
|
|
351
628
|
const methodResolve = {
|
|
@@ -479,138 +756,58 @@ function _resolve(ctor) {
|
|
|
479
756
|
return _InjectableRegistry.get(ctor);
|
|
480
757
|
}
|
|
481
758
|
//#endregion
|
|
482
|
-
//#region src/annotation/
|
|
483
|
-
const
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return newFn;
|
|
491
|
-
})();
|
|
492
|
-
const existing = byFn.get(fnName);
|
|
493
|
-
if (existing) return existing;
|
|
494
|
-
const created = {};
|
|
495
|
-
byFn.set(fnName, created);
|
|
496
|
-
return created;
|
|
497
|
-
}
|
|
498
|
-
function _setOnce(def, key, schema, fnName) {
|
|
499
|
-
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
500
|
-
def[key] = schema;
|
|
759
|
+
//#region src/annotation/validator.ts
|
|
760
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
761
|
+
function ResponseBody(schema) {
|
|
762
|
+
return (target, propertyKey) => {
|
|
763
|
+
const ctor = target.constructor;
|
|
764
|
+
const fnName = String(propertyKey);
|
|
765
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
766
|
+
};
|
|
501
767
|
}
|
|
502
768
|
function RequestBody(schema) {
|
|
503
769
|
return (target, propertyKey) => {
|
|
504
770
|
const ctor = target.constructor;
|
|
505
771
|
const fnName = String(propertyKey);
|
|
506
|
-
_setOnce(
|
|
772
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
507
773
|
};
|
|
508
774
|
}
|
|
509
775
|
function RequestParam(schema) {
|
|
510
776
|
return (target, propertyKey) => {
|
|
511
777
|
const ctor = target.constructor;
|
|
512
778
|
const fnName = String(propertyKey);
|
|
513
|
-
_setOnce(
|
|
779
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
514
780
|
};
|
|
515
781
|
}
|
|
516
782
|
function RequestQuery(schema) {
|
|
517
783
|
return (target, propertyKey) => {
|
|
518
784
|
const ctor = target.constructor;
|
|
519
785
|
const fnName = String(propertyKey);
|
|
520
|
-
_setOnce(
|
|
786
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
521
787
|
};
|
|
522
788
|
}
|
|
523
|
-
function
|
|
524
|
-
|
|
789
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
790
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
791
|
+
const existing = byFn.get(fnName);
|
|
792
|
+
if (existing) return existing;
|
|
793
|
+
const created = {};
|
|
794
|
+
byFn.set(fnName, created);
|
|
795
|
+
return created;
|
|
525
796
|
}
|
|
526
797
|
async function _parseOrThrow(schema, input, kind) {
|
|
527
798
|
const result = await schema["~standard"].validate(input);
|
|
528
799
|
if (result.issues) {
|
|
529
|
-
console.debug(`Failed to parse
|
|
800
|
+
console.debug(`Failed to parse ${schema["~standard"].vendor} schema\nissues: ${JSON.stringify(result.issues, null, 2)}`);
|
|
530
801
|
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
531
802
|
}
|
|
532
803
|
return result.value;
|
|
533
804
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
537
|
-
/**
|
|
538
|
-
* Custom annotation that will store all routes inside of a map,
|
|
539
|
-
* which can then be used to initialize all the routes to the router.
|
|
540
|
-
*/
|
|
541
|
-
function _Route({ method, path = "" }) {
|
|
542
|
-
return (target, propertyKey) => {
|
|
543
|
-
const ctor = target.constructor;
|
|
544
|
-
const list = _routeStore.get(ctor) ?? [];
|
|
545
|
-
list.push({
|
|
546
|
-
method,
|
|
547
|
-
path: path ?? "",
|
|
548
|
-
fnName: String(propertyKey)
|
|
549
|
-
});
|
|
550
|
-
_routeStore.set(ctor, list);
|
|
551
|
-
};
|
|
805
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
806
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
552
807
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const GET = (path = "") => _Route({
|
|
557
|
-
method: "GET",
|
|
558
|
-
path
|
|
559
|
-
});
|
|
560
|
-
/**
|
|
561
|
-
* Register POST route on the given path (default "") for the given controller.
|
|
562
|
-
*/
|
|
563
|
-
const POST = (path = "") => _Route({
|
|
564
|
-
method: "POST",
|
|
565
|
-
path
|
|
566
|
-
});
|
|
567
|
-
/**
|
|
568
|
-
* Register PUT route on the given path (default "") for the given controller.
|
|
569
|
-
*/
|
|
570
|
-
const PUT = (path = "") => _Route({
|
|
571
|
-
method: "PUT",
|
|
572
|
-
path
|
|
573
|
-
});
|
|
574
|
-
/**
|
|
575
|
-
* Register DELETE route on the given path (default "") for the given controller.
|
|
576
|
-
*/
|
|
577
|
-
const DELETE = (path = "") => _Route({
|
|
578
|
-
method: "DELETE",
|
|
579
|
-
path
|
|
580
|
-
});
|
|
581
|
-
/**
|
|
582
|
-
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
583
|
-
*/
|
|
584
|
-
const OPTIONS = (path = "") => _Route({
|
|
585
|
-
method: "OPTIONS",
|
|
586
|
-
path
|
|
587
|
-
});
|
|
588
|
-
/**
|
|
589
|
-
* Register PATCH route on the given path (default "") for the given controller.
|
|
590
|
-
*/
|
|
591
|
-
const PATCH = (path = "") => _Route({
|
|
592
|
-
method: "PATCH",
|
|
593
|
-
path
|
|
594
|
-
});
|
|
595
|
-
/**
|
|
596
|
-
* Register HEAD route on the given path (default "") for the given controller.
|
|
597
|
-
*/
|
|
598
|
-
const HEAD = (path = "") => _Route({
|
|
599
|
-
method: "HEAD",
|
|
600
|
-
path
|
|
601
|
-
});
|
|
602
|
-
/**
|
|
603
|
-
* Register a middleware route on the given path (default "") for the given controller.
|
|
604
|
-
*/
|
|
605
|
-
const Middleware = (path = "") => _Route({
|
|
606
|
-
method: "USE",
|
|
607
|
-
path
|
|
608
|
-
});
|
|
609
|
-
/**
|
|
610
|
-
* Given a class constructor, fetch all the routes attached.
|
|
611
|
-
*/
|
|
612
|
-
function _getRoutes(ctor) {
|
|
613
|
-
return _routeStore.get(ctor) ?? [];
|
|
808
|
+
function _setOnce(def, key, schema, fnName) {
|
|
809
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
810
|
+
def[key] = schema;
|
|
614
811
|
}
|
|
615
812
|
//#endregion
|
|
616
813
|
//#region src/annotation/controller.ts
|
|
@@ -624,6 +821,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
624
821
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
625
822
|
return (target) => {
|
|
626
823
|
const targetClass = target;
|
|
824
|
+
_registerControllerClass(target, prefix);
|
|
627
825
|
const router = Router();
|
|
628
826
|
const routes = _getRoutes(target);
|
|
629
827
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -666,12 +864,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
666
864
|
return;
|
|
667
865
|
}
|
|
668
866
|
router[methodName](fp, async (request, response, next) => {
|
|
669
|
-
const schemas =
|
|
867
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
670
868
|
if (schemas) {
|
|
671
|
-
if (schemas.
|
|
672
|
-
if (schemas.
|
|
673
|
-
if (schemas.
|
|
674
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
869
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
870
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
871
|
+
if (schemas.requestQuery) {
|
|
872
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
675
873
|
Object.defineProperty(request, "query", {
|
|
676
874
|
value: parsedQuery,
|
|
677
875
|
writable: true,
|
|
@@ -681,7 +879,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
681
879
|
}
|
|
682
880
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
683
881
|
if (result instanceof ResponseEntity) {
|
|
684
|
-
|
|
882
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
883
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
685
884
|
return;
|
|
686
885
|
}
|
|
687
886
|
if (result instanceof RedirectView) {
|
|
@@ -715,7 +914,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
715
914
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
716
915
|
}
|
|
717
916
|
//#endregion
|
|
718
|
-
//#region src/middleware/default/base.ts
|
|
917
|
+
//#region src/middleware/default/error/base.ts
|
|
719
918
|
let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
720
919
|
handle(err, _request, _response, _next) {
|
|
721
920
|
console.error("[Error]", err);
|
|
@@ -725,7 +924,17 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
|
725
924
|
__decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
|
|
726
925
|
DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
|
|
727
926
|
//#endregion
|
|
728
|
-
//#region src/middleware/default/
|
|
927
|
+
//#region src/middleware/default/error/parse.ts
|
|
928
|
+
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
929
|
+
handle(err, _request, _response, next) {
|
|
930
|
+
if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
931
|
+
next(err);
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
__decorate([Middleware()], DefaultParserErrorMiddleware.prototype, "handle", null);
|
|
935
|
+
DefaultParserErrorMiddleware = __decorate([MiddlewareClass()], DefaultParserErrorMiddleware);
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/middleware/default/error/responsestatus.ts
|
|
729
938
|
let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
|
|
730
939
|
handle(err, _request, _response, next) {
|
|
731
940
|
if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
@@ -735,4 +944,39 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
|
|
|
735
944
|
__decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
|
|
736
945
|
DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
|
|
737
946
|
//#endregion
|
|
738
|
-
|
|
947
|
+
//#region src/middleware/default/openapi/index.ts
|
|
948
|
+
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
949
|
+
handle(_request, _response, _next) {
|
|
950
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
954
|
+
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
955
|
+
//#endregion
|
|
956
|
+
//#region src/middleware/default/swagger/index.ts
|
|
957
|
+
let Serve = class Serve {
|
|
958
|
+
constructor() {
|
|
959
|
+
this.handlers = swagger.serve;
|
|
960
|
+
}
|
|
961
|
+
handle(request, response, next) {
|
|
962
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
966
|
+
Serve = __decorate([MiddlewareClass()], Serve);
|
|
967
|
+
let Setup = class Setup {
|
|
968
|
+
constructor() {
|
|
969
|
+
this.handler = swagger.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
970
|
+
}
|
|
971
|
+
handle(request, response, next) {
|
|
972
|
+
return this.handler(request, response, next);
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
976
|
+
Setup = __decorate([MiddlewareClass()], Setup);
|
|
977
|
+
const DefaultSwaggerMiddleware = {
|
|
978
|
+
Serve,
|
|
979
|
+
Setup
|
|
980
|
+
};
|
|
981
|
+
//#endregion
|
|
982
|
+
export { Controller, ControllerSchema, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteSchema, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setControllerSchema, _setOnce, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tahminator/sapling",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5-beta.0b3bd061",
|
|
4
4
|
"author": "Tahmid Ahmed",
|
|
5
5
|
"description": "A library to help you write cleaner Express.js code",
|
|
6
6
|
"repository": {
|
|
@@ -44,12 +44,14 @@
|
|
|
44
44
|
"@standard-schema/spec": "^1.1.0",
|
|
45
45
|
"@types/express": "^5",
|
|
46
46
|
"@types/supertest": "^7.2.0",
|
|
47
|
+
"@types/swagger-ui-express": "^4.1.8",
|
|
47
48
|
"@vitest/coverage-istanbul": "^4.1.2",
|
|
48
49
|
"eslint": "^10.1.0",
|
|
49
50
|
"eslint-plugin-perfectionist": "^5.7.0",
|
|
50
51
|
"globals": "^17.4.0",
|
|
51
52
|
"jiti": "^2.6.1",
|
|
52
53
|
"jsdom": "^29.0.1",
|
|
54
|
+
"openapi-types": "12.1.3",
|
|
53
55
|
"prettier": "^3.8.1",
|
|
54
56
|
"superjson": "^2.2.6",
|
|
55
57
|
"supertest": "^7.2.2",
|
|
@@ -60,6 +62,10 @@
|
|
|
60
62
|
"zod": "^4.4.3"
|
|
61
63
|
},
|
|
62
64
|
"inlinedDependencies": {
|
|
63
|
-
"@standard-schema/spec": "1.1.0"
|
|
65
|
+
"@standard-schema/spec": "1.1.0",
|
|
66
|
+
"openapi-types": "12.1.3"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"swagger-ui-express": "^5.0.1"
|
|
64
70
|
}
|
|
65
71
|
}
|