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

@uraniadev/sveltekit-valibot-openapi

v0.1.30

Published

Generate an OpenAPI 3.1 specification from your SvelteKit routes and Valibot schemas.

Downloads

143

Readme

Valibot → OpenAPI 3.1

(SvelteKit-first, framework-agnostic)

Generate an OpenAPI 3.1 specification directly from Valibot schemas and minimal endpoint metadata. No runtime hooks. No hidden validation. No route magic. The output is a clean, deterministic OpenAPI document.

  • 🧩 First-class SvelteKit +server integration
  • 🔌 Framework-agnostic generator (createOpenApiSpec)
  • 🔍 Query schemas → OpenAPI parameters (strict object/union-of-objects)
  • 🧾 Multi-media request/response body support
  • 🔐 Security schemes & per-operation overrides
  • 🛡 Hardened sanitization (prototype-free, bounded, schema-safe)
  • 🧰 Fully async-schema compatible (async → sync structural normalization)

This library is pure documentation generation, not a runtime validator or router. Still alpha.


🚀 Installation

pnpm add @uraniadev/sveltekit-valibot-openapi valibot @valibot/to-json-schema

📘 Defining endpoints

Endpoints are declared by exporting a _openapi object from your SvelteKit route module.

Each key is an HTTP method, each value is created with defineEndpoint(). The object is later sanitized, validated, and deep-frozen by the generator to guarantee structural safety.

// src/routes/api/todos/+server.ts
import * as v from "valibot";
import { defineEndpoint } from "@uraniadev/sveltekit-valibot-openapi";

const Todo = v.object({
  id: v.string(),
  title: v.string(),
});

const TodoList = v.array(Todo);
const TodoCreate = v.object({ title: v.string() });

export const _openapi = {
  GET: defineEndpoint({
    method: "GET",
    path: "/api/todos",
    summary: "List todos",
    query: v.object({
      search: v.optional(v.string()),
    }),
    responses: {
      200: {
        description: "List of todos",
        schema: TodoList,
      },
    },
  }),

  POST: defineEndpoint({
    method: "POST",
    path: "/api/todos",
    summary: "Create a todo",
    body: TodoCreate,
    responses: {
      201: {
        description: "Created todo",
        schema: Todo,
      },
    },
  }),
} as const;

✔ What defineEndpoint() supports

  • query: object-like only (object / optional / nullable / pipe / union-of-objects)

  • queryParams: extra documentation aligned with query

  • body:

    • a schema → emitted as application/json
    • or a { content: { "media/type": schema } } map
  • responses:

    • { schema }
    • { content: { "media/type": schema } }
    • both (JSON fallback auto-added)
  • tags, summary, description, deprecated

  • per-endpoint security

📡 Generating and exposing the OpenAPI spec

createOpenApiSpec produces the OpenAPI spec object. You expose it however you want.

SvelteKit example

// src/routes/openapi/+server.ts
import { json } from "@sveltejs/kit";
import { createOpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";

const modules = import.meta.glob("../api/**/+server.{ts,js}");

export const GET = async () => {
  const spec = await createOpenApiSpec(modules, {
    basePath: "/api",
    info: {
      title: "My API",
      version: "1.0.0",
      description: "Example SvelteKit API",
    },
    servers: [
      { url: "https://api.example.com", description: "Production" },
      { url: "http://localhost:5173", description: "Development" },
    ],
    securitySchemes: {
      bearerAuth: {
        type: "http",
        scheme: "bearer",
        bearerFormat: "JWT",
      },
    },
    security: [{ bearerAuth: [] }],
  });

  return json(spec);
};

Visit:

/openapi

to see your OpenAPI 3.1 JSON.

Generic (non-SvelteKit) usage

import { createOpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";

const modules = import.meta.glob("./routes/**/route.{ts,js}");

async function build() {
  const spec = await createOpenApiSpec(modules, {
    info: { title: "My Service", version: "1.0.0" },
  });

  console.log(JSON.stringify(spec, null, 2));
}

🧾 Request Bodies

Request bodies fully support multi-media content maps:

body: {
  description: "Update profile",
  required: false,
  content: {
    "application/json": v.object({ name: v.string() }),
    "multipart/form-data": v.object({
      name: v.string(),
      avatar: v.string(),
    }),
  },
}

Shorthand (schema only):

body: v.object({ title: v.string() });

is documented as:

{
  "content": {
    "application/json": { "schema": { … } }
  }
}

All body definitions undergo strict validation:

  • media types validated (type/subtype)
  • schemas must be valid Valibot schemas
  • description length capped
  • full deep-frozen sanitized output

📤 Responses

A response may define:

  • a single JSON schema (schema)
  • a content map
  • or both
responses: {
  200: {
    description: "Multiple formats",
    content: {
      "application/json": v.object({ ok: v.string() }),
      "text/plain": v.string(),
      "image/png": v.string(),
    },
  },
  404: {
    description: "Not found",
    schema: v.object({ message: v.string() }),
  },
}

Rules enforced by sanitization:

  • status keys must be numeric 3-digit codes
  • unknown keys rejected
  • description length capped
  • schemas validated + normalized asynchronously
  • content media types validated
  • max 32 responses per endpoint

🔍 Query Parameters

query must be object-like:

  • object(...)
  • wrapped in optional, nullable, nullish, pipe, brand, fallback, default
  • unions of objects only if all branches expose identical keys

Unsupported shapes (primitives, arrays) are rejected.

const Query = v.object({
  search: v.optional(v.string()),
  limit: v.number(),
  sort: v.union([v.literal("asc"), v.literal("desc")]),
});

The generator:

  • unwraps wrapper types
  • validates union shapes
  • rejects mismatched branches
  • extracts top-level fields only
  • produces OpenAPI in: "query" parameters
  • merges documentation from queryParams

Array-typed query values are permitted and documented normally.


🛡 Hardened Sanitization Layer

Every _openapi module is sanitized before inclusion:

  • must be plain, prototype-free objects
  • forbidden keys: __proto__, constructor, prototype
  • no getters/setters
  • max 32 methods per module
  • endpoint definitions validated strictly:
    • allowed keys only
    • required method and responses
    • tags capped, doc strings capped
    • body/query/responses validated structurally
    • deep-frozen immutable output

This prevents prototype pollution, malformed metadata, and unbounded structures from entering your spec.


🧬 Schema Normalization & Budget Limits

Valibot schemas go through a full structural normalization step:

  • async schemas → sync structure
  • date(){ type: "string", format: "date-time" }
  • never() removed
  • wrapper unwrapping
  • union normalization
  • array nesting bounded

To prevent runaway or malicious schemas:

  • max depth: 32
  • max nodes: 10,000
  • max union options: 32
  • max object properties: 128
  • max array nesting: 16

Invalid or pathological schemas fail early with explicit errors.


🧱 Component Schema Registry (deduplication)

Schemas used in request/response bodies are automatically:

  • normalized
  • converted to JSON Schema
  • deduplicated
  • registered under #/components/schemas/...

This avoids excessive inlining and makes the generated spec tooling-friendly.

{
  "components": {
    "schemas": {
      "User_1": { ... },
      "Todo_2": { ... }
    }
  }
}

🏷 Auto-tagging

If an endpoint has tags, the generator aggregates them into a sorted list:

{
  "tags": ["Users", "Todo"]
}

🗂 Path Handling

Your generator now:

  • infers OpenAPI paths from route files ([id]{id})
  • extracts path parameters and documents them automatically
  • ensures all path parameters are required and typed

Example:

src/routes/api/users/[id]/+server.ts
↓
/api/users/{id}

🔐 Authentication / Security

Global security

const spec = await createOpenApiSpec(glob, {
  securitySchemes: {
    bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT" },
  },
  security: [{ bearerAuth: [] }],
});

Per-endpoint overrides

defineEndpoint({
  method: "GET",
  path: "/api/public",
  security: [], // override to no auth
  responses: { 200: { description: "OK" } },
});

❌ What this library does NOT do

  • ❌ No runtime validation
  • ❌ No runtime authentication
  • ❌ No magic route behavior

It is pure documentation generation, not a framework.


📦 Public Types

Import from the published package:

import { EndpointDef, OpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";

🤖 About the Project

This library was not exactly “vibe-coded” or generated blindly. It was built through an iterative workflow where AI was used as a technical assistant, not as an author.

All architectural decisions, schema handling logic, and API design were intentionally crafted by the maintainer, with AI serving as a tool to accelerate refactoring, validate edge cases, and improve TypeScript ergonomics.

Every line of code was reviewed, tested, and integrated with a somehow clear understanding of SvelteKit, Valibot, and OpenAPI constraints.

So any mistake or naivety is purely mine, amplified by AI abuse 😉