Skip to content

ExtractQuery resolves to unknown for open-record validators (vine.record), breaking generated registry #116

Description

@HeinTouchableFR

Summary

When a POST/PUT/PATCH/DELETE endpoint's validator has an open-record inferred input (e.g. vine.record(vine.string()) from VineJS, whose InferInput is { [K: string]: string }), the generated registry emits:

query: ExtractQuery<InferInput<typeofcontactValidator>>

ExtractQuery resolves to unknown, which violates EndpointTypes['query']: Record<string, any>. tsc then fails inside the generated registry/index.ts (satisfies Record<string, AdonisEndpoint>) and at the createTuyau({ registry }) call site.

Environment

  • @tuyau/core: 1.2.2
  • @vinejs/vine: 4.4.0
  • TypeScript: 5.9
  • AdonisJS 7 app using generateRegistry() from @tuyau/core/hooks

Minimal reproduction

// app/validators/contact.tsimportvinefrom'@vinejs/vine'exportconstcontactValidator=vine.create(vine.record(vine.string().trim().maxLength(2000)))
// controllerexportdefaultclassContactController{asyncexecute({ request, response }: HttpContext){constpayload=awaitcontactValidator.validate(request.all())// ...returnresponse.redirect().back()}}
router.post('/contact',[ContactController,'execute'])
// clientimport{registry}from'./.adonisjs/client/registry'import{createTuyau}from'@tuyau/core/client'exportconstclient=createTuyau({baseUrl: '/', registry })

Actual behavior

tsc --noEmit fails with:

.adonisjs/client/registry/index.ts: error TS2322: Type '{ body: ...; query: unknown; ... }' is not assignable to type 'EndpointTypes'.
Types of property 'query' are incompatible.
Type 'unknown' is not assignable to type 'Record<string, any>'.

and the same incompatibility cascades into the createTuyau({ registry }) call site (TuyauRegistry constraint).

Root cause

ExtractQuery is defined as:

typeExtractQuery<T>='query'extendskeyofT ? Textends{query?: infer Q} ? Q : {} : {}

For T = { [K: string]: string }:

  1. 'query' extends keyof T is true (keyof of a string index signature is string | number), so the guard does not filter open records out.
  2. TypeScript does not infer from an index signature into an optional property position: T extends { query?: infer Q } matches, but there is no declaredquery property, so Q has no inference candidates and falls back to unknown.

Hence query: unknown in the generated registry, which is not assignable to Record<string, any>.

Note that ExtractBody (DistributiveOmit) handles the same input fine — only query breaks.

Expected behavior

An open-record validator declares no query params, so query should resolve to {} instead of unknown.

A minimal, semantics-preserving fix in ExtractQuery — only accept the inferred type when it satisfies the Record<string, any> constraint that EndpointTypes requires, otherwise fall back to {}:

typeExtractQuery<T>='query'extendskeyofT
? Textends{query?: infer Q}
? [Q]extends[Record<string,any>]
? Q
: {}
: {}
: {}

This only changes outcomes that are currently hard type errors (unknown, or a scalar query field as in #115); object-typed query declarations keep working exactly as today.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions