@tahminator/sapling 1.5.27-beta.3d0c6593 → 1.5.28-beta.01a8d6cf
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/dist/index.cjs +102 -16
- package/dist/index.d.cts +102 -2
- package/dist/index.d.mts +102 -2
- package/dist/index.mjs +97 -17
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -221,6 +221,22 @@ var ResponseEntityBuilder = class {
|
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
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
|
|
224
240
|
//#region src/helper/sapling.ts
|
|
225
241
|
const settings = {
|
|
226
242
|
serialize: JSON.stringify,
|
|
@@ -359,22 +375,6 @@ var Sapling = class Sapling {
|
|
|
359
375
|
}
|
|
360
376
|
};
|
|
361
377
|
//#endregion
|
|
362
|
-
//#region src/helper/error.ts
|
|
363
|
-
/**
|
|
364
|
-
* Ensure that you define a middleware that can handle this error.
|
|
365
|
-
*
|
|
366
|
-
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
367
|
-
*/
|
|
368
|
-
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
369
|
-
constructor(status, message) {
|
|
370
|
-
super(message ?? "Something went wrong.");
|
|
371
|
-
this.status = status;
|
|
372
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
373
|
-
this.name = `HttpError(${HttpStatus[status]})`;
|
|
374
|
-
if (Error.captureStackTrace) Error.captureStackTrace(this, ResponseStatusError);
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
//#endregion
|
|
378
378
|
//#region src/types.ts
|
|
379
379
|
const methodResolve = {
|
|
380
380
|
GET: "get",
|
|
@@ -507,6 +507,80 @@ function _resolve(ctor) {
|
|
|
507
507
|
return _InjectableRegistry.get(ctor);
|
|
508
508
|
}
|
|
509
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
|
|
510
584
|
//#region src/annotation/route.ts
|
|
511
585
|
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
512
586
|
/**
|
|
@@ -613,6 +687,12 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
613
687
|
if (method !== "USE") usedRoutes.add(routeKey);
|
|
614
688
|
const methodName = methodResolve[method];
|
|
615
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
|
+
}
|
|
616
696
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
617
697
|
if (method === "USE") return;
|
|
618
698
|
if (result instanceof ResponseEntity) {
|
|
@@ -655,7 +735,11 @@ exports.OPTIONS = OPTIONS;
|
|
|
655
735
|
exports.PATCH = PATCH;
|
|
656
736
|
exports.POST = POST;
|
|
657
737
|
exports.PUT = PUT;
|
|
738
|
+
exports.ParserError = ParserError;
|
|
658
739
|
exports.RedirectView = RedirectView;
|
|
740
|
+
exports.RequestBody = RequestBody;
|
|
741
|
+
exports.RequestParam = RequestParam;
|
|
742
|
+
exports.RequestQuery = RequestQuery;
|
|
659
743
|
exports.ResponseEntity = ResponseEntity;
|
|
660
744
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
661
745
|
exports.ResponseStatusError = ResponseStatusError;
|
|
@@ -664,6 +748,8 @@ exports._ControllerRegistry = _ControllerRegistry;
|
|
|
664
748
|
exports._InjectableDeps = _InjectableDeps;
|
|
665
749
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
666
750
|
exports._Route = _Route;
|
|
751
|
+
exports._getRequestSchemas = _getRequestSchemas;
|
|
667
752
|
exports._getRoutes = _getRoutes;
|
|
753
|
+
exports._parseOrThrow = _parseOrThrow;
|
|
668
754
|
exports._resolve = _resolve;
|
|
669
755
|
exports.methodResolve = methodResolve;
|
package/dist/index.d.cts
CHANGED
|
@@ -153,6 +153,84 @@ declare function _getRoutes(ctor: Function): readonly RouteDefinition[];
|
|
|
153
153
|
*/
|
|
154
154
|
declare function MiddlewareClass(...args: Parameters<typeof Controller>): ClassDecorator;
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
|
|
157
|
+
/** The Standard Typed interface. This is a base type extended by other specs. */
|
|
158
|
+
interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
159
|
+
/** The Standard properties. */
|
|
160
|
+
readonly "~standard": StandardTypedV1.Props<Input, Output>;
|
|
161
|
+
}
|
|
162
|
+
declare namespace StandardTypedV1 {
|
|
163
|
+
/** The Standard Typed properties interface. */
|
|
164
|
+
interface Props<Input = unknown, Output = Input> {
|
|
165
|
+
/** The version number of the standard. */
|
|
166
|
+
readonly version: 1;
|
|
167
|
+
/** The vendor name of the schema library. */
|
|
168
|
+
readonly vendor: string;
|
|
169
|
+
/** Inferred types associated with the schema. */
|
|
170
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
171
|
+
}
|
|
172
|
+
/** The Standard Typed types interface. */
|
|
173
|
+
interface Types<Input = unknown, Output = Input> {
|
|
174
|
+
/** The input type of the schema. */
|
|
175
|
+
readonly input: Input;
|
|
176
|
+
/** The output type of the schema. */
|
|
177
|
+
readonly output: Output;
|
|
178
|
+
}
|
|
179
|
+
/** Infers the input type of a Standard Typed. */
|
|
180
|
+
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
181
|
+
/** Infers the output type of a Standard Typed. */
|
|
182
|
+
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
183
|
+
}
|
|
184
|
+
/** The Standard Schema interface. */
|
|
185
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
186
|
+
/** The Standard Schema properties. */
|
|
187
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
188
|
+
}
|
|
189
|
+
declare namespace StandardSchemaV1 {
|
|
190
|
+
/** The Standard Schema properties interface. */
|
|
191
|
+
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
|
|
192
|
+
/** Validates unknown input values. */
|
|
193
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
|
|
194
|
+
}
|
|
195
|
+
/** The result interface of the validate function. */
|
|
196
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
197
|
+
/** The result interface if validation succeeds. */
|
|
198
|
+
interface SuccessResult<Output> {
|
|
199
|
+
/** The typed output value. */
|
|
200
|
+
readonly value: Output;
|
|
201
|
+
/** A falsy value for `issues` indicates success. */
|
|
202
|
+
readonly issues?: undefined;
|
|
203
|
+
}
|
|
204
|
+
interface Options {
|
|
205
|
+
/** Explicit support for additional vendor-specific parameters, if needed. */
|
|
206
|
+
readonly libraryOptions?: Record<string, unknown> | undefined;
|
|
207
|
+
}
|
|
208
|
+
/** The result interface if validation fails. */
|
|
209
|
+
interface FailureResult {
|
|
210
|
+
/** The issues of failed validation. */
|
|
211
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
212
|
+
}
|
|
213
|
+
/** The issue interface of the failure output. */
|
|
214
|
+
interface Issue {
|
|
215
|
+
/** The error message of the issue. */
|
|
216
|
+
readonly message: string;
|
|
217
|
+
/** The path of the issue, if any. */
|
|
218
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
219
|
+
}
|
|
220
|
+
/** The path segment interface of the issue. */
|
|
221
|
+
interface PathSegment {
|
|
222
|
+
/** The key representing a path segment. */
|
|
223
|
+
readonly key: PropertyKey;
|
|
224
|
+
}
|
|
225
|
+
/** The Standard types interface. */
|
|
226
|
+
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
|
|
227
|
+
/** Infers the input type of a Standard. */
|
|
228
|
+
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
|
229
|
+
/** Infers the output type of a Standard. */
|
|
230
|
+
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
|
231
|
+
}
|
|
232
|
+
/** The Standard JSON Schema interface. */
|
|
233
|
+
//#endregion
|
|
156
234
|
//#region src/helper/redirect.d.ts
|
|
157
235
|
/**
|
|
158
236
|
* Generic HTTP redirect wrapped modeled after Spring's `RedirectView`.
|
|
@@ -308,7 +386,7 @@ declare enum HttpStatus {
|
|
|
308
386
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
|
309
387
|
}
|
|
310
388
|
//#endregion
|
|
311
|
-
//#region src/helper/error.d.ts
|
|
389
|
+
//#region src/helper/error/responsestatus.d.ts
|
|
312
390
|
/**
|
|
313
391
|
* Ensure that you define a middleware that can handle this error.
|
|
314
392
|
*
|
|
@@ -418,4 +496,26 @@ declare class Sapling {
|
|
|
418
496
|
static setDeserializeFn(this: void, fn: (value: string) => any): void;
|
|
419
497
|
}
|
|
420
498
|
//#endregion
|
|
421
|
-
|
|
499
|
+
//#region src/helper/error/exception.d.ts
|
|
500
|
+
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery";
|
|
501
|
+
/**
|
|
502
|
+
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
503
|
+
*/
|
|
504
|
+
declare class ParserError extends ResponseStatusError {
|
|
505
|
+
constructor(location: ParserErrorLocation, issues: readonly StandardSchemaV1.Issue[], vendor: string);
|
|
506
|
+
private static formatMessage;
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/annotation/request.d.ts
|
|
510
|
+
type RequestSchemaDefinition = {
|
|
511
|
+
body?: StandardSchemaV1;
|
|
512
|
+
param?: StandardSchemaV1;
|
|
513
|
+
query?: StandardSchemaV1;
|
|
514
|
+
};
|
|
515
|
+
declare function RequestBody(schema: StandardSchemaV1): MethodDecorator;
|
|
516
|
+
declare function RequestParam(schema: StandardSchemaV1): MethodDecorator;
|
|
517
|
+
declare function RequestQuery(schema: StandardSchemaV1): MethodDecorator;
|
|
518
|
+
declare function _getRequestSchemas(ctor: Function, fnName: string): RequestSchemaDefinition | undefined;
|
|
519
|
+
declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: ParserErrorLocation): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
520
|
+
//#endregion
|
|
521
|
+
export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRequestSchemas, _getRoutes, _parseOrThrow, _resolve, methodResolve };
|
package/dist/index.d.mts
CHANGED
|
@@ -153,6 +153,84 @@ declare function _getRoutes(ctor: Function): readonly RouteDefinition[];
|
|
|
153
153
|
*/
|
|
154
154
|
declare function MiddlewareClass(...args: Parameters<typeof Controller>): ClassDecorator;
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
|
|
157
|
+
/** The Standard Typed interface. This is a base type extended by other specs. */
|
|
158
|
+
interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
159
|
+
/** The Standard properties. */
|
|
160
|
+
readonly "~standard": StandardTypedV1.Props<Input, Output>;
|
|
161
|
+
}
|
|
162
|
+
declare namespace StandardTypedV1 {
|
|
163
|
+
/** The Standard Typed properties interface. */
|
|
164
|
+
interface Props<Input = unknown, Output = Input> {
|
|
165
|
+
/** The version number of the standard. */
|
|
166
|
+
readonly version: 1;
|
|
167
|
+
/** The vendor name of the schema library. */
|
|
168
|
+
readonly vendor: string;
|
|
169
|
+
/** Inferred types associated with the schema. */
|
|
170
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
171
|
+
}
|
|
172
|
+
/** The Standard Typed types interface. */
|
|
173
|
+
interface Types<Input = unknown, Output = Input> {
|
|
174
|
+
/** The input type of the schema. */
|
|
175
|
+
readonly input: Input;
|
|
176
|
+
/** The output type of the schema. */
|
|
177
|
+
readonly output: Output;
|
|
178
|
+
}
|
|
179
|
+
/** Infers the input type of a Standard Typed. */
|
|
180
|
+
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
181
|
+
/** Infers the output type of a Standard Typed. */
|
|
182
|
+
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
183
|
+
}
|
|
184
|
+
/** The Standard Schema interface. */
|
|
185
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
186
|
+
/** The Standard Schema properties. */
|
|
187
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
188
|
+
}
|
|
189
|
+
declare namespace StandardSchemaV1 {
|
|
190
|
+
/** The Standard Schema properties interface. */
|
|
191
|
+
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
|
|
192
|
+
/** Validates unknown input values. */
|
|
193
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
|
|
194
|
+
}
|
|
195
|
+
/** The result interface of the validate function. */
|
|
196
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
197
|
+
/** The result interface if validation succeeds. */
|
|
198
|
+
interface SuccessResult<Output> {
|
|
199
|
+
/** The typed output value. */
|
|
200
|
+
readonly value: Output;
|
|
201
|
+
/** A falsy value for `issues` indicates success. */
|
|
202
|
+
readonly issues?: undefined;
|
|
203
|
+
}
|
|
204
|
+
interface Options {
|
|
205
|
+
/** Explicit support for additional vendor-specific parameters, if needed. */
|
|
206
|
+
readonly libraryOptions?: Record<string, unknown> | undefined;
|
|
207
|
+
}
|
|
208
|
+
/** The result interface if validation fails. */
|
|
209
|
+
interface FailureResult {
|
|
210
|
+
/** The issues of failed validation. */
|
|
211
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
212
|
+
}
|
|
213
|
+
/** The issue interface of the failure output. */
|
|
214
|
+
interface Issue {
|
|
215
|
+
/** The error message of the issue. */
|
|
216
|
+
readonly message: string;
|
|
217
|
+
/** The path of the issue, if any. */
|
|
218
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
219
|
+
}
|
|
220
|
+
/** The path segment interface of the issue. */
|
|
221
|
+
interface PathSegment {
|
|
222
|
+
/** The key representing a path segment. */
|
|
223
|
+
readonly key: PropertyKey;
|
|
224
|
+
}
|
|
225
|
+
/** The Standard types interface. */
|
|
226
|
+
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
|
|
227
|
+
/** Infers the input type of a Standard. */
|
|
228
|
+
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
|
229
|
+
/** Infers the output type of a Standard. */
|
|
230
|
+
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
|
231
|
+
}
|
|
232
|
+
/** The Standard JSON Schema interface. */
|
|
233
|
+
//#endregion
|
|
156
234
|
//#region src/helper/redirect.d.ts
|
|
157
235
|
/**
|
|
158
236
|
* Generic HTTP redirect wrapped modeled after Spring's `RedirectView`.
|
|
@@ -308,7 +386,7 @@ declare enum HttpStatus {
|
|
|
308
386
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
|
309
387
|
}
|
|
310
388
|
//#endregion
|
|
311
|
-
//#region src/helper/error.d.ts
|
|
389
|
+
//#region src/helper/error/responsestatus.d.ts
|
|
312
390
|
/**
|
|
313
391
|
* Ensure that you define a middleware that can handle this error.
|
|
314
392
|
*
|
|
@@ -418,4 +496,26 @@ declare class Sapling {
|
|
|
418
496
|
static setDeserializeFn(this: void, fn: (value: string) => any): void;
|
|
419
497
|
}
|
|
420
498
|
//#endregion
|
|
421
|
-
|
|
499
|
+
//#region src/helper/error/exception.d.ts
|
|
500
|
+
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery";
|
|
501
|
+
/**
|
|
502
|
+
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
503
|
+
*/
|
|
504
|
+
declare class ParserError extends ResponseStatusError {
|
|
505
|
+
constructor(location: ParserErrorLocation, issues: readonly StandardSchemaV1.Issue[], vendor: string);
|
|
506
|
+
private static formatMessage;
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/annotation/request.d.ts
|
|
510
|
+
type RequestSchemaDefinition = {
|
|
511
|
+
body?: StandardSchemaV1;
|
|
512
|
+
param?: StandardSchemaV1;
|
|
513
|
+
query?: StandardSchemaV1;
|
|
514
|
+
};
|
|
515
|
+
declare function RequestBody(schema: StandardSchemaV1): MethodDecorator;
|
|
516
|
+
declare function RequestParam(schema: StandardSchemaV1): MethodDecorator;
|
|
517
|
+
declare function RequestQuery(schema: StandardSchemaV1): MethodDecorator;
|
|
518
|
+
declare function _getRequestSchemas(ctor: Function, fnName: string): RequestSchemaDefinition | undefined;
|
|
519
|
+
declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: ParserErrorLocation): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
520
|
+
//#endregion
|
|
521
|
+
export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRequestSchemas, _getRoutes, _parseOrThrow, _resolve, methodResolve };
|
package/dist/index.mjs
CHANGED
|
@@ -197,6 +197,22 @@ var ResponseEntityBuilder = class {
|
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
199
|
//#endregion
|
|
200
|
+
//#region src/helper/error/responsestatus.ts
|
|
201
|
+
/**
|
|
202
|
+
* Ensure that you define a middleware that can handle this error.
|
|
203
|
+
*
|
|
204
|
+
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
205
|
+
*/
|
|
206
|
+
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
207
|
+
constructor(status, message) {
|
|
208
|
+
super(message ?? "Something went wrong.");
|
|
209
|
+
this.status = status;
|
|
210
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
211
|
+
this.name = `HttpError(${HttpStatus[status]})`;
|
|
212
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, ResponseStatusError);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
//#endregion
|
|
200
216
|
//#region src/helper/sapling.ts
|
|
201
217
|
const settings = {
|
|
202
218
|
serialize: JSON.stringify,
|
|
@@ -335,22 +351,6 @@ var Sapling = class Sapling {
|
|
|
335
351
|
}
|
|
336
352
|
};
|
|
337
353
|
//#endregion
|
|
338
|
-
//#region src/helper/error.ts
|
|
339
|
-
/**
|
|
340
|
-
* Ensure that you define a middleware that can handle this error.
|
|
341
|
-
*
|
|
342
|
-
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
343
|
-
*/
|
|
344
|
-
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
345
|
-
constructor(status, message) {
|
|
346
|
-
super(message ?? "Something went wrong.");
|
|
347
|
-
this.status = status;
|
|
348
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
349
|
-
this.name = `HttpError(${HttpStatus[status]})`;
|
|
350
|
-
if (Error.captureStackTrace) Error.captureStackTrace(this, ResponseStatusError);
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
//#endregion
|
|
354
354
|
//#region src/types.ts
|
|
355
355
|
const methodResolve = {
|
|
356
356
|
GET: "get",
|
|
@@ -483,6 +483,80 @@ function _resolve(ctor) {
|
|
|
483
483
|
return _InjectableRegistry.get(ctor);
|
|
484
484
|
}
|
|
485
485
|
//#endregion
|
|
486
|
+
//#region src/helper/error/exception.ts
|
|
487
|
+
/**
|
|
488
|
+
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
489
|
+
*/
|
|
490
|
+
var ParserError = class ParserError extends ResponseStatusError {
|
|
491
|
+
constructor(location, issues, vendor) {
|
|
492
|
+
super(400, ParserError.formatMessage(location, issues, vendor));
|
|
493
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
494
|
+
}
|
|
495
|
+
static formatMessage(location, issues, vendor) {
|
|
496
|
+
const formatted = issues.map((i) => {
|
|
497
|
+
const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
|
|
498
|
+
return path ? `${path}: ${i.message}` : i.message;
|
|
499
|
+
}).join("; ");
|
|
500
|
+
return `${vendor} failed to parse ${(() => {
|
|
501
|
+
switch (location) {
|
|
502
|
+
case "reqbody": return "request body";
|
|
503
|
+
case "reqparams": return "request params";
|
|
504
|
+
case "reqquery": return "request query";
|
|
505
|
+
}
|
|
506
|
+
})()}: ${formatted}`;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/annotation/request.ts
|
|
511
|
+
const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
512
|
+
function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
|
|
513
|
+
const byFn = (() => {
|
|
514
|
+
const fn = _requestSchemaStore.get(ctor);
|
|
515
|
+
if (fn) return fn;
|
|
516
|
+
const newFn = /* @__PURE__ */ new Map();
|
|
517
|
+
_requestSchemaStore.set(ctor, newFn);
|
|
518
|
+
return newFn;
|
|
519
|
+
})();
|
|
520
|
+
const existing = byFn.get(fnName);
|
|
521
|
+
if (existing) return existing;
|
|
522
|
+
const created = {};
|
|
523
|
+
byFn.set(fnName, created);
|
|
524
|
+
return created;
|
|
525
|
+
}
|
|
526
|
+
function _setOnce(def, key, schema, fnName) {
|
|
527
|
+
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
528
|
+
def[key] = schema;
|
|
529
|
+
}
|
|
530
|
+
function RequestBody(schema) {
|
|
531
|
+
return (target, propertyKey) => {
|
|
532
|
+
const ctor = target.constructor;
|
|
533
|
+
const fnName = String(propertyKey);
|
|
534
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function RequestParam(schema) {
|
|
538
|
+
return (target, propertyKey) => {
|
|
539
|
+
const ctor = target.constructor;
|
|
540
|
+
const fnName = String(propertyKey);
|
|
541
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function RequestQuery(schema) {
|
|
545
|
+
return (target, propertyKey) => {
|
|
546
|
+
const ctor = target.constructor;
|
|
547
|
+
const fnName = String(propertyKey);
|
|
548
|
+
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function _getRequestSchemas(ctor, fnName) {
|
|
552
|
+
return _requestSchemaStore.get(ctor)?.get(fnName);
|
|
553
|
+
}
|
|
554
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
555
|
+
const result = await schema["~standard"].validate(input);
|
|
556
|
+
if (result.issues) throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
557
|
+
return result.value;
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
486
560
|
//#region src/annotation/route.ts
|
|
487
561
|
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
488
562
|
/**
|
|
@@ -589,6 +663,12 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
589
663
|
if (method !== "USE") usedRoutes.add(routeKey);
|
|
590
664
|
const methodName = methodResolve[method];
|
|
591
665
|
router[methodName](fp, async (request, response, next) => {
|
|
666
|
+
const schemas = _getRequestSchemas(target, fnName);
|
|
667
|
+
if (schemas) {
|
|
668
|
+
if (schemas.body) request.body = await _parseOrThrow(schemas.body, request.body, "reqbody");
|
|
669
|
+
if (schemas.param) request.params = await _parseOrThrow(schemas.param, request.params, "reqparams");
|
|
670
|
+
if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
|
|
671
|
+
}
|
|
592
672
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
593
673
|
if (method === "USE") return;
|
|
594
674
|
if (result instanceof ResponseEntity) {
|
|
@@ -618,4 +698,4 @@ function MiddlewareClass(...args) {
|
|
|
618
698
|
return Controller(...args);
|
|
619
699
|
}
|
|
620
700
|
//#endregion
|
|
621
|
-
export { Controller, DELETE, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, RedirectView, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRoutes, _resolve, methodResolve };
|
|
701
|
+
export { Controller, DELETE, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRequestSchemas, _getRoutes, _parseOrThrow, _resolve, methodResolve };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tahminator/sapling",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.28-beta.01a8d6cf",
|
|
4
4
|
"author": "Tahmid Ahmed",
|
|
5
5
|
"description": "A library to help you write cleaner Express.js code",
|
|
6
6
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@eslint/js": "^10.0.1",
|
|
44
|
+
"@standard-schema/spec": "^1.1.0",
|
|
44
45
|
"@types/express": "^5",
|
|
45
46
|
"@types/supertest": "^7.2.0",
|
|
46
47
|
"@vitest/coverage-istanbul": "^4.1.2",
|
|
@@ -56,5 +57,8 @@
|
|
|
56
57
|
"typescript-eslint": "^8.57.2",
|
|
57
58
|
"vite-tsconfig-paths": "^6.1.1",
|
|
58
59
|
"vitest": "^4.1.2"
|
|
60
|
+
},
|
|
61
|
+
"inlinedDependencies": {
|
|
62
|
+
"@standard-schema/spec": "1.1.0"
|
|
59
63
|
}
|
|
60
64
|
}
|