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

@formspec/dsl

v0.1.0-alpha.8

Published

DSL functions for defining FormSpec forms

Readme

@formspec/dsl

Type-safe form definition using a fluent builder API. This is the recommended approach for defining forms programmatically, especially when you need runtime form construction or dynamic forms.

Installation

npm install @formspec/dsl @formspec/build
# Or use the umbrella package:
npm install formspec

Requirements

This package is ESM-only and requires:

// package.json
{
  "type": "module"
}
// tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

Quick Start

import { formspec, field, group, when, is, type InferFormSchema } from "@formspec/dsl";
import { buildFormSchemas } from "@formspec/build";

// Define a form with full type safety
const ContactForm = formspec(
  field.text("name", { label: "Name", required: true }),
  field.text("email", { label: "Email", required: true }),
  field.enum("subject", ["general", "support", "sales"] as const, {
    label: "Subject",
    required: true
  }),
  field.text("message", { label: "Message", required: true }),
  field.boolean("subscribe", { label: "Subscribe to newsletter" })
);

// Infer TypeScript types from the form definition
type ContactData = InferFormSchema<typeof ContactForm>;
// Result: { name: string; email: string; subject: "general" | "support" | "sales"; message: string; subscribe: boolean }

// Generate JSON Schema and UI Schema
const { jsonSchema, uiSchema } = buildFormSchemas(ContactForm);

Field Types

Text Field

field.text("fieldName", {
  label: "Display Label",
  description: "Help text",
  required: true,
  minLength: 1,
  maxLength: 100,
  pattern: "^[a-zA-Z]+$"  // Regex pattern
});

Number Field

field.number("age", {
  label: "Age",
  required: true,
  min: 0,
  max: 120
});

Boolean Field

field.boolean("acceptTerms", {
  label: "I accept the terms and conditions",
  required: true
});

Enum Field (Dropdown/Select)

Use as const to preserve literal types for type inference:

// Simple string options
field.enum("status", ["draft", "published", "archived"] as const, {
  label: "Status",
  required: true
});

// Options with separate IDs and labels
field.enum("country", [
  { id: "us", label: "United States" },
  { id: "ca", label: "Canada" },
  { id: "uk", label: "United Kingdom" }
] as const, {
  label: "Country"
});

Note: Use as const when passing enum options from a variable. For inline array literals, the const type parameter preserves literal types automatically.

Dynamic Enum Field

For dropdowns populated at runtime from an API:

field.dynamicEnum("customerId", "fetch_customers", {
  label: "Customer",
  required: true
});

The second argument is a source identifier used with @formspec/runtime resolvers.

Array Field

For repeatable field groups:

field.array("lineItems",
  field.text("description", { label: "Description", required: true }),
  field.number("quantity", { label: "Quantity", min: 1 }),
  field.number("price", { label: "Unit Price", min: 0 })
);

// With configuration
field.arrayWithConfig("contacts",
  { label: "Contact List", minItems: 1, maxItems: 5 },
  field.text("name", { label: "Name" }),
  field.text("phone", { label: "Phone" })
);

Object Field

For nested field groups:

field.object("address",
  field.text("street", { label: "Street", required: true }),
  field.text("city", { label: "City", required: true }),
  field.text("zipCode", { label: "ZIP Code", required: true })
);

Grouping

Use group() to visually organize fields:

const UserForm = formspec(
  group("Personal Information",
    field.text("firstName", { label: "First Name", required: true }),
    field.text("lastName", { label: "Last Name", required: true }),
    field.text("email", { label: "Email", required: true })
  ),
  group("Preferences",
    field.enum("theme", ["light", "dark", "system"] as const, { label: "Theme" }),
    field.boolean("notifications", { label: "Enable notifications" })
  )
);

Conditional Fields

Use when() and is() to show/hide fields based on other field values:

const OrderForm = formspec(
  field.enum("shippingMethod", ["standard", "express", "pickup"] as const, {
    label: "Shipping Method",
    required: true
  }),

  // Only show address fields when shipping method is not "pickup"
  when(is("shippingMethod", "standard"),
    field.text("address", { label: "Shipping Address", required: true }),
    field.text("city", { label: "City", required: true })
  ),
  when(is("shippingMethod", "express"),
    field.text("address", { label: "Shipping Address", required: true }),
    field.text("city", { label: "City", required: true }),
    field.text("phone", { label: "Phone for courier", required: true })
  )
);

Type Inference

The library provides powerful type inference utilities:

import { type InferFormSchema, type InferFieldValue } from "@formspec/dsl";

const MyForm = formspec(
  field.text("name"),
  field.number("age"),
  field.enum("role", ["admin", "user", "guest"] as const)
);

// Infer the complete form data type
type FormData = InferFormSchema<typeof MyForm>;
// { name: string; age: number; role: "admin" | "user" | "guest" }

// Access form elements at runtime
for (const element of MyForm.elements) {
  if (element._type === "field") {
    console.log(element.name, element._field);
  }
}

Validation

Validate form definitions at runtime:

import { formspec, field, validateForm, logValidationIssues } from "@formspec/dsl";

const form = formspec(
  field.text("email"),
  field.text("email")  // Duplicate field name!
);

const result = validateForm(form.elements);
if (!result.valid) {
  logValidationIssues(result, "MyForm");
  // Logs: [MyForm] ERROR at email: Duplicate field name "email"
}

// Or use formspecWithValidation for automatic checking
import { formspecWithValidation } from "@formspec/dsl";

const validatedForm = formspecWithValidation(
  { name: "MyForm", validate: "throw" },
  field.text("email"),
  field.text("email")  // Throws error!
);

Schema Generation

Use @formspec/build to generate JSON Schema and UI Schema:

import { buildFormSchemas, writeSchemas } from "@formspec/build";

// Get schema objects
const { jsonSchema, uiSchema } = buildFormSchemas(MyForm);

// Or write to files
writeSchemas(MyForm, {
  outDir: "./generated",
  name: "MyForm"
});
// Creates:
//   ./generated/MyForm-schema.json
//   ./generated/MyForm-uischema.json

When to Use This Package

Use @formspec/dsl when:

  • Forms are defined programmatically - Building forms from configuration or code
  • Runtime form construction - Creating forms dynamically based on user input or API data
  • Full type inference needed - Deriving TypeScript types from form definitions
  • No build step preferred - Works directly at runtime without CLI codegen

Consider @formspec/decorators when:

  • Class-based forms preferred - Using TypeScript classes with property decorators
  • Type inference from existing types - Leveraging existing TypeScript class types
  • Static analysis available - Using the CLI for build-time schema generation

API Reference

Functions

| Function | Description | |----------|-------------| | formspec(...elements) | Create a form specification | | formspecWithValidation(options, ...elements) | Create a form with validation | | group(label, ...elements) | Create a visual field group | | when(predicate, ...elements) | Create conditional fields | | is(fieldName, value) | Create an equality predicate | | validateForm(elements) | Validate form elements | | logValidationIssues(result) | Log validation issues to console |

Field Builders

| Builder | Description | |---------|-------------| | field.text(name, config?) | Text input field | | field.number(name, config?) | Numeric input field | | field.boolean(name, config?) | Checkbox/toggle field | | field.enum(name, options, config?) | Dropdown/select field | | field.dynamicEnum(name, source, config?) | API-populated dropdown | | field.dynamicSchema(name, source, config?) | Dynamic nested schema | | field.array(name, ...items) | Repeatable field array | | field.arrayWithConfig(name, config, ...items) | Array with configuration | | field.object(name, ...properties) | Nested object field | | field.objectWithConfig(name, config, ...properties) | Object with configuration |

Type Utilities

| Type | Description | |------|-------------| | InferFormSchema<F> | Infer data type from FormSpec | | InferSchema<Elements> | Infer data type from element array | | InferFieldValue<F> | Infer value type from a single field | | ExtractFields<E> | Extract all fields from an element |

License

UNLICENSED