npm.io
1.0.0 • Published 1 month ago

@toad-contracts/valibot

Licence
MIT
Version
1.0.0
Deps
1
Size
10 kB
Vulns
0
Weekly
0

@toad-contracts/valibot

The valibot adapter for @toad-contracts/core.

The core contract library is written against the vendor-neutral Standard Schema interface, which valibot v1 implements. This package re-exports the entire core API and adds withObjectKeys, the valibot implementation of the single object-key introspection surface (StandardObjectKeysV1) that API contracts need for path-param schemas and message contracts need for field introspection, something the Standard Schema interface does not expose.

valibot is a peer dependency.

pnpm add @toad-contracts/valibot valibot

Usage

Import everything from @toad-contracts/valibot. The full core surface (defineApiContract, response factories, inference types, client types, and more) is re-exported unchanged. See the @toad-contracts/core README for the complete reference.

import {
  defineApiContract,
  mapApiContractToPath,
  describeApiContract,
  withObjectKeys,
} from "@toad-contracts/valibot";
import { object, string } from "valibot";

const getUser = defineApiContract({
  method: "get",
  requestPathParamsSchema: withObjectKeys(object({ userId: string() })),
  pathResolver: ({ userId }) => `/users/${userId}`,
  responsesByStatusCode: {
    200: object({ id: string(), name: string() }),
  },
});

mapApiContractToPath(getUser); // "/users/:userId"
describeApiContract(getUser); // "GET /users/:userId"

What this package adds

withObjectKeys(schema) is the only addition; everything else is a direct re-export from @toad-contracts/core.

API contracts need a requestPathParamsSchema to expose its object keys to build the route path, and message contracts need a schema's declared field names for routing/projection — both read through the same StandardObjectKeysV1 surface, which the Standard Schema interface does not provide. withObjectKeys attaches that capability to a valibot object schema by reading its .entries:

// effectively:
export const withObjectKeys = (schema) => {
  const keys = Object.keys(schema.entries);
  Object.assign(schema["~standard"], {
    objectKeys: { input: () => keys, output: () => keys },
  });
  return schema;
};

Wrap any path-param or message schema with it. Only plain object schemas (object, strictObject, looseObject, objectWithRest) expose .entries; a wrapped schema such as pipe(object(...), ...) or a non-object schema does not, so withObjectKeys throws an actionable TypeError rather than silently producing a schema with no object keys.

The path-mapping helpers mapApiContractToPath(contract) and describeApiContract(contract) are re-exported from core unchanged and already single-argument; they read the keys through whatever withObjectKeys attached. The same wrapper makes a schema satisfy @toad-contracts/messages' RoutableMessageSchema, so a routing container can enumerate a message's declared field names — no separate message-specific helper is needed:

import { withObjectKeys } from "@toad-contracts/valibot";
import { literal, object, string } from "valibot";

const schema = withObjectKeys(object({ type: literal("user.created"), id: string() }));
schema["~standard"].objectKeys.input(); // ["type", "id"]

Keywords