@softwareproduction/nuxt-dryv
v0.1.0
Published
Nuxt module for integrating the Dryv validation library (@softwareproduction/dryvue)
Downloads
139
Readme
nuxt-dryv integrates the DryvJS validation engine into Nuxt 3 via Dryvue. It provides zero-config plugin registration, Nuxt-native server communication with $fetch, and an extensible result handler pipeline — all with full SSR support.
Like the other Dryv packages, nuxt-dryv works as a standalone validation solution or as the frontend runtime for full-stack rules generated by Dryv (.NET).
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- Usage
- Custom Result Handlers
- Built-in Warning Deduplication
- How It Works
- Server-Side Validation Flow
- TypeScript Support
- Integration with Existing Projects
- Development
- License
Features
- Zero-config Vue plugin registration — Automatically installs the
@softwareproduction/dryvueplugin with a Vuereactive()wrapper - Nuxt-native server communication — Uses Nuxt's built-in
$fetchfor validation API calls (supports SSR, proxying, and per-environment URL configuration) - Import alias for generated rule sets — Optional
#dryvalias that maps to your project's generated validation rule sets - Warning deduplication — Built-in handler that suppresses repeated identical warnings on subsequent validations (opt-out via config)
- Extensible result handler registry — Register and unregister custom handlers to intercept, transform, or enrich validation results
- Auto-imported composables —
addResultHandlerandremoveResultHandlerare available without explicit imports - Full SSR support — Works on both server and client;
serverBaseUrltargets your backend in SSR, client requests stay relative by default
Installation
# npm
npm install @softwareproduction/nuxt-dryv @softwareproduction/dryvue
# yarn
yarn add @softwareproduction/nuxt-dryv @softwareproduction/dryvue
# pnpm
pnpm add @softwareproduction/nuxt-dryv @softwareproduction/dryvuePeer dependency:
@softwareproduction/dryvue >= 2.0.0must be installed in your project.
Quick Start
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@softwareproduction/nuxt-dryv'],
dryv: {
serverBaseUrl: 'http://localhost:5000/api/validation',
},
})That's it — the @softwareproduction/dryvue plugin is now registered globally and you can start using useDryv in any component.
Configuration
All options are specified under the dryv key in nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@softwareproduction/nuxt-dryv'],
dryv: {
// Base URL for server-side (SSR) validation calls.
// Points to your backend service directly.
serverBaseUrl: 'http://internal-backend:5000/api/validation',
// Base URL for client-side validation calls.
// When omitted, requests use relative paths (current application URL).
clientBaseUrl: '/api/validation',
// Path (relative to your project root) to the generated validation rule sets.
// When set, the module registers a `#dryv` import alias pointing to this path.
validationPath: 'types/generated/validation/index',
// Whether to register the built-in warning deduplication handler.
// Default: true
handleWarnings: true,
},
})Options Reference
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| serverBaseUrl | string \| undefined | undefined | URL prefix for server-side (SSR) validation calls. Typically an internal backend URL. |
| clientBaseUrl | string \| undefined | undefined | URL prefix for client-side validation calls. When omitted, requests use relative paths. |
| validationPath | string \| undefined | undefined | Relative path to generated validation rule sets. Registers the #dryv import alias when set. |
| handleWarnings | boolean | true | Enables the built-in warning deduplication handler. Set to false if you handle warnings yourself. |
All options are exposed at runtime via useRuntimeConfig().public.dryv.
Usage
Basic Form Validation
<script setup lang="ts">
import { useDryv } from '@softwareproduction/dryvue'
import { ContactFormValidationSet } from '#dryv'
interface ContactForm {
name: string
email: string
message: string
}
const form = reactive<ContactForm>({
name: '',
email: '',
message: '',
})
const { validatable, model, validate } = useDryv<ContactForm>(
form,
ContactFormValidationSet,
)
async function submit() {
const result = await validate()
if (result.hasErrors) return
// Form is valid — proceed with submission
}
</script>
<template>
<form @submit.prevent="submit">
<div>
<label>Name</label>
<input v-model="model.name" />
<span v-if="validatable.name.text" class="error">
{{ validatable.name.text }}
</span>
</div>
<div>
<label>Email</label>
<input v-model="model.email" type="email" />
<span v-if="validatable.email.text" class="error">
{{ validatable.email.text }}
</span>
</div>
<div>
<label>Message</label>
<textarea v-model="model.message" />
<span v-if="validatable.message.text" class="error">
{{ validatable.message.text }}
</span>
</div>
<button type="submit">Send</button>
</form>
</template>Composable Pattern
Extract validation logic into a reusable composable:
// composables/useContactForm.ts
import { useDryv } from '@softwareproduction/dryvue'
import { ContactFormValidationSet } from '#dryv'
interface ContactForm {
name: string
email: string
message: string
}
export function useContactForm() {
const form = reactive<ContactForm>({
name: '',
email: '',
message: '',
})
const { validatable, model, validate } = useDryv<ContactForm>(
form,
ContactFormValidationSet,
)
async function submit() {
const result = await validate()
if (result.hasErrors) return false
// Call your API here
return true
}
return { model, validatable, submit }
}Without the #dryv Alias
If you don't use generated rule sets or prefer explicit imports, simply omit validationPath and pass rule sets directly:
import { useDryv } from '@softwareproduction/dryvue'
import { myRuleSet } from '~/validation/myRuleSet'
const { validatable, model, validate } = useDryv(form, myRuleSet)Custom Result Handlers
The module provides an extensible pipeline for intercepting validation results. Handlers are called in registration order; the first handler that returns a non-undefined value wins.
Registering a Handler
// In a plugin, composable, or component setup
const key = addResultHandler(async (session, model, path, rule, result) => {
// Example: replace a backend error code with a user-friendly message
if (rule.name === 'zipCodeLookup' && result.results?.[0]?.text === 'ZIP_NOT_FOUND') {
return {
...result,
results: [{ ...result.results[0], text: 'We could not find this ZIP code.' }],
}
}
// Return undefined to pass through to the next handler
return undefined
})Removing a Handler
removeResultHandler(key)Handler Signature
type ResultHandler = <TModel extends object>(
session: DryvValidationSession<TModel>,
model: TModel,
path: string,
rule: DryvValidationRule<TModel>,
result: DryvValidationResult,
) => Promise<DryvValidationResult | undefined>session— The current validation session (contains the rule set name and state)model— The reactive model being validatedpath— The field path (e.g."email")rule— The validation rule that produced this resultresult— The raw validation result from the rule
Return a DryvValidationResult to override, or undefined to let the next handler (or default behaviour) process it.
Built-in Warning Deduplication
When handleWarnings is enabled (default), the module tracks warning hashes per field per rule set. On subsequent validations:
- New warning — displayed normally and its hash is stored
- Same warning repeated — suppressed (treated as success) so the user isn't blocked twice
- Warning changed — the new warning is displayed and the stored hash is updated
This is useful for "soft" validations where you want to warn the user once but allow them to proceed if they confirm.
Disable this behaviour if you implement your own warning strategy:
dryv: {
handleWarnings: false,
}How It Works
Module registration (
src/module.ts) — Called at build time via@nuxt/kit. Registers the runtime plugin, sets up auto-imports, and optionally configures the#dryvalias.Runtime plugin (
src/runtime/plugins/dryv.ts) — Runs at app startup (both SSR and client). Configures@softwareproduction/dryvuewith:callServer— Uses$fetchwith the configuredserverBaseUrl/clientBaseUrlreactiveWrapper— Wraps internal objects with Vue'sreactive()handleResult— Delegates to the result handler registry
Result handler registry (
src/runtime/composables/useResultHandlers.ts) — A per-app-instance registry stored on the Nuxt app context. Handlers are invoked sequentially until one returns a result.
Server-Side Validation Flow
Browser Nuxt Server Backend
│ │ │
│ validate() called │ │
│──── $fetch('/endpoint') ────────│ │
│ │── $fetch(serverBaseUrl+'/ep')──│
│ │ │
│ │◄──── validation result ───────│
│◄──── validation result ─────────│ │
│ │ │The URL prefix used for validation calls is resolved per environment:
- Server (SSR): uses
serverBaseUrlto reach your backend directly (e.g.http://backend:5000/api/validation) - Client: uses
clientBaseUrlif set, otherwise requests use relative paths
In most setups, only serverBaseUrl is needed — client-side calls stay relative and reach the backend through Nuxt's proxy or the same origin.
TypeScript Support
The module ships with full type declarations. The ModuleOptions interface is exported for programmatic use:
import type { ModuleOptions } from '@softwareproduction/nuxt-dryv'If you use the #dryv alias, ensure your generated rule set files export typed DryvValidationRuleSet objects for full type inference in useDryv<T>().
Integration with Existing Projects
Migrating from a local module
If you previously used a local modules/dryv directory:
- Install the package:
npm install @softwareproduction/nuxt-dryv @softwareproduction/dryvue - Replace
'./modules/dryv'with'@softwareproduction/nuxt-dryv'in yourmodulesarray - Move your
baseUrlto thedryvconfig key - Update the
validationPathif your alias target differs from the default - Remove the local
modules/dryvdirectory
Configuring setup, parseDate, format, and loadParameters
The module sets up callServer, reactiveWrapper, and handleResult automatically. For additional DryvOptions (e.g., setup, format, parseDate, loadParameters), use the dryv:options hook in a plugin:
// plugins/dryv-extend.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('dryv:options', (options) => {
// Per-composable setup (called each time useDryv() is invoked)
options.setup = () => ({
format: useMyFormatter(),
loadParameters: useMyParameterLoader(),
})
// Custom date parsing
options.parseDate = (date, locale, format) => {
// your parsing logic
return new Date(date).valueOf()
}
// Custom formatting
options.format = (data, type, pattern) => {
// your formatting logic
return String(data)
}
// Custom parameter loading
options.loadParameters = async (validationSetName) => {
return await $fetch(`/api/validation/parameters/${validationSetName}`)
}
})
})The dryv:options hook fires after the module has configured the base options but before the plugin is installed, so you can override or extend any property on the DryvOptions object.
Development
# Install dependencies
npm install
# Generate type stubs and prepare the dev environment
npm run dev:prepare
# Start the playground dev server
npm run dev
# Build the module for publishing
npm run build
# Build the playground for production
npm run dev:buildProject Structure
packages/nuxt-dryv/
├── src/
│ ├── module.ts # Nuxt module definition
│ └── runtime/
│ ├── composables/
│ │ └── useResultHandlers.ts # Result handler registry (auto-imported)
│ └── plugins/
│ └── dryv.ts # Runtime plugin
├── playground/
│ ├── app.vue # Playground app
│ ├── nuxt.config.ts # Playground config
│ └── package.json
├── package.json
├── tsconfig.json
└── README.mdContributing
- Fork this repository
- Create a feature branch
- Make your changes
- Run
npm run dev:prepareandnpm run devto test in the playground - Submit a pull request
License
MIT
