# Schemas and adapters

Canonical URL: https://catofjupit3r.github.io/enumwaii/docs/adapters/



## Standard Schema is the default [#standard-schema-is-the-default]

An enumwaii declaration is already a Standard Schema v1 schema. Pass the declaration itself to a compatible library; no wrapper or adapter is necessary.

<Callout type="info" title="Start with Standard Schema">
  Standard Schema integrations accept the enumwaii declaration directly. Add Zod
  or Valibot when the receiving API requires that library's schema type.
</Callout>

```ts twoslash
import type { StandardSchemaV1 } from "@standard-schema/spec";
import { em } from "enumwaii";

declare const form: {
  register<TOutput>(
    name: string,
    schema: StandardSchemaV1<unknown, TOutput>,
  ): void;
};
declare const router: {
  input<TOutput>(schema: StandardSchemaV1<unknown, TOutput>): void;
};
// ---cut---
const roles = em(["ADMIN", "USER"]);

form.register("role", roles);
router.input(roles);
```

Enumwaii uses the official `StandardSchemaV1` type from `@standard-schema/spec`, but does not re-export it. Import the type from the official package when authoring a generic Standard Schema integration:

```ts twoslash
import type { StandardSchemaV1 } from "@standard-schema/spec";
```

The spec package is a regular enumwaii dependency because its type appears in enumwaii's public declarations. It contributes no validation runtime of its own. Applications that name the type in their own source should also declare the spec package directly instead of relying on a transitive dependency.

`@standard-schema/utils` is not required by enumwaii. It is useful to authors of generic Standard Schema tooling, but adding it to the runtime package would not improve declaration validation or the consumer API.

## Library-specific adapters [#library-specific-adapters]

Choose the adapter required by the surrounding API. The package-manager choice is shared with every other install command in these docs.

<Tabs items="[&#x22;Zod&#x22;, &#x22;Valibot&#x22;]" groupId="enumwaii-schema-adapter">
  <Tab value="Zod">
    Install Zod for APIs that require a Zod schema:

    <CodeBlockTabs defaultValue="npm" groupId="enumwaii-package-manager">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="deno">
          deno
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```sh  
        npm install zod
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```sh
        pnpm add zod
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```sh
        yarn add zod
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```sh
        bun add zod
        ```
      </CodeBlockTab>

      <CodeBlockTab value="deno">
        ```sh
        deno add npm:zod
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```ts twoslash
    declare const payload: unknown;
    // ---cut---
    import { z } from "zod";
    import { em } from "enumwaii";
    import { emToZodSchema } from "enumwaii/zod";

    const roles = em(["ADMIN", "USER"]);
    const inputSchema = z.object({
      role: emToZodSchema(roles),
    });

    const input = inputSchema.parse(payload);
    // input.role is the branded enumwaii member union
    ```

    The adapter follows Zod's normal parse and error behavior while preserving the branded output type.
  </Tab>

  <Tab value="Valibot">
    Install Valibot for APIs that require its schema type:

    <CodeBlockTabs defaultValue="npm" groupId="enumwaii-package-manager">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="deno">
          deno
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```sh  
        npm install valibot
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```sh
        pnpm add valibot
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```sh
        yarn add valibot
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```sh
        bun add valibot
        ```
      </CodeBlockTab>

      <CodeBlockTab value="deno">
        ```sh
        deno add npm:valibot
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```ts twoslash
    declare const payload: unknown;
    // ---cut---
    import * as v from "valibot";
    import { em } from "enumwaii";
    import { emToValibotSchema } from "enumwaii/valibot";

    const roles = em(["ADMIN", "USER"]);
    const inputSchema = v.object({
      role: emToValibotSchema(roles),
    });

    const input = v.parse(inputSchema, payload);
    // input.role is the branded enumwaii member union
    ```

    The adapter follows Valibot's normal parsing and issue behavior while preserving the branded output type.
  </Tab>
</Tabs>

Both library adapters are separate package entry points and both validator packages are optional peers. Applications that use only Standard Schema do not load either integration.

## nuqs query parsers [#nuqs-query-parsers]

Install nuqs when URL query state should use enumwaii's validation and branded member type:

<CodeBlockTabs defaultValue="npm" groupId="enumwaii-package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="deno">
      deno
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```sh  
    npm install nuqs
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```sh
    pnpm add nuqs
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```sh
    yarn add nuqs
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```sh
    bun add nuqs
    ```
  </CodeBlockTab>

  <CodeBlockTab value="deno">
    ```sh
    deno add npm:nuqs
    ```
  </CodeBlockTab>
</CodeBlockTabs>

For a shared default, create the parser beside the declaration and export both:

```ts twoslash
import { em } from "enumwaii";
import { createEnumwaiiQueryParser } from "enumwaii/nuqs";

export const roles = em(["ADMIN", "USER"]);
export const ROLE = roles.enum;
export type Role = (typeof roles)["~type"];
export const roleQueryParser = createEnumwaiiQueryParser(roles, ROLE.USER);
```

When the default depends on component state or props, create it inside the component:

```tsx twoslash
// @jsx: react-jsx
import { useQueryState } from "nuqs";
import { em } from "enumwaii";
import { createEnumwaiiQueryParser } from "enumwaii/nuqs";

const roles = em(["ADMIN", "USER"]);
const ROLE = roles.enum;
type Role = (typeof roles)["~type"];

export function RoleFilter({ defaultRole }: { defaultRole: Role }) {
  const parser = createEnumwaiiQueryParser(roles, defaultRole);
  const [role, setRole] = useQueryState("role", parser);

  return <button onClick={() => setRole(ROLE.ADMIN)}>{role}</button>;
}
```

The parser returns `null` for invalid query strings as required by nuqs, applies the supplied owned member as its default, and serializes members as their raw string value. The `enumwaii/nuqs` entry point and `nuqs` peer are optional; applications that do not import it do not load nuqs.
