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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@softwareproduction/nuxt-introspection-forms

v0.1.0

Published

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

Downloads

139

Readme

@softwareproduction/nuxt-introspection-forms

Nuxt module that wraps @softwareproduction/introspection-forms for seamless integration with Nuxt 3 and 4 applications. It handles component registration, composable auto-imports, alias configuration, and global defaults injection — so you can use schema-driven forms with zero boilerplate.

Table of Contents

Installation

yarn add @softwareproduction/nuxt-introspection-forms @softwareproduction/introspection-forms
yarn add -D @graphql-codegen/cli graphql

Peer dependencies: vue >= 3.4, nuxt >= 3.9.

Module Setup

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@softwareproduction/nuxt-introspection-forms'],

  introspectionForms: {
    // Path to generated introspection types (relative to rootDir)
    generatedPath: 'types/generated/introspection',
  },
})

The module will:

  1. Register <IntrospectionForm> and <IntrospectionField> as global components.
  2. Auto-import useIntrospectionForm and useIntrospectionFormsEnumFilter composables.
  3. Create #introspection-types and #introspection-forms aliases for convenient imports.
  4. Register a client-side plugin that provides global defaults via inject.

Component Mapping Plugin

Create a Nuxt plugin to define which UI components render which field types. This is where you connect @softwareproduction/introspection-forms to your design system.

// app/plugins/configure-forms.ts
import {
  FormInput,
  FormCheckbox,
  FormRadio,
  FormSelect,
  FormTextarea,
  FormDateInput,
} from '#components'
import type { IntrospectionFormsDefaults, IntrospectionField } from '@softwareproduction/introspection-forms'
import { withProps, useIntrospectionFormsEnumFilter } from '@softwareproduction/introspection-forms'

export default defineNuxtPlugin(nuxtApp => {
  const { filterEnumValues } = useIntrospectionFormsEnumFilter()

  nuxtApp.provide('introspection-forms', {
    // Map by TypeScript scalar type
    byFieldType: {
      string: { component: FormInput },
      number: { component: FormInput, props: { type: 'number' } },
      boolean: { component: FormCheckbox },
      enum: (introspection: IntrospectionField) => ({
        component: introspection.enumValues.length > 5 ? FormSelect : FormRadio,
        props: withProps<typeof FormRadio>(() => ({
          options: (_, t) =>
            filterEnumValues(introspection).map(value => ({
              value,
              label: t(`forms.${introspection.originalType}.${value}`),
            })),
        })),
      }),
    },

    // Map by original GraphQL type name
    byOriginalType: {
      DateTime: { component: FormDateInput },
      Float: { component: FormInput, props: { type: 'number', step: '0.01' } },
    },

    // Map by field name (regex matching, first match wins)
    byFieldName: [
      { regexp: /^email/i, config: { component: FormInput, props: { type: 'email' } } },
      { regexp: /phone|telefon/i, config: { component: FormInput, props: { type: 'tel' } } },
      { regexp: /^(body|message|notes)/i, config: { component: FormTextarea } },
      { regexp: /files/i, config: { component: FormInput, props: { type: 'file', multiple: true } } },
      { regexp: /^(password|passwort)/i, config: { component: FormInput, props: { type: 'password' } } },
    ],

    // Global enum value filters
    enumFilters: {
      Salutation: values => values.filter(v => v !== 'None'),
    },
  } satisfies IntrospectionFormsDefaults)
})

Usage in Pages & Components

The module auto-imports everything you need. Import only the generated introspection type:

<script setup lang="ts">
import { TypeOfContactFormInput } from '#introspection-types'

const data = ref(TypeOfContactFormInput.create())

const form = useIntrospectionForm(TypeOfContactFormInput, {
  firstName: true,
  lastName: true,
  email: true,
  body: true,
})

async function submit() {
  // data.value now contains the form values
  await $fetch('/api/contact', { method: 'POST', body: data.value })
}
</script>

<template>
  <IntrospectionForm :form="form" :model="data">
    <button @click.prevent="submit">Send</button>
  </IntrospectionForm>
</template>

With Field Overrides

Override defaults for specific fields without losing the auto-resolved component:

<script setup lang="ts">
import { TypeOfRegistrationFormInput } from '#introspection-types'

const data = ref(TypeOfRegistrationFormInput.create({ useSameAddress: true }))

const form = useIntrospectionForm(TypeOfRegistrationFormInput, {
  firstName: { span: 1 },                           // Keep default component, adjust layout
  lastName: { span: 1 },
  email: true,                                      // Use defaults entirely
  phone: {
    visible: model => model.preferredContact === 'Phone',  // Conditional visibility
  },
  acceptTerms: { span: 2 },
})
</script>

With Nested Forms

<script setup lang="ts">
import { TypeOfOrderInput, TypeOfAddressInput } from '#introspection-types'

const data = ref(TypeOfOrderInput.create())

const addressForm = useIntrospectionForm(TypeOfAddressInput, {
  street: true,
  houseNumber: true,
  zipCode: true,
  city: true,
})

const form = useIntrospectionForm(TypeOfOrderInput, {
  productId: true,
  quantity: true,
  shippingAddress: { form: addressForm },
})
</script>

GraphQL Codegen Integration

Set up the codegen plugin to generate introspection metadata from your schema:

// codegen.config.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    './types/generated/introspection/_placeholder.ts': {
      plugins: ['@softwareproduction/introspection-forms/codegen'],
      config: {
        output: './types/generated/introspection',
        typesImport: '../graphql-types',
        introspectionTypeImport: '@softwareproduction/introspection-forms',
      },
    },
  },
}

export default config

Add scripts to your package.json:

{
  "scripts": {
    "gen:forms": "graphql-codegen --config codegen.config.ts",
    "gen:types": "graphql-codegen --config codegen-types.config.ts"
  }
}

Run both generators when your schema changes:

yarn gen:types && yarn gen:forms

Module Options

Configure via introspectionForms in nuxt.config.ts:

| Option | Type | Default | Description | |--------|------|---------|-------------| | generatedPath | string | 'types/generated/introspection' | Path to generated introspection files (relative to rootDir) | | defaults | IntrospectionFormsDefaults | undefined | Static component mapping defaults (alternative to the plugin approach for simple configs) |

Provided Aliases

The module registers two aliases for convenient imports:

| Alias | Resolves To | Usage | |-------|-------------|-------| | #introspection-types | <rootDir>/<generatedPath> | import { TypeOfXxx } from '#introspection-types' | | #introspection-forms | @softwareproduction/introspection-forms package | import { withProps } from '#introspection-forms' |

Auto-Imports

These are available globally without manual imports:

| Import | Source | Description | |--------|--------|-------------| | useIntrospectionForm | @softwareproduction/introspection-forms | Create a form runtime | | useIntrospectionFormsEnumFilter | @softwareproduction/introspection-forms | Access enum filters |

Components registered globally:

| Component | Description | |-----------|-------------| | <IntrospectionForm> | Top-level form renderer | | <IntrospectionField> | Individual field renderer |

Validation with Dryv

The module integrates with Dryv for client-side validation. Pass a DryvValidationRuleSet to useIntrospectionForm and use v-model:validate to receive the validate command from the component:

<script setup lang="ts">
import { TypeOfContactFormInput } from '#introspection-types'
import { ContactFormValidationSet } from '#validation'

const data = ref(TypeOfContactFormInput.create())
const validate = ref<(checkOnly?: boolean) => Promise<boolean>>()

const form = useIntrospectionForm(
  TypeOfContactFormInput,
  ContactFormValidationSet,
  {
    firstName: true,
    lastName: true,
    email: true,
  },
)

async function submit() {
  const success = await validate.value?.()
  if (!success) return
  await $fetch('/api/submit', { method: 'POST', body: data.value })
}
</script>

<template>
  <IntrospectionForm :form="form" :model="data" v-model:validate="validate">
    <button @click.prevent="submit">Submit</button>
  </IntrospectionForm>
</template>

The component internally creates a Dryv validation session when rules are provided and exposes the validate function via v-model:validate. Validation errors appear inline beneath each field automatically.

Advanced Configuration

Multiple Form Configs (Spread Pattern)

Break large forms into logical groups:

const personalFields = { firstName: true, lastName: true, dateOfBirth: true }
const contactFields = { email: true, phone: true, preferredContact: true }
const consentFields = { acceptTerms: { span: 2 }, acceptNewsletter: { span: 2 } }

const form = useIntrospectionForm(TypeOfRegistrationFormInput, {
  ...personalFields,
  ...contactFields,
  ...consentFields,
})

Form-Level Visibility and Disabled

const form = useIntrospectionForm({
  introspection: TypeOfPaymentInput,
  config: { cardNumber: true, cvv: true, expiryDate: true },
  visible: model => model.paymentMethod === 'creditCard',
  disabled: model => model.isProcessing,
})

Storage Persistence per Form

<IntrospectionForm
  :form="form"
  :model="data"
  storage="session"
  :interceptStorage="(stored) => ({ ...stored, acceptTerms: false })"
>
  ...
</IntrospectionForm>

License

MIT