diff --git a/README.md b/README.md
index c14d22687..549e01bf9 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [@app/armory](./apps/armory/README.md) | |
| [@app/policy-engine](./apps/policy-engine/README.md) | |
+| [@app/vault](./apps/vault/README.md) | |
| [@narval/encryption](./packages/encryption/README.md) | |
| [@narval/policy-engine-shared](./packages/policy-engine-shared/README.md) | |
| [@narval/signature](./packages/signature/README.md) | |
diff --git a/apps/armory/src/main.ts b/apps/armory/src/main.ts
index f8bf31edb..20c966ed8 100644
--- a/apps/armory/src/main.ts
+++ b/apps/armory/src/main.ts
@@ -1,7 +1,7 @@
+import { withSwagger } from '@narval/nestjs-shared'
import { ClassSerializerInterceptor, INestApplication, Logger, ValidationPipe } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory, Reflector } from '@nestjs/core'
-import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { lastValueFrom, map, of, switchMap } from 'rxjs'
import { Config } from './armory.config'
import { ArmoryModule } from './armory.module'
@@ -9,28 +9,6 @@ import { ApplicationExceptionFilter } from './shared/filter/application-exceptio
import { HttpExceptionFilter } from './shared/filter/http-exception.filter'
import { ZodExceptionFilter } from './shared/filter/zod-exception.filter'
-/**
- * Adds Swagger documentation to the application.
- *
- * @param app - The INestApplication instance.
- * @returns The modified INestApplication instance.
- */
-const withSwagger = (app: INestApplication): INestApplication => {
- const document = SwaggerModule.createDocument(
- app,
- new DocumentBuilder()
- .setTitle('Armory')
- .setDescription('Armory is the most secure access management for web3')
- .setVersion('1.0')
- .build()
- )
- SwaggerModule.setup('docs', app, document, {
- customSiteTitle: 'Armory API'
- })
-
- return app
-}
-
/**
* Adds global pipes to the application.
*
@@ -88,7 +66,13 @@ async function bootstrap(): Promise {
await lastValueFrom(
of(application).pipe(
- map(withSwagger),
+ map(
+ withSwagger({
+ title: 'Armory',
+ description: 'Armory is the most secure access management for web3',
+ version: '1.0'
+ })
+ ),
map(withGlobalPipes),
map(withGlobalInterceptors),
map(withGlobalFilters(configService)),
diff --git a/apps/armory/src/orchestration/orchestration.module.ts b/apps/armory/src/orchestration/orchestration.module.ts
index 4e6888759..0e2703f69 100644
--- a/apps/armory/src/orchestration/orchestration.module.ts
+++ b/apps/armory/src/orchestration/orchestration.module.ts
@@ -5,6 +5,7 @@ import { BullModule } from '@nestjs/bull'
import { ClassSerializerInterceptor, Module, ValidationPipe } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'
+import { ZodValidationPipe } from 'nestjs-zod'
import { AUTHORIZATION_REQUEST_PROCESSING_QUEUE } from '../armory.constant'
import { DataFeedModule } from '../data-feed/data-feed.module'
import { PriceModule } from '../price/price.module'
@@ -63,8 +64,13 @@ import { AuthorizationRequestProcessingProducer } from './queue/producer/authori
useClass: ClassSerializerInterceptor
},
{
+ // DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
+ },
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe
}
],
exports: [AuthorizationRequestGateway]
diff --git a/apps/policy-engine/src/engine/engine.module.ts b/apps/policy-engine/src/engine/engine.module.ts
index 463ceed7c..bd411aa0c 100644
--- a/apps/policy-engine/src/engine/engine.module.ts
+++ b/apps/policy-engine/src/engine/engine.module.ts
@@ -3,6 +3,7 @@ import { EncryptionModule } from '@narval/encryption-module'
import { HttpModule } from '@nestjs/axios'
import { Module, ValidationPipe } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'
+import { ZodValidationPipe } from 'nestjs-zod'
import { load } from '../policy-engine.config'
import { EncryptionModuleOptionFactory } from '../shared/factory/encryption-module-option.factory'
import { AdminApiKeyGuard } from '../shared/guard/admin-api-key.guard'
@@ -52,8 +53,13 @@ import { TenantRepository } from './persistence/repository/tenant.repository'
TenantService,
EvaluationService,
{
+ // DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
+ },
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe
}
],
exports: [EngineService, ProvisionService, BootstrapService]
diff --git a/apps/policy-engine/src/main.ts b/apps/policy-engine/src/main.ts
index 0b2e8d420..233194674 100644
--- a/apps/policy-engine/src/main.ts
+++ b/apps/policy-engine/src/main.ts
@@ -1,33 +1,13 @@
import { ConfigService } from '@narval/config-module'
+import { withSwagger } from '@narval/nestjs-shared'
import { INestApplication, Logger, ValidationPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
-import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { lastValueFrom, map, of, switchMap } from 'rxjs'
import { Config } from './policy-engine.config'
import { PolicyEngineModule } from './policy-engine.module'
import { ApplicationExceptionFilter } from './shared/filter/application-exception.filter'
import { HttpExceptionFilter } from './shared/filter/http-exception.filter'
-/**
- * Adds Swagger documentation to the application.
- *
- * @param app - The INestApplication instance.
- * @returns The modified INestApplication instance.
- */
-const withSwagger = (app: INestApplication): INestApplication => {
- const document = SwaggerModule.createDocument(
- app,
- new DocumentBuilder()
- .setTitle('Policy Engine')
- .setDescription('The next generation of authorization for web3')
- .setVersion('1.0')
- .build()
- )
- SwaggerModule.setup('docs', app, document)
-
- return app
-}
-
/**
* Adds global pipes to the application.
*
@@ -67,7 +47,13 @@ async function bootstrap() {
await lastValueFrom(
of(application).pipe(
- map(withSwagger),
+ map(
+ withSwagger({
+ title: 'Policy Engine',
+ description: 'The next generation of authorization for web3',
+ version: '1.0'
+ })
+ ),
map(withGlobalPipes),
map(withGlobalFilters(configService)),
switchMap((app) => app.listen(port))
diff --git a/apps/policy-engine/src/policy-engine.module.ts b/apps/policy-engine/src/policy-engine.module.ts
index e2f624fed..75cf2a95d 100644
--- a/apps/policy-engine/src/policy-engine.module.ts
+++ b/apps/policy-engine/src/policy-engine.module.ts
@@ -2,6 +2,7 @@ import { ConfigModule, ConfigService } from '@narval/config-module'
import { EncryptionModule } from '@narval/encryption-module'
import { Module, OnApplicationBootstrap, ValidationPipe } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'
+import { ZodValidationPipe } from 'nestjs-zod'
import { BootstrapService } from './engine/core/service/bootstrap.service'
import { EngineService } from './engine/core/service/engine.service'
import { ProvisionService } from './engine/core/service/provision.service'
@@ -27,8 +28,13 @@ import { EncryptionModuleOptionFactory } from './shared/factory/encryption-modul
],
providers: [
{
+ // DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
+ },
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe
}
]
})
diff --git a/apps/vault/src/main.module.ts b/apps/vault/src/main.module.ts
index 002e0ca6e..db88dc197 100644
--- a/apps/vault/src/main.module.ts
+++ b/apps/vault/src/main.module.ts
@@ -2,6 +2,7 @@ import { EncryptionModule } from '@narval/encryption-module'
import { Module, ValidationPipe, forwardRef } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { APP_PIPE } from '@nestjs/core'
+import { ZodValidationPipe } from 'nestjs-zod'
import { load } from './main.config'
import { EncryptionModuleOptionFactory } from './shared/factory/encryption-module-option.factory'
import { TenantModule } from './tenant/tenant.module'
@@ -27,8 +28,13 @@ import { VaultModule } from './vault/vault.module'
],
providers: [
{
+ // DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
+ },
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe
}
]
})
diff --git a/apps/vault/src/main.ts b/apps/vault/src/main.ts
index 1e86aa6b6..9464b685c 100644
--- a/apps/vault/src/main.ts
+++ b/apps/vault/src/main.ts
@@ -1,30 +1,10 @@
+import { withSwagger } from '@narval/nestjs-shared'
import { INestApplication, Logger, ValidationPipe } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
-import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { lastValueFrom, map, of, switchMap } from 'rxjs'
import { MainModule } from './main.module'
-/**
- * Adds Swagger documentation to the application.
- *
- * @param app - The INestApplication instance.
- * @returns The modified INestApplication instance.
- */
-const withSwagger = (app: INestApplication): INestApplication => {
- const document = SwaggerModule.createDocument(
- app,
- new DocumentBuilder()
- .setTitle('Vault')
- .setDescription('The next generation of authorization for web3')
- .setVersion('1.0')
- .build()
- )
- SwaggerModule.setup('docs', app, document)
-
- return app
-}
-
/**
* Adds global pipes to the application.
*
@@ -49,7 +29,13 @@ async function bootstrap() {
await lastValueFrom(
of(application).pipe(
- map(withSwagger),
+ map(
+ withSwagger({
+ title: 'Vault',
+ description: 'The next generation of authorization for web3',
+ version: '1.0'
+ })
+ ),
map(withGlobalPipes),
switchMap((app) => app.listen(port))
)
diff --git a/apps/vault/src/tenant/tenant.module.ts b/apps/vault/src/tenant/tenant.module.ts
index b532b6372..225873045 100644
--- a/apps/vault/src/tenant/tenant.module.ts
+++ b/apps/vault/src/tenant/tenant.module.ts
@@ -1,6 +1,7 @@
import { HttpModule } from '@nestjs/axios'
import { Module, OnApplicationBootstrap, ValidationPipe, forwardRef } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'
+import { ZodValidationPipe } from 'nestjs-zod'
import { AdminApiKeyGuard } from '../shared/guard/admin-api-key.guard'
import { KeyValueModule } from '../shared/module/key-value/key-value.module'
import { VaultModule } from '../vault/vault.module'
@@ -19,8 +20,13 @@ import { TenantRepository } from './persistence/repository/tenant.repository'
TenantRepository,
TenantService,
{
+ // DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
+ },
+ {
+ provide: APP_PIPE,
+ useClass: ZodValidationPipe
}
],
exports: [TenantService, TenantRepository]
diff --git a/package-lock.json b/package-lock.json
index f53cc3cc0..261c7def0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,6 +43,7 @@
"lodash": "^4.17.21",
"lowdb": "^7.0.1",
"nest-commander": "^3.12.5",
+ "nestjs-zod": "^3.0.0",
"next": "14.0.4",
"prism-react-renderer": "^2.3.1",
"react": "18.2.0",
@@ -17882,6 +17883,14 @@
"deep-equal": "^2.0.5"
}
},
+ "node_modules/arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/array-buffer-byte-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
@@ -24299,6 +24308,25 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==",
+ "dependencies": {
+ "for-in": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -26202,6 +26230,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -28055,6 +28088,14 @@
"shell-quote": "^1.8.1"
}
},
+ "node_modules/lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/less": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@@ -29541,6 +29582,89 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"peer": true
},
+ "node_modules/merge-deep": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz",
+ "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==",
+ "dependencies": {
+ "arr-union": "^3.1.0",
+ "clone-deep": "^0.2.4",
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/clone-deep": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
+ "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==",
+ "dependencies": {
+ "for-own": "^0.1.3",
+ "is-plain-object": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "lazy-cache": "^1.0.3",
+ "shallow-clone": "^0.1.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/shallow-clone": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
+ "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==",
+ "dependencies": {
+ "is-extendable": "^0.1.1",
+ "kind-of": "^2.0.1",
+ "lazy-cache": "^0.2.3",
+ "mixin-object": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/shallow-clone/node_modules/kind-of": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
+ "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==",
+ "dependencies": {
+ "is-buffer": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/merge-deep/node_modules/shallow-clone/node_modules/lazy-cache": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
+ "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -31945,6 +32069,26 @@
}
}
},
+ "node_modules/mixin-object": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
+ "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==",
+ "dependencies": {
+ "for-in": "^0.1.3",
+ "is-extendable": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mixin-object/node_modules/for-in": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
+ "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -32237,6 +32381,34 @@
}
}
},
+ "node_modules/nestjs-zod": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/nestjs-zod/-/nestjs-zod-3.0.0.tgz",
+ "integrity": "sha512-vL9CHShCVj6TmjCVPOd4my46D8d7FdoB4nQvvh+lmVTuzvnwuD+slSxjT4EDdPDWDFtjhfpvQnnkr55/80KHEQ==",
+ "dependencies": {
+ "merge-deep": "^3.0.3"
+ },
+ "peerDependencies": {
+ "@nestjs/common": ">= 8.0.0",
+ "@nestjs/core": ">= 8.0.0",
+ "@nestjs/swagger": ">= 5.0.0",
+ "zod": ">= 3.14.3"
+ },
+ "peerDependenciesMeta": {
+ "@nestjs/common": {
+ "optional": true
+ },
+ "@nestjs/core": {
+ "optional": true
+ },
+ "@nestjs/swagger": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
"node_modules/next": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz",
diff --git a/package.json b/package.json
index bc98f450e..56489f987 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,7 @@
"lodash": "^4.17.21",
"lowdb": "^7.0.1",
"nest-commander": "^3.12.5",
+ "nestjs-zod": "^3.0.0",
"next": "14.0.4",
"prism-react-renderer": "^2.3.1",
"react": "18.2.0",
diff --git a/packages/nestjs-shared/README.md b/packages/nestjs-shared/README.md
index 4c0f7aef0..f66678199 100644
--- a/packages/nestjs-shared/README.md
+++ b/packages/nestjs-shared/README.md
@@ -1,6 +1,7 @@
# NestJS Shared
-This library contains the shared nestjs utilities such as validators, DTOs and decorators.
+This library contains the shared NestJS utilities such as validators, DTOs and
+decorators.
## Testing
diff --git a/packages/nestjs-shared/src/index.ts b/packages/nestjs-shared/src/index.ts
index 33d9aa5d1..5e63f9cad 100644
--- a/packages/nestjs-shared/src/index.ts
+++ b/packages/nestjs-shared/src/index.ts
@@ -1,2 +1,3 @@
-export * from './lib/decorators'
+export * from './lib/decorator'
export * from './lib/dto'
+export * from './lib/util'
diff --git a/packages/nestjs-shared/src/lib/decorators/index.ts b/packages/nestjs-shared/src/lib/decorator/index.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/index.ts
rename to packages/nestjs-shared/src/lib/decorator/index.ts
diff --git a/packages/nestjs-shared/src/lib/decorators/is-account-id.decorator.ts b/packages/nestjs-shared/src/lib/decorator/is-account-id.decorator.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/is-account-id.decorator.ts
rename to packages/nestjs-shared/src/lib/decorator/is-account-id.decorator.ts
diff --git a/packages/nestjs-shared/src/lib/decorators/is-asset-id.decorator.ts b/packages/nestjs-shared/src/lib/decorator/is-asset-id.decorator.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/is-asset-id.decorator.ts
rename to packages/nestjs-shared/src/lib/decorator/is-asset-id.decorator.ts
diff --git a/packages/nestjs-shared/src/lib/decorators/is-hex-string.decorator.ts b/packages/nestjs-shared/src/lib/decorator/is-hex-string.decorator.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/is-hex-string.decorator.ts
rename to packages/nestjs-shared/src/lib/decorator/is-hex-string.decorator.ts
diff --git a/packages/nestjs-shared/src/lib/decorators/is-not-empty-array-enum.decorator.ts b/packages/nestjs-shared/src/lib/decorator/is-not-empty-array-enum.decorator.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/is-not-empty-array-enum.decorator.ts
rename to packages/nestjs-shared/src/lib/decorator/is-not-empty-array-enum.decorator.ts
diff --git a/packages/nestjs-shared/src/lib/decorators/is-not-empty-array-string.decorator.ts b/packages/nestjs-shared/src/lib/decorator/is-not-empty-array-string.decorator.ts
similarity index 100%
rename from packages/nestjs-shared/src/lib/decorators/is-not-empty-array-string.decorator.ts
rename to packages/nestjs-shared/src/lib/decorator/is-not-empty-array-string.decorator.ts
diff --git a/packages/nestjs-shared/src/lib/dto/sign-transaction-request-data.dto.ts b/packages/nestjs-shared/src/lib/dto/sign-transaction-request-data.dto.ts
index 430a3bad1..300b9a1e1 100644
--- a/packages/nestjs-shared/src/lib/dto/sign-transaction-request-data.dto.ts
+++ b/packages/nestjs-shared/src/lib/dto/sign-transaction-request-data.dto.ts
@@ -2,7 +2,7 @@ import { Action, Address, Hex } from '@narval/policy-engine-shared'
import { ApiProperty } from '@nestjs/swagger'
import { Transform, Type } from 'class-transformer'
import { IsDefined, IsEthereumAddress, IsIn, IsInt, IsOptional, IsString, Min, ValidateNested } from 'class-validator'
-import { IsHexString } from '../decorators/is-hex-string.decorator'
+import { IsHexString } from '../decorator/is-hex-string.decorator'
import { BaseActionDto } from './'
class AccessListDto {
diff --git a/packages/nestjs-shared/src/lib/util/index.ts b/packages/nestjs-shared/src/lib/util/index.ts
new file mode 100644
index 000000000..648a9a9e3
--- /dev/null
+++ b/packages/nestjs-shared/src/lib/util/index.ts
@@ -0,0 +1 @@
+export * from './with-swagger.util'
diff --git a/packages/nestjs-shared/src/lib/util/with-swagger.util.ts b/packages/nestjs-shared/src/lib/util/with-swagger.util.ts
new file mode 100644
index 000000000..2973f7e5a
--- /dev/null
+++ b/packages/nestjs-shared/src/lib/util/with-swagger.util.ts
@@ -0,0 +1,29 @@
+import { INestApplication } from '@nestjs/common'
+import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
+import { patchNestJsSwagger } from 'nestjs-zod'
+
+/**
+ * Adds Swagger documentation to the application.
+ *
+ * @param app - The INestApplication instance.
+ * @returns The modified INestApplication instance.
+ */
+export const withSwagger =
+ (params: { title: string; description: string; version: string }) =>
+ (app: INestApplication): INestApplication => {
+ // IMPORTANT: This modifies the Nest Swagger module to be compatible with
+ // DTOs created by Zod schemas. The patch MUST be done before the
+ // configuration process.
+ patchNestJsSwagger()
+
+ const document = SwaggerModule.createDocument(
+ app,
+ new DocumentBuilder().setTitle(params.title).setDescription(params.description).setVersion(params.version).build()
+ )
+
+ SwaggerModule.setup('docs', app, document, {
+ customSiteTitle: `${params.title} API`
+ })
+
+ return app
+ }