@tahminator/sapling 1.5.28-beta.01a8d6cf → 1.5.28-beta.03ae0bff
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 +55 -1
- package/dist/index.cjs +29 -30
- package/dist/index.d.cts +3 -26
- package/dist/index.d.mts +3 -26
- package/dist/index.mjs +29 -30
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ A lightweight Express.js dependency injection & route abstraction library.
|
|
|
19
19
|
* [Responses](#responses)
|
|
20
20
|
* [Error Handling](#error-handling)
|
|
21
21
|
* [Middleware](#middleware)
|
|
22
|
+
* [Request Validation](#request-validation)
|
|
22
23
|
* [Redirects](#redirects)
|
|
23
24
|
* [Dependency Injection](#dependency-injection)
|
|
24
25
|
* [Custom Serialization](#custom-serialization)
|
|
@@ -142,8 +143,11 @@ Sapling supports the usual suspects:
|
|
|
142
143
|
- `@DELETE(path?)`
|
|
143
144
|
- `@PATCH(path?)`
|
|
144
145
|
- `@Middleware(path?)` - for middleware
|
|
146
|
+
- `@RequestBody(schema)` - validate & parse the request body
|
|
147
|
+
- `@RequestParam(schema)` - validate & parse route params
|
|
148
|
+
- `@RequestQuery(schema)` - validate & parse the query string
|
|
145
149
|
|
|
146
|
-
Path defaults to `"/"` if you don't pass one.
|
|
150
|
+
Path defaults to `"/"` if you don't pass one. The request schema decorators accept any [Standard Schema](https://github.com/standard-schema/standard-schema) compatible validator (e.g. Zod, Valibot, ArkType).
|
|
147
151
|
|
|
148
152
|
### Responses
|
|
149
153
|
|
|
@@ -234,6 +238,56 @@ app.use(Sapling.resolve(CookieParserMiddleware));
|
|
|
234
238
|
app.use(cookieParser());
|
|
235
239
|
```
|
|
236
240
|
|
|
241
|
+
### Request Validation
|
|
242
|
+
|
|
243
|
+
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.).
|
|
244
|
+
|
|
245
|
+
If validation fails, a `ParserError` is thrown, which Express handles as a `400 Bad Request` by default:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { z } from "zod";
|
|
249
|
+
|
|
250
|
+
const CreateUserSchema = z.object({ name: z.string(), age: z.number() });
|
|
251
|
+
const UserParamsSchema = z.object({ id: z.string() });
|
|
252
|
+
const ListUsersQuerySchema = z.object({ page: z.coerce.number() });
|
|
253
|
+
|
|
254
|
+
@Controller({ prefix: "/users" })
|
|
255
|
+
class UserController {
|
|
256
|
+
@RequestBody(CreateUserSchema)
|
|
257
|
+
@POST()
|
|
258
|
+
createUser(request: Request): ResponseEntity<User> {
|
|
259
|
+
// request.body has been fully validated and rewritten. you can safely assert the type!
|
|
260
|
+
const requestBody = request.body as unknown as z.infer<CreateUserSchema>;
|
|
261
|
+
|
|
262
|
+
const user = this.database.user.create(requestBody.name, requestBody.age)
|
|
263
|
+
|
|
264
|
+
return ResponseEntity.ok().body(user);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@RequestParam(UserParamsSchema)
|
|
268
|
+
@GET("/:id")
|
|
269
|
+
getUser(request: Request): ResponseEntity<z.infer<typeof UserParamsSchema>> {
|
|
270
|
+
// request.params has been fully validated and rewritten. you can safely assert the type!
|
|
271
|
+
const params = request.params as unknown as z.infer<typeof UserParamsSchema>;
|
|
272
|
+
|
|
273
|
+
const user = this.database.user.findById(params.id);
|
|
274
|
+
|
|
275
|
+
return ResponseEntity.ok().body(user);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@RequestQuery(ListUsersQuerySchema)
|
|
279
|
+
@GET()
|
|
280
|
+
listUsers(request: Request): ResponseEntity<z.infer<typeof ListUsersQuerySchema>> {
|
|
281
|
+
// request.query has been fully validated and rewritten. you can safely assert the type!
|
|
282
|
+
const query = request.query as unknown as z.infer<typeof ListUsersQuerySchema>;
|
|
283
|
+
|
|
284
|
+
const users = this.database.user.findAll({ page: query.page });
|
|
285
|
+
|
|
286
|
+
return ResponseEntity.ok().body(users);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
237
291
|
### Redirects
|
|
238
292
|
|
|
239
293
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -310,34 +310,6 @@ var Sapling = class Sapling {
|
|
|
310
310
|
app.use(Sapling.json());
|
|
311
311
|
}
|
|
312
312
|
/**
|
|
313
|
-
* Register a middleware that will handle {@link ResponseStatusError}.
|
|
314
|
-
*
|
|
315
|
-
* This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
|
|
316
|
-
* You may still define middleware to handle all other errors in a separate `app.use` call.
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* ```ts
|
|
320
|
-
* import express from "express";
|
|
321
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
322
|
-
*
|
|
323
|
-
* const app = express();
|
|
324
|
-
*
|
|
325
|
-
* Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
|
|
326
|
-
* // `err` is guaranteed to be of type ResponseStatusError
|
|
327
|
-
* res.status(err.status).json({
|
|
328
|
-
* success: false,
|
|
329
|
-
* message: err.message,
|
|
330
|
-
* });
|
|
331
|
-
* });
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
|
-
static loadResponseStatusErrorMiddleware(app, fn) {
|
|
335
|
-
app.use(((err, req, res, next) => {
|
|
336
|
-
if (err instanceof ResponseStatusError) fn(err, req, res, next);
|
|
337
|
-
else next(err);
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
313
|
* Serialize a value into a JSON string.
|
|
342
314
|
*
|
|
343
315
|
* This function is used in {@link ResponseEntity} to serialize the `body`.
|
|
@@ -678,6 +650,14 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
678
650
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
679
651
|
_InjectableDeps.set(targetClass, deps);
|
|
680
652
|
const controllerInstance = _resolve(targetClass);
|
|
653
|
+
if (routes.reduce((prev, r) => {
|
|
654
|
+
if (r.method !== "USE") return prev;
|
|
655
|
+
const fn = controllerInstance[r.fnName];
|
|
656
|
+
return typeof fn === "function" && fn.length >= 4 ? prev + 1 : prev;
|
|
657
|
+
}, 0) > 1) throw new Error(`Invalid @MiddlewareClass class "${targetClass.name}":
|
|
658
|
+
Multiple 4-arg @Middleware() error handlers were found.
|
|
659
|
+
Express will not enter routers in error mode, so an error-middleware class must expose exactly one error handler.
|
|
660
|
+
Split these into separate @MiddlewareClass classes, or merge the logic into a single method.`);
|
|
681
661
|
for (const { method, path, fnName } of routes) {
|
|
682
662
|
const fn = controllerInstance[fnName];
|
|
683
663
|
if (typeof fn !== "function") continue;
|
|
@@ -686,6 +666,26 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
686
666
|
if (method !== "USE" && usedRoutes.has(routeKey)) throw new Error(`Duplicate route [${method}] "${path instanceof RegExp ? path.source : fp}" detected in controller "${target.name}"`);
|
|
687
667
|
if (method !== "USE") usedRoutes.add(routeKey);
|
|
688
668
|
const methodName = methodResolve[method];
|
|
669
|
+
if (method === "USE" && fn.length >= 4) {
|
|
670
|
+
const middlewareFn = async (err, request, response, next) => {
|
|
671
|
+
try {
|
|
672
|
+
const result = fn.bind(controllerInstance)(err, request, response, next);
|
|
673
|
+
if (result instanceof ResponseEntity) {
|
|
674
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (result instanceof RedirectView) {
|
|
678
|
+
response.redirect(result.getUrl());
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
} catch (e) {
|
|
682
|
+
console.error(e);
|
|
683
|
+
next(e);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
_ControllerRegistry.set(targetClass, middlewareFn);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
689
|
router[methodName](fp, async (request, response, next) => {
|
|
690
690
|
const schemas = _getRequestSchemas(target, fnName);
|
|
691
691
|
if (schemas) {
|
|
@@ -694,7 +694,6 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
694
694
|
if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
|
|
695
695
|
}
|
|
696
696
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
697
|
-
if (method === "USE") return;
|
|
698
697
|
if (result instanceof ResponseEntity) {
|
|
699
698
|
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
|
|
700
699
|
return;
|
|
@@ -703,7 +702,7 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
703
702
|
response.redirect(result.getUrl());
|
|
704
703
|
return;
|
|
705
704
|
}
|
|
706
|
-
if (!response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
705
|
+
if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
707
706
|
});
|
|
708
707
|
}
|
|
709
708
|
_ControllerRegistry.set(targetClass, router);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { NextFunction, Request, Response, Router } from "express";
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, Response, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -29,7 +29,7 @@ type HttpHeaders = Record<string, string>;
|
|
|
29
29
|
type ExpressMiddlewareFn = ($1: Request, $2: Response, $3: NextFunction) => void;
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/annotation/controller.d.ts
|
|
32
|
-
declare const _ControllerRegistry: WeakMap<Function, Router>;
|
|
32
|
+
declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
|
|
33
33
|
type ControllerProps = {
|
|
34
34
|
/**
|
|
35
35
|
* Optional URL prefix applied to all routes in the controller. Defaults to "".
|
|
@@ -416,7 +416,7 @@ declare class Sapling {
|
|
|
416
416
|
* app.use(router);
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
419
|
-
static resolve<TClass>(this: void, clazz: Class<TClass>): Router;
|
|
419
|
+
static resolve<TClass>(this: void, clazz: Class<TClass>): Router | ErrorRequestHandler;
|
|
420
420
|
/**
|
|
421
421
|
* Register this function as a middleware in order to utilize Sapling's `deserialize` function.
|
|
422
422
|
*
|
|
@@ -443,29 +443,6 @@ declare class Sapling {
|
|
|
443
443
|
* ```
|
|
444
444
|
*/
|
|
445
445
|
static registerApp(app: e.Express): void;
|
|
446
|
-
/**
|
|
447
|
-
* Register a middleware that will handle {@link ResponseStatusError}.
|
|
448
|
-
*
|
|
449
|
-
* This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
|
|
450
|
-
* You may still define middleware to handle all other errors in a separate `app.use` call.
|
|
451
|
-
*
|
|
452
|
-
* @example
|
|
453
|
-
* ```ts
|
|
454
|
-
* import express from "express";
|
|
455
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
456
|
-
*
|
|
457
|
-
* const app = express();
|
|
458
|
-
*
|
|
459
|
-
* Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
|
|
460
|
-
* // `err` is guaranteed to be of type ResponseStatusError
|
|
461
|
-
* res.status(err.status).json({
|
|
462
|
-
* success: false,
|
|
463
|
-
* message: err.message,
|
|
464
|
-
* });
|
|
465
|
-
* });
|
|
466
|
-
* ```
|
|
467
|
-
*/
|
|
468
|
-
static loadResponseStatusErrorMiddleware(this: void, app: e.Express, fn: (err: ResponseStatusError, request: e.Request, response: e.Response, next: e.NextFunction) => void): void;
|
|
469
446
|
/**
|
|
470
447
|
* Serialize a value into a JSON string.
|
|
471
448
|
*
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { NextFunction, Request, Response, Router } from "express";
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, Response, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -29,7 +29,7 @@ type HttpHeaders = Record<string, string>;
|
|
|
29
29
|
type ExpressMiddlewareFn = ($1: Request, $2: Response, $3: NextFunction) => void;
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/annotation/controller.d.ts
|
|
32
|
-
declare const _ControllerRegistry: WeakMap<Function, Router>;
|
|
32
|
+
declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
|
|
33
33
|
type ControllerProps = {
|
|
34
34
|
/**
|
|
35
35
|
* Optional URL prefix applied to all routes in the controller. Defaults to "".
|
|
@@ -416,7 +416,7 @@ declare class Sapling {
|
|
|
416
416
|
* app.use(router);
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
419
|
-
static resolve<TClass>(this: void, clazz: Class<TClass>): Router;
|
|
419
|
+
static resolve<TClass>(this: void, clazz: Class<TClass>): Router | ErrorRequestHandler;
|
|
420
420
|
/**
|
|
421
421
|
* Register this function as a middleware in order to utilize Sapling's `deserialize` function.
|
|
422
422
|
*
|
|
@@ -443,29 +443,6 @@ declare class Sapling {
|
|
|
443
443
|
* ```
|
|
444
444
|
*/
|
|
445
445
|
static registerApp(app: e.Express): void;
|
|
446
|
-
/**
|
|
447
|
-
* Register a middleware that will handle {@link ResponseStatusError}.
|
|
448
|
-
*
|
|
449
|
-
* This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
|
|
450
|
-
* You may still define middleware to handle all other errors in a separate `app.use` call.
|
|
451
|
-
*
|
|
452
|
-
* @example
|
|
453
|
-
* ```ts
|
|
454
|
-
* import express from "express";
|
|
455
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
456
|
-
*
|
|
457
|
-
* const app = express();
|
|
458
|
-
*
|
|
459
|
-
* Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
|
|
460
|
-
* // `err` is guaranteed to be of type ResponseStatusError
|
|
461
|
-
* res.status(err.status).json({
|
|
462
|
-
* success: false,
|
|
463
|
-
* message: err.message,
|
|
464
|
-
* });
|
|
465
|
-
* });
|
|
466
|
-
* ```
|
|
467
|
-
*/
|
|
468
|
-
static loadResponseStatusErrorMiddleware(this: void, app: e.Express, fn: (err: ResponseStatusError, request: e.Request, response: e.Response, next: e.NextFunction) => void): void;
|
|
469
446
|
/**
|
|
470
447
|
* Serialize a value into a JSON string.
|
|
471
448
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -286,34 +286,6 @@ var Sapling = class Sapling {
|
|
|
286
286
|
app.use(Sapling.json());
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
|
-
* Register a middleware that will handle {@link ResponseStatusError}.
|
|
290
|
-
*
|
|
291
|
-
* This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
|
|
292
|
-
* You may still define middleware to handle all other errors in a separate `app.use` call.
|
|
293
|
-
*
|
|
294
|
-
* @example
|
|
295
|
-
* ```ts
|
|
296
|
-
* import express from "express";
|
|
297
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
298
|
-
*
|
|
299
|
-
* const app = express();
|
|
300
|
-
*
|
|
301
|
-
* Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
|
|
302
|
-
* // `err` is guaranteed to be of type ResponseStatusError
|
|
303
|
-
* res.status(err.status).json({
|
|
304
|
-
* success: false,
|
|
305
|
-
* message: err.message,
|
|
306
|
-
* });
|
|
307
|
-
* });
|
|
308
|
-
* ```
|
|
309
|
-
*/
|
|
310
|
-
static loadResponseStatusErrorMiddleware(app, fn) {
|
|
311
|
-
app.use(((err, req, res, next) => {
|
|
312
|
-
if (err instanceof ResponseStatusError) fn(err, req, res, next);
|
|
313
|
-
else next(err);
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
289
|
* Serialize a value into a JSON string.
|
|
318
290
|
*
|
|
319
291
|
* This function is used in {@link ResponseEntity} to serialize the `body`.
|
|
@@ -654,6 +626,14 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
654
626
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
655
627
|
_InjectableDeps.set(targetClass, deps);
|
|
656
628
|
const controllerInstance = _resolve(targetClass);
|
|
629
|
+
if (routes.reduce((prev, r) => {
|
|
630
|
+
if (r.method !== "USE") return prev;
|
|
631
|
+
const fn = controllerInstance[r.fnName];
|
|
632
|
+
return typeof fn === "function" && fn.length >= 4 ? prev + 1 : prev;
|
|
633
|
+
}, 0) > 1) throw new Error(`Invalid @MiddlewareClass class "${targetClass.name}":
|
|
634
|
+
Multiple 4-arg @Middleware() error handlers were found.
|
|
635
|
+
Express will not enter routers in error mode, so an error-middleware class must expose exactly one error handler.
|
|
636
|
+
Split these into separate @MiddlewareClass classes, or merge the logic into a single method.`);
|
|
657
637
|
for (const { method, path, fnName } of routes) {
|
|
658
638
|
const fn = controllerInstance[fnName];
|
|
659
639
|
if (typeof fn !== "function") continue;
|
|
@@ -662,6 +642,26 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
662
642
|
if (method !== "USE" && usedRoutes.has(routeKey)) throw new Error(`Duplicate route [${method}] "${path instanceof RegExp ? path.source : fp}" detected in controller "${target.name}"`);
|
|
663
643
|
if (method !== "USE") usedRoutes.add(routeKey);
|
|
664
644
|
const methodName = methodResolve[method];
|
|
645
|
+
if (method === "USE" && fn.length >= 4) {
|
|
646
|
+
const middlewareFn = async (err, request, response, next) => {
|
|
647
|
+
try {
|
|
648
|
+
const result = fn.bind(controllerInstance)(err, request, response, next);
|
|
649
|
+
if (result instanceof ResponseEntity) {
|
|
650
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (result instanceof RedirectView) {
|
|
654
|
+
response.redirect(result.getUrl());
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
} catch (e) {
|
|
658
|
+
console.error(e);
|
|
659
|
+
next(e);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
_ControllerRegistry.set(targetClass, middlewareFn);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
665
|
router[methodName](fp, async (request, response, next) => {
|
|
666
666
|
const schemas = _getRequestSchemas(target, fnName);
|
|
667
667
|
if (schemas) {
|
|
@@ -670,7 +670,6 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
670
670
|
if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
|
|
671
671
|
}
|
|
672
672
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
673
|
-
if (method === "USE") return;
|
|
674
673
|
if (result instanceof ResponseEntity) {
|
|
675
674
|
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
|
|
676
675
|
return;
|
|
@@ -679,7 +678,7 @@ function Controller({ prefix = "", deps = [] } = {}) {
|
|
|
679
678
|
response.redirect(result.getUrl());
|
|
680
679
|
return;
|
|
681
680
|
}
|
|
682
|
-
if (!response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
681
|
+
if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
683
682
|
});
|
|
684
683
|
}
|
|
685
684
|
_ControllerRegistry.set(targetClass, router);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tahminator/sapling",
|
|
3
|
-
"version": "1.5.28-beta.
|
|
3
|
+
"version": "1.5.28-beta.03ae0bff",
|
|
4
4
|
"author": "Tahmid Ahmed",
|
|
5
5
|
"description": "A library to help you write cleaner Express.js code",
|
|
6
6
|
"repository": {
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"tsdown": "^0.21.10",
|
|
57
57
|
"typescript-eslint": "^8.57.2",
|
|
58
58
|
"vite-tsconfig-paths": "^6.1.1",
|
|
59
|
-
"vitest": "^4.1.2"
|
|
59
|
+
"vitest": "^4.1.2",
|
|
60
|
+
"zod": "^4.4.3"
|
|
60
61
|
},
|
|
61
62
|
"inlinedDependencies": {
|
|
62
63
|
"@standard-schema/spec": "1.1.0"
|