@softwareproduction/nuxt-dryv

Nuxt module for integrating the Dryv validation library (@softwareproduction/dryvue)
380
Install
npm install @softwareproduction/nuxt-dryv
Latest Version:0.2.0
Vue:3
License:MIT
Last Updated:Oct 23, 2026
Links: GitHub  ·  npm
Maintainer: bananapeelsurfer


DRY Validation for Distributed Apps

npm dryvue License

Nuxt 3 module for the DryvJS validation framework.


nuxt-dryv integrates the DryvJS validation engine into Nuxt 3 via Dryvue. It handles 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

  • Automatic Vue plugin registration — Installs the @softwareproduction/dryvue plugin with a Vue reactive() wrapper
  • Nuxt-native server communication — Uses Nuxt's built-in $fetch for validation API calls (supports SSR, proxying, and per-environment URL configuration)
  • Import alias for generated rule sets — Optional #dryv alias 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 — useDryv, addResultHandler, and removeResultHandler are available without explicit imports
  • Full SSR support — Works on both server and client; serverBaseUrl targets 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/dryvue

Peer dependency: @softwareproduction/dryvue >= 2.0.0 must 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 validated
  • path — The field path (e.g. "email")
  • rule — The validation rule that produced this result
  • result — 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:

  1. New warning — displayed normally and its hash is stored
  2. Same warning repeated — suppressed (treated as success) so the user isn't blocked twice
  3. 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

  1. Module registration (src/module.ts) — Called at build time via @nuxt/kit. Registers the runtime plugin, sets up auto-imports, and optionally configures the #dryv alias.

  2. Runtime plugin (src/runtime/plugins/dryv.ts) — Runs at app startup (both SSR and client). Configures @softwareproduction/dryvue with:

    • callServer — Uses $fetch with the configured serverBaseUrl / clientBaseUrl
    • reactiveWrapper — Wraps internal objects with Vue's reactive()
    • handleResult — Delegates to the result handler registry
  3. 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 serverBaseUrl to reach your backend directly (e.g. http://backend:5000/api/validation)
  • Client: uses clientBaseUrl if 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:

  1. Install the package: npm install @softwareproduction/nuxt-dryv @softwareproduction/dryvue
  2. Replace './modules/dryv' with '@softwareproduction/nuxt-dryv' in your modules array
  3. Move your baseUrl to the dryv config key
  4. Update the validationPath if your alias target differs from the default
  5. Remove the local modules/dryv directory

Configuring loadParameters and Other Hooks

The module sets up callServer (using $fetch), reactiveWrapper, and handleResult automatically. The only hook you must implement is loadParameters — and only if your rule sets use parameters.

Use the dryv:options hook in a plugin to provide it via options.setup — a factory function that returns a Partial<DryvOptions>. Because setup is called lazily at validation time (within a component's lifecycle), Nuxt composables like useState or custom GraphQL clients are safely available:

// plugins/dryv-extend.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('dryv:options', (options) => {
    options.setup = () => ({
      loadParameters: useLoadParameters(),
      // Optional overrides:
      // callServer: async (url, method, data?) => { ... },
      // parseDate: (date, locale, format) => { ... },
      // format: useFormat(),
    })
  })
})

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. Any hook returned from setup takes precedence over the module's defaults.


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:build

Project 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.md

Contributing

  1. Fork this repository
  2. Create a feature branch
  3. Make your changes
  4. Run npm run dev:prepare and npm run dev to test in the playground
  5. Submit a pull request

License

MIT

Related Packages

@softwareproduction/nuxt-introspection-forms

Nuxt module for introspection-forms — schema-driven form generation from GraphQL

194 0

Version History

Version Released Vue License
0.2.0 ^3.0 MIT
0.1.0 ^3.0 MIT