npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

nuxt-precognition

v0.1.2

Published

Nuxt module implementing Precognition protocol (inspired by official Laravel Precognition) for backend precognitive validation.

Readme

Nuxt Precognition

npm version npm downloads License Nuxt

Nuxt Precognition is a validation module for Nuxt that implements the Precognition protocol in a backend-agnostic way. It supports any backend or validation library, and is not tied to Laravel.

Table of Contents

Requirements

  • Nuxt >= 3.x
  • Node.js >= 18

Why Nuxt Precognition?

  • Backend agnostic: Works with any backend that supports the Precognition protocol.
  • Validation library agnostic: Use Zod, Yup, or any other validation library.
  • Client & server side validation: Seamless validation on both ends.
  • Optimal TypeScript support: Typesafe forms and error handling.
  • Highly customizable: Plug in your own error parsers and status handlers.

Quick Example

interface User {
  email: string
  password: string
}

const form = useForm(
  (): User => ({ email: '', password: '' }),
  (body, headers) => $fetch('/api/login', { method: 'POST', headers, body })
)

Features

  • Laravel compliant
  • Validation library agnostic
  • Client and server side validation
  • TypeScript support
  • Customizable error parsing and status handling

Installation

Install the module in your Nuxt app:

npx nuxi module add nuxt-precognition

How It Works

The core concept is error parsers: functions that extract validation errors from thrown errors.

type ValidationErrors = Record<string, string | string[]>

interface ValidationErrorsData {
  message: string
  errors: ValidationErrors
}

type ValidationErrorParser = (error: Error) => ValidationErrorsData | undefined | null

Define Zod Error Parser

// app/utils/precognition.ts or shared/utils/precognition.ts
import { ZodError } from 'zod'

export const zodPrecognitionErrorParser: ValidationErrorParser = (error) => {
  if (error instanceof ZodError) {
    const errors: Record<string, string[]> = {}
    for (const issue of error.issues) {
      const key = issue.path.join('.')
      if (key in errors) {
        errors[key].push(issue.message)
        continue
      }
      errors[key] = [issue.message]
    }
    return { errors, message: error.message }
  }
  return null
}

Note:
For Server side validation, place this file in shared/utils folder.

Client Side Validation

Add the parser client side globally.

// app/plugins/precognition.ts
export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.errorParsers.push(zodErrorParser)

  // ..
})

Use the composable in setup method.

const UserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

const form = useForm(
  (): z.infer<typeof UserSchema> => ({
    email: '',
    password: '',
  }),
  (body, headers) => $fetch('/api/login', {
    method: 'POST',
    headers,
    body,
  }),
  {
    clientValidation(data) {
      UserSchema.parse(data)
    },
  },
)

function login() {
  form.submit()
}

function reset() {
  form.reset()
  document.getElementById('email')?.focus()
}
<form
  @submit.prevent="login"
  @reset.prevent="reset"
>
  <div>
    <label for="email">Email address</label>
    <input
      id="email"
      v-model="form.email"
      name="email"
      type="email"
      @change="form.validate('email')"
    >
    <span v-if="form.valid('email')">OK!!</span>
    <span v-if="form.invalid('email')">{{ form.errors.email }}</span>
  </div>

  <div>
    <label for="password">Password</label>
    <input
      id="password"
      v-model="form.password"
      name="password"
      type="password"
      autocomplete="current-password"
      required
      @change="form.validate('password')"
    >
    <span v-if="form.valid('password')">OK!!</span>
    <span v-if="form.invalid('password')">{{ form.errors.password }}</span>
  </div>

  <div>
    <button type="submit">Sign in</button>
    <button type="reset">Reset</button>
  </div>
</form>

Server Side Validation

  1. update the default configuration.
// nuxt.config.ts

export default defineNuxtConfig({
  modules: [
    'nuxt-precognition'
  ],
  precognition: {
    backendValidation: true, 
    enableNuxtClientErrorParser: true,
  },
})
  1. Create a Nitro plugin to parse server errors:
// server/plugins/precognition.ts
import { ZodError } from 'zod'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.$precognition.errorParsers = [
      zodErrorParser
    ]
  })
})
  1. Use definePrecognitiveEventHandler in the object way and add validation in the onRequest hook.
// server/api/login.post.ts
import { z } from 'zod'
import { definePrecognitiveEventHandler, readBody } from '#imports'

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string()
}).refine((_data) => {
  // Check for email and password match
  // ...
  return true
},
{ message: 'invalid credentials', path: ['email'] },
)

export default definePrecognitiveEventHandler({
  async onRequest(event) {
    const body = await readBody(event)
    loginSchema.parse(body)
  },
  handler: () => {
    return {
      status: 200,
      body: {
        message: 'Success',
      },
    }
  },
})

Precognition Protocol

If you need to define your own backend logic outside Nitro, follow these requirements.

  • Precognitive Requests must have:
    1. Precognitive Header { 'Precognitive': 'true' }
  • To validate specific variables, each keys must be specified inside the ValidateOnly Header, comma separated and leveraging dot notation { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
  • To validate the full Form the ValidateOnly Header should be omitted or define as an empty string.
  • Successful validation response must have:
    1. Precognitive Header { 'Precognitive': 'true' }
    2. Precognitive Successful Header { 'Precognition-Success': 'true' }
    3. Precognitive Successful status code: 204
  • Error validation response must have:
    1. Precognitive Header { 'Precognitive': 'true' }
    2. Precognition-Validate-Only header if needed { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
    3. Validation Error status code: 422
    4. Validation Errors and Message will be parsed as per your define logic, or using standard errorParsers:
      • NuxtErrorParsers: NuxtPrecognitiveErrorResponse: Response & { _data: { data: ValidationErrorsData }}
      • LaravelErrorParsers: LaravelPrecognitiveErrorResponse: Response & { _data: ValidationErrorsData }

Configuration

Add to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['nuxt-precognition'],
  precognition: {
    backendValidation: true,
    enableNuxtClientErrorParser: true,
    // ...other options
  }
})

Options

| name | type | default | description | |---|---|---|---| |validationTimeout|number|1500|Debounce time, in milliseconds, between two precognitive validation requests.| |backendValidation|boolean|false|Flag to enable the precognitive validation.| |validateFiles|boolean|false|Flag to enable files validation on precognitive requests.| |enableNuxtClientErrorParser|boolean|false|Flag to enable nuxtErrorParsers on client side (in form.validate and form.submit).| |enableLaravelClientErrorParser|boolean|false|Flag to enable laravelErrorParsers on client side (in form.validate and form.submit).| |enableLaravelServerErrorParser|boolean|false|Flag to enable laravelErrorParsers on server side (in definePrecognitiveEventHandler).|


Status Handlers

Like in official package, you can define globally, or @instance level, custom handlers for specific error codes:

// plugins/precognition.ts

export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.statusHandlers = {
    401: async (error, form) => {
      form.error = createError('Unauthorized')
      await navigateTo('/login')
    },
    403: async (error, form) => {
      form.error = createError('Forbidden')
    },
  }
})

Laravel Integration

If you want to use Laravel, you won't need nuxt nitro integration.

  1. Enable Backend Validation and Error Parsers:
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-precognition'],
  precognition: {
    backendValidation: true,
    enableLaravelClientErrorParser: true,
  }
})
  1. Plugin Example:

Add Sanctum token prefetch and ensure proper handling of all precognitive requests.

// plugins/laravel.ts
export default defineNuxtPlugin((app) => {
  const { $precognition } = useNuxtApp()
  const token = useCookie('XSRF-TOKEN')

  const api = $fetch.create({
    baseURL: 'http://localhost',
    credentials: 'include',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    onRequest: ({ options }) => {
      if (token.value) {
        const headers = new Headers(options.headers)
        headers.set('X-XSRF-TOKEN', token.value)
        options.headers = headers
      }
    },
    onResponse: (context) => {
      // ensure non false positive validations
      $precognition.assertSuccessfulPrecognitiveResponses(context)
    },
  })

  async function fetchSanctumToken() {
    try {
      await api('/sanctum/csrf-cookie')
      token.value = useCookie('XSRF-TOKEN').value
      if (!token.value) throw new Error('Failed to get CSRF token')
    } catch (e) {
      console.error(e)
    }
  }

  app.hook('app:mounted', fetchSanctumToken)

  return {
    provide: {
      api,
      sanctum: {
        fetchToken: fetchSanctumToken,
        token,
      },
    },
  }
})
  1. Laravel CORS Configuration:

Ensure Precognitive headers will be shared with Nuxt application.

// config/cors.php
return [
  'paths' => ['*'],
  'allowed_methods' => ['*'],
  'allowed_origins' => ['*'],
  'allowed_origins_patterns' => [env('FRONTEND_URL', 'http://localhost:3000')],
  'allowed_headers' => ['*'],
  'exposed_headers' => ['Precognition', 'Precognition-Success'],
  'max_age' => 0,
  'supports_credentials' => true,
];
  1. Enable Precognition Middleware:

Apply precognitive middleware where needed.

// routes/api.php
Route::middleware('precognitive')->group(function () {
    Route::apiResource('posts', \App\Http\Controllers\PostController::class);
});

That's it. Nuxt validation will be in sync with Laravel!!.


Contributing

# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with the playground
npm run dev

# Build the playground
npm run dev:build

# Run ESLint
npm run lint

# Run Vitest
npm run test
npm run test:watch

# Release new version
npm run release

License

MIT © [sot1986]