Skip to content

createValidate StandardSchema implementation loses input type information, breaking type inference in type-safe RPC frameworks #1681

@switchmd

Description

@switchmd

Background

Type-safe RPC frameworks like SvelteKit's remote functions and tRPC rely on StandardSchema for parameter validation while maintaining end-to-end type safety. StandardSchema is an interface that separates Input and Output types, allowing type inference through InferInput and InferOutput utility types.

Typia added StandardSchema support to createValidate and createValidateEquals functions starting from v9.2.0. However, the current implementation doesn't properly propagate type information, causing DX issues in type-safe RPC frameworks.

Problem

The current type signature of typia.createValidate<T>() is:

function createValidate<T>(): ((input: unknown) => IValidation<T>) & StandardSchemaV1<unknown, T>

Issues with this signature:

  1. Input type is hardcoded as unknown: In StandardSchemaV1<unknown, T>, the first generic parameter (Input) is hardcoded as unknown.

  2. Type inference failure in RPC frameworks: Type-safe RPC frameworks use StandardSchemaV1.InferInput<Schema> to infer parameter types for client-side calls. However, with typia, this type is always inferred as unknown, breaking auto-completion and type checking.

  3. Practical example:

// data.remote.ts (SvelteKit)
import typia from 'typia';
import { query } from '$app/server';

interface Post {
  slug: string;
  title: string;
  content: string;
}

const postSchema = typia.createValidate<Post>();

export const getPost = query(postSchema, async (params) => {
  params.slug // ✅ Server-side type inference works correctly (Post type)
  // ...
});

Server-side type inference works correctly. However, the problem occurs on the client-side:

// +page.svelte
const post = await getPost(/* ❌ Parameter type is inferred as unknown */);

The inferred type shows:

RemoteQueryFunction<unknown, { content: string; slug: string; title: string; }>

Since the first generic of RemoteQueryFunction is unknown:

  • Auto-completion doesn't work when calling the function on the client
  • Type checking doesn't work (no error even when passing wrong arguments)
  • Developers lose the benefits of end-to-end type safety

Root cause: Because the Input type in StandardSchemaV1<unknown, Post> is unknown, the framework's InferInput utility returns unknown.

Expected Behavior

StandardSchema separates Input and Output types to allow schema libraries that support data transformation or type coercion to clearly distinguish between input and output types. The interface is defined as StandardSchemaV1<Input = unknown, Output = Input>, explicitly stating that Input and Output can be different.

The reason for separating Input and Output types:

  1. Transformation: When using transform APIs in libraries like Zod, input and output types can differ:
// Zod example
const mySchema = z.string().transform((val) => val.length);
// Input: string
// Output: number
  1. Coercion: When using type coercion, the type at input time can differ from the type after validation (e.g., converting string "123" to number 123).

  2. Data enrichment: Adding default values, computed fields, etc.

In typia's case, since createValidate only performs validation without any data transformation, Input and Output should be the same type T:

function createValidate<T>(): ((input: unknown) => IValidation<T>) & StandardSchemaV1<T, T>

Proposed Solution

Change the Input type parameter to T:

function createValidate<T>(): ((input: unknown) => IValidation<T>) & StandardSchemaV1<T, T>

This change will:

  • Enable perfect type inference in type-safe RPC frameworks
  • Restore end-to-end type safety that these frameworks provide
  • Improve type safety and auto-completion support for developers
  • Maintain consistency with other StandardSchema implementations (Zod, Valibot, etc.)
  • Align with typia's actual behavior (no transformation)

PR 열까유?

Metadata

Metadata

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions