@tahminator/sapling 1.5.27-beta.3d0c6593 → 1.5.28-beta.583b1882

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 CHANGED
@@ -507,6 +507,60 @@ function _resolve(ctor) {
507
507
  return _InjectableRegistry.get(ctor);
508
508
  }
509
509
  //#endregion
510
+ //#region src/annotation/request.ts
511
+ const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
512
+ function _getOrCreateDef(ctor, fnName) {
513
+ let byFn = _requestSchemaStore.get(ctor);
514
+ if (!byFn) {
515
+ byFn = /* @__PURE__ */ new Map();
516
+ _requestSchemaStore.set(ctor, byFn);
517
+ }
518
+ const existing = byFn.get(fnName);
519
+ if (existing) return existing;
520
+ const created = {};
521
+ byFn.set(fnName, created);
522
+ return created;
523
+ }
524
+ function _setOnce(def, key, schema, fnName) {
525
+ if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
526
+ def[key] = schema;
527
+ }
528
+ function RequestBody(schema) {
529
+ return (target, propertyKey) => {
530
+ const ctor = target.constructor;
531
+ const fnName = String(propertyKey);
532
+ _setOnce(_getOrCreateDef(ctor, fnName), "body", schema, fnName);
533
+ };
534
+ }
535
+ function RequestParam(schema) {
536
+ return (target, propertyKey) => {
537
+ const ctor = target.constructor;
538
+ const fnName = String(propertyKey);
539
+ _setOnce(_getOrCreateDef(ctor, fnName), "param", schema, fnName);
540
+ };
541
+ }
542
+ function RequestQuery(schema) {
543
+ return (target, propertyKey) => {
544
+ const ctor = target.constructor;
545
+ const fnName = String(propertyKey);
546
+ _setOnce(_getOrCreateDef(ctor, fnName), "query", schema, fnName);
547
+ };
548
+ }
549
+ function _getRequestSchemas(ctor, fnName) {
550
+ return _requestSchemaStore.get(ctor)?.get(fnName);
551
+ }
552
+ function _formatIssues(issues) {
553
+ return issues.map((i) => {
554
+ const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
555
+ return path ? `${path}: ${i.message}` : i.message;
556
+ }).join("; ");
557
+ }
558
+ async function _parseOrThrow(schema, input, kind) {
559
+ const result = await schema["~standard"].validate(input);
560
+ if (!result.issues) return result.value;
561
+ throw new ResponseStatusError(400, `${kind === "body" ? "Invalid request body" : kind === "params" ? "Invalid request params" : "Invalid request query"}: ${_formatIssues(result.issues)}`);
562
+ }
563
+ //#endregion
510
564
  //#region src/annotation/route.ts
511
565
  const _routeStore = /* @__PURE__ */ new WeakMap();
512
566
  /**
@@ -613,6 +667,12 @@ function Controller({ prefix = "", deps = [] } = {}) {
613
667
  if (method !== "USE") usedRoutes.add(routeKey);
614
668
  const methodName = methodResolve[method];
615
669
  router[methodName](fp, async (request, response, next) => {
670
+ const schemas = _getRequestSchemas(target, fnName);
671
+ if (schemas) {
672
+ if (schemas.body) request.body = await _parseOrThrow(schemas.body, request.body, "body");
673
+ if (schemas.param) request.params = await _parseOrThrow(schemas.param, request.params, "params");
674
+ if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "query");
675
+ }
616
676
  const result = await fn.bind(controllerInstance)(request, response, next);
617
677
  if (method === "USE") return;
618
678
  if (result instanceof ResponseEntity) {
@@ -656,6 +716,9 @@ exports.PATCH = PATCH;
656
716
  exports.POST = POST;
657
717
  exports.PUT = PUT;
658
718
  exports.RedirectView = RedirectView;
719
+ exports.RequestBody = RequestBody;
720
+ exports.RequestParam = RequestParam;
721
+ exports.RequestQuery = RequestQuery;
659
722
  exports.ResponseEntity = ResponseEntity;
660
723
  exports.ResponseEntityBuilder = ResponseEntityBuilder;
661
724
  exports.ResponseStatusError = ResponseStatusError;
@@ -664,6 +727,8 @@ exports._ControllerRegistry = _ControllerRegistry;
664
727
  exports._InjectableDeps = _InjectableDeps;
665
728
  exports._InjectableRegistry = _InjectableRegistry;
666
729
  exports._Route = _Route;
730
+ exports._getRequestSchemas = _getRequestSchemas;
667
731
  exports._getRoutes = _getRoutes;
732
+ exports._parseOrThrow = _parseOrThrow;
668
733
  exports._resolve = _resolve;
669
734
  exports.methodResolve = methodResolve;
package/dist/index.d.cts CHANGED
@@ -153,6 +153,96 @@ 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
234
+ //#region src/annotation/request.d.ts
235
+ type RequestSchemaDefinition = {
236
+ body?: StandardSchemaV1;
237
+ param?: StandardSchemaV1;
238
+ query?: StandardSchemaV1;
239
+ };
240
+ declare function RequestBody(schema: StandardSchemaV1): MethodDecorator;
241
+ declare function RequestParam(schema: StandardSchemaV1): MethodDecorator;
242
+ declare function RequestQuery(schema: StandardSchemaV1): MethodDecorator;
243
+ declare function _getRequestSchemas(ctor: Function, fnName: string): RequestSchemaDefinition | undefined;
244
+ declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: "body" | "params" | "query"): Promise<StandardSchemaV1.InferOutput<TSchema>>;
245
+ //#endregion
156
246
  //#region src/helper/redirect.d.ts
157
247
  /**
158
248
  * Generic HTTP redirect wrapped modeled after Spring's `RedirectView`.
@@ -418,4 +508,4 @@ declare class Sapling {
418
508
  static setDeserializeFn(this: void, fn: (value: string) => any): void;
419
509
  }
420
510
  //#endregion
421
- export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, RedirectView, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRoutes, _resolve, methodResolve };
511
+ export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, 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,96 @@ 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
234
+ //#region src/annotation/request.d.ts
235
+ type RequestSchemaDefinition = {
236
+ body?: StandardSchemaV1;
237
+ param?: StandardSchemaV1;
238
+ query?: StandardSchemaV1;
239
+ };
240
+ declare function RequestBody(schema: StandardSchemaV1): MethodDecorator;
241
+ declare function RequestParam(schema: StandardSchemaV1): MethodDecorator;
242
+ declare function RequestQuery(schema: StandardSchemaV1): MethodDecorator;
243
+ declare function _getRequestSchemas(ctor: Function, fnName: string): RequestSchemaDefinition | undefined;
244
+ declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: "body" | "params" | "query"): Promise<StandardSchemaV1.InferOutput<TSchema>>;
245
+ //#endregion
156
246
  //#region src/helper/redirect.d.ts
157
247
  /**
158
248
  * Generic HTTP redirect wrapped modeled after Spring's `RedirectView`.
@@ -418,4 +508,4 @@ declare class Sapling {
418
508
  static setDeserializeFn(this: void, fn: (value: string) => any): void;
419
509
  }
420
510
  //#endregion
421
- export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, RedirectView, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRoutes, _resolve, methodResolve };
511
+ export { Class, Controller, DELETE, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRequestSchemas, _getRoutes, _parseOrThrow, _resolve, methodResolve };
package/dist/index.mjs CHANGED
@@ -483,6 +483,60 @@ function _resolve(ctor) {
483
483
  return _InjectableRegistry.get(ctor);
484
484
  }
485
485
  //#endregion
486
+ //#region src/annotation/request.ts
487
+ const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
488
+ function _getOrCreateDef(ctor, fnName) {
489
+ let byFn = _requestSchemaStore.get(ctor);
490
+ if (!byFn) {
491
+ byFn = /* @__PURE__ */ new Map();
492
+ _requestSchemaStore.set(ctor, byFn);
493
+ }
494
+ const existing = byFn.get(fnName);
495
+ if (existing) return existing;
496
+ const created = {};
497
+ byFn.set(fnName, created);
498
+ return created;
499
+ }
500
+ function _setOnce(def, key, schema, fnName) {
501
+ if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
502
+ def[key] = schema;
503
+ }
504
+ function RequestBody(schema) {
505
+ return (target, propertyKey) => {
506
+ const ctor = target.constructor;
507
+ const fnName = String(propertyKey);
508
+ _setOnce(_getOrCreateDef(ctor, fnName), "body", schema, fnName);
509
+ };
510
+ }
511
+ function RequestParam(schema) {
512
+ return (target, propertyKey) => {
513
+ const ctor = target.constructor;
514
+ const fnName = String(propertyKey);
515
+ _setOnce(_getOrCreateDef(ctor, fnName), "param", schema, fnName);
516
+ };
517
+ }
518
+ function RequestQuery(schema) {
519
+ return (target, propertyKey) => {
520
+ const ctor = target.constructor;
521
+ const fnName = String(propertyKey);
522
+ _setOnce(_getOrCreateDef(ctor, fnName), "query", schema, fnName);
523
+ };
524
+ }
525
+ function _getRequestSchemas(ctor, fnName) {
526
+ return _requestSchemaStore.get(ctor)?.get(fnName);
527
+ }
528
+ function _formatIssues(issues) {
529
+ return issues.map((i) => {
530
+ const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
531
+ return path ? `${path}: ${i.message}` : i.message;
532
+ }).join("; ");
533
+ }
534
+ async function _parseOrThrow(schema, input, kind) {
535
+ const result = await schema["~standard"].validate(input);
536
+ if (!result.issues) return result.value;
537
+ throw new ResponseStatusError(400, `${kind === "body" ? "Invalid request body" : kind === "params" ? "Invalid request params" : "Invalid request query"}: ${_formatIssues(result.issues)}`);
538
+ }
539
+ //#endregion
486
540
  //#region src/annotation/route.ts
487
541
  const _routeStore = /* @__PURE__ */ new WeakMap();
488
542
  /**
@@ -589,6 +643,12 @@ function Controller({ prefix = "", deps = [] } = {}) {
589
643
  if (method !== "USE") usedRoutes.add(routeKey);
590
644
  const methodName = methodResolve[method];
591
645
  router[methodName](fp, async (request, response, next) => {
646
+ const schemas = _getRequestSchemas(target, fnName);
647
+ if (schemas) {
648
+ if (schemas.body) request.body = await _parseOrThrow(schemas.body, request.body, "body");
649
+ if (schemas.param) request.params = await _parseOrThrow(schemas.param, request.params, "params");
650
+ if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "query");
651
+ }
592
652
  const result = await fn.bind(controllerInstance)(request, response, next);
593
653
  if (method === "USE") return;
594
654
  if (result instanceof ResponseEntity) {
@@ -618,4 +678,4 @@ function MiddlewareClass(...args) {
618
678
  return Controller(...args);
619
679
  }
620
680
  //#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 };
681
+ export { Controller, DELETE, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, 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.27-beta.3d0c6593",
3
+ "version": "1.5.28-beta.583b1882",
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
  }