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

svelte-reflector

v2.5.3

Published

Reflects types from openAPI schemas

Readme

Svelte Reflector

Turn your OpenAPI into a first-class Svelte 5 DX.

Svelte Reflector is a developer-experience-first code generator that converts OpenAPI specs into fully typed, reactive Svelte 5 modules — ready for production, forms included.

npm version npm downloads npm total downloads TypeScript Svelte

Features

  • Automatic Type Generation - Generates TypeScript interfaces and classes from OpenAPI schemas
  • Svelte 5 Runes Integration - Uses $state and $derived for reactive state management
  • Abstract Modules - Generated modules are abstract classes, ready to be extended with custom logic
  • Per-Module Schemas - Each module gets its own schema file with only the types it needs (tree-shaking friendly)
  • Form Handling - Auto-generates form schemas with BuildedInput<T> wrappers and validation support
  • Type-Safe API Calls - Full TypeScript support for all API operations
  • Query Parameter Sync - QueryBuilder and EnumQueryBuilder keep state synced with URL searchParams
  • Enum Support - Auto-generates enum types and array enum query builders
  • OpenAPI/Swagger Compatible - Works with any backend that exposes OpenAPI specs
  • Development Mode - Smart regeneration based on environment
  • Validation Ready - Built-in support for custom field validators
  • Vite Plugin - Can be used as a Vite plugin for automatic generation on build

Installation

npm install svelte-reflector
# or
yarn add svelte-reflector
# or
pnpm add svelte-reflector

Note: prettier >= 3.0.0 is a required peer dependency. Make sure it's installed in your project.

Quick Start

1. Configure Environment Variables

Create a .env file in your project root:

# Required - Your backend URL
BACKEND_URL=https://api.example.com/
# or
PUBLIC_BACKEND=https://api.example.com/

# Optional - Environment (defaults to PROD)
ENVIRONMENT=DEV
# or
VITE_ENVIRONMENT=DEV

2. Create Reflector Config (Optional)

Create a src/reflector.config.ts to define custom validators:

export const validators = [
  {
    fields: ["email", "userEmail"],
    validator: "validateEmail",
  },
  {
    fields: ["phone", "mobile"],
    validator: "validatePhone",
  },
];

Validators are resolved from $lib/sanitizers/validateFormats — you need to implement and export them in your project.

3. Configure API Import Path (Optional)

Create a reflector.json in your project root to customize the API import path:

{
  "api": "$lib/api"
}

Defaults to $lib/api if not specified. This is the module that generated modules will import for making HTTP requests.

4. Run the Generator

# Manual generation
npx reflect

# Or programmatically as a Vite plugin
import { reflector } from "svelte-reflector";
await reflector(true); // true = force generation

5. Use Generated Modules

Generated modules are abstract classes. Extend them to add custom logic or simply to instantiate:

import { UserModule } from "$reflector/controllers/user/user.module.svelte";
import type { User } from "$reflector/controllers/user/user.schema.svelte";

// Extend the abstract module
class UserService extends UserModule {}

const userService = new UserService();

// Access reactive state
console.log(userService.loading); // $state<boolean>
console.log(userService.list);    // $state<User[]>

// Call API methods
await userService.listAll({
  behavior: {
    onSuccess: (response) => console.log(response),
    onError: (error) => console.error(error),
  },
});

// Work with forms
const userForm = userService.forms.createUser;
userForm.name.value = "John Doe";
userForm.email.value = "[email protected]";

// Submit form
await userService.createUser();

Generated Structure

src/reflector/
├── controllers/
│   └── user/
│       ├── user.module.svelte.ts      # Abstract API module with methods
│       └── user.schema.svelte.ts      # Schemas & types used by this module
├── reflector.svelte.ts                # Core utilities (build, isFormValid, QueryBuilder, etc.)
├── fields.ts                          # Field name constants
├── enums.ts                           # Enum type definitions
├── mocked-params.svelte.ts            # Mocked path parameters ($state)
└── backup.json                        # Cached OpenAPI spec

Each module gets its own schema file (*.schema.svelte.ts) containing only the schemas it uses, with transitive dependencies automatically resolved.

Generated Module API

Each generated module is an abstract class that provides:

State Properties

| Property | Type | Description | |----------|------|-------------| | loading | $state<boolean> | Request loading state | | list | $state<T[]> | List results (for list endpoints) | | forms | $state<Record<string, T>> | Form instances | | querys | Querys | Query parameter state (QueryBuilder instances) | | headers | Headers | Header state | | paths | Paths | Path parameter state |

Methods

// List all items (GET with page parameter)
async listAll(params?: { behavior?: Behavior }): Promise<T[]>

// Get single entity (GET without page parameter)
async get(params?: { behavior?: Behavior }): Promise<T>

// Create/Update (POST/PUT/PATCH)
async create(params?: { behavior?: Behavior }): Promise<T>
async update(params?: { behavior?: Behavior }): Promise<T>

// Delete (DELETE)
async delete(params?: { behavior?: Behavior }): Promise<void>

// Reset all state (protected)
protected reset(): void

// Clear forms (protected)
protected clearForms(): void

reset() and clearForms() are protected — override them in your subclass if you need custom reset behavior.

QueryBuilder

Query parameters are wrapped in QueryBuilder / EnumQueryBuilder instances that read directly from page.url.searchParams. There is no local cached state — every read goes through the URL, so multiple instances with the same key are always coherent.

// Single value query parameter
const querys = module.querys;

querys.status.value;          // string | null — read-only getter, always
                              // reflects the current URL
querys.status.update("active"); // pushes ?status=active via goto()

// Array enum query parameter
const enumQuery = module.querys.roles; // EnumQueryBuilder<RoleType>
enumQuery.selected = "admin";
enumQuery.add();      // appends to URL searchParams
enumQuery.remove(0);  // removes from URL searchParams
enumQuery.values;     // $derived — always in sync with URL

Defaults

default declared in the OpenAPI schema is propagated to the builder constructor automatically:

parameters:
  - name: limit
    in: query
    schema: { type: integer, default: 10 }
  - name: tags
    in: query
    schema:
      type: array
      items: { type: string, enum: [hot, new, sale] }
      default: [hot]

generates:

class Querys {
  readonly limit = new QueryBuilder({ key: 'limit', defaultValue: 10 });
  readonly tags = new EnumQueryBuilder<'hot' | 'new' | 'sale'>({
    key: 'tags',
    defaultValues: ['hot'],
  });
}

When the URL has the param, the URL value wins. When the URL has no param, the default is returned. The URL stays clean until the user interacts.

Migrating from 1.x

value is no longer a setter. Two replacements:

// 1.x
querys.page.value = "1";
querys.page.value ??= "1"; // seed default

// 2.x — declarative default (preferred, set at codegen via OpenAPI)
new QueryBuilder({ key: "page", defaultValue: "1" });

// 2.x — imperative update (push to URL)
querys.page.update("1");

The auto-injected setQueryGroup([...]) constructor on the generated Querys class was removed — defaults now live on the builder. Import setQueryGroup manually from $reflector/reflector.svelte if you still need batch URL writes.

Ephemeral pagination (sidebar / widget)

Sometimes a list lives outside the canonical route — a global sidebar, a widget, a paginated combobox in a modal — and should NOT mutate the current URL. For those cases, every generated call() with query params accepts an optional queryOverride:

const sidebar = new UserService();

// Current URL stays put. Request goes out with ?page=2&limit=10.
await sidebar.listAll({
  queryOverride: { page: "2", limit: "10" },
});

When queryOverride is passed, the method skips this.querys.bundle() entirely and uses the override as queryData. Without it, the method reads from QueryBuilder.value (the URL) as usual — so you can mix and match on a per-call basis. Omit a key to drop it from the request; pass null to send a literal null.

Configuration

Environment Variables

| Variable | Required | Description | |----------|----------|-------------| | BACKEND_URL | Yes* | Backend API URL | | PUBLIC_BACKEND | Yes* | Alternative to BACKEND_URL | | ENVIRONMENT | No | DEV/PROD (defaults to PROD) | | VITE_ENVIRONMENT | No | Vite-specific env var | | NODE_ENV | No | Node environment |

* At least one of BACKEND_URL or PUBLIC_BACKEND is required.

Behavior Pattern

All API methods accept a Behavior object for callbacks:

class Behavior<TSuccess, TError> {
  onSuccess?: (value: TSuccess) => Promise<void> | void;
  onError?: (error: TError) => Promise<void> | void;
}

// Usage
await userService.createUser({
  behavior: {
    onSuccess: (user) => console.log("Created:", user),
    onError: (err) => console.error("Failed:", err),
  },
});

Form Validation

Forms use BuildedInput class with validation:

class BuildedInput<T> {
  value: T;           // Current value ($state)
  display: T;         // Display value ($state)
  required: boolean;  // Is field required
  placeholder: T;     // Placeholder/example value
  readonly kind: 'builded';
  validator?: (v: T) => string | null; // Validation function
  validate(): string | null; // Run validation
}

// Check if all form fields are valid
import { isFormValid } from "$reflector/reflector.svelte";

if (isFormValid(userService.forms.createUser)) {
  await userService.createUser();
}

TypeScript Configuration

Add path aliases to your tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "$reflector/*": ["./src/reflector/*"],
      "$lib/*": ["./src/lib/*"]
    }
  }
}

For Vite projects, also update vite.config.ts:

export default defineConfig({
  resolve: {
    alias: {
      $reflector: path.resolve("./src/reflector"),
      $lib: path.resolve("./src/lib"),
    },
  },
});

Workflow

Development Mode

In ENVIRONMENT=DEV:

  • Schemas are NOT auto-regenerated on build
  • Use npx reflect to manually regenerate
  • Faster builds, manual control

Production Mode

In ENVIRONMENT=PROD:

  • Schemas are auto-regenerated on each build
  • Fresh types from latest OpenAPI spec
  • Fallback to backup.json if backend is unavailable

Advanced Usage

Extending Abstract Modules

Since modules are abstract, you can add custom logic:

import { UserModule } from "$reflector/controllers/user/user.module.svelte";

class UserService extends UserModule {
  // Add custom computed state
  get activeUsers() {
    return this.list.filter(u => u.active);
  }

  // Override protected methods for custom behavior
  protected override clearForms() {
    super.clearForms();
    // custom cleanup logic
  }

  // Add custom methods
  async fetchAndFilter(status: string) {
    this.querys.status.update(status);
    await this.listAll();
  }
}

Manual Schema Access

import { User } from "$reflector/controllers/user/user.schema.svelte";

// Create instance
const user = new User({ name: "John", email: "[email protected]" });

// Get data bundle
const data = user.bundle(); // { name: "John", email: "[email protected]" }

Batch Query Updates

import { setQueryGroup } from "$reflector/reflector.svelte";

// Update multiple query params at once
setQueryGroup([
  { key: "page", value: 1 },
  { key: "status", value: "active" },
  { key: "roles", value: ["admin", "editor"] }, // Array params supported
]);

Troubleshooting

"BACKEND_URL vazio" Error

Ensure you have set BACKEND_URL or PUBLIC_BACKEND in your .env file.

Schemas Not Updating

In DEV mode, run npx reflect manually. Check that your backend's OpenAPI spec is accessible at {BACKEND_URL}openapi.json.

Type Errors After Generation

  1. Restart your TypeScript language server
  2. Check path aliases in tsconfig.json
  3. Ensure $reflector/* alias is configured

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Links


Built with by the Pinaculo Digital team.