Skip to content

Generated registry breaks createTuyau typing when a non-GET validator body field is named "query" #115

Description

@TAYUN

Summary

When a non-GET endpoint uses a validator body field named query, the generated Tuyau registry becomes incompatible with AdonisEndpoint, and createTuyau({ registry }) falls back to TransformApiDefinition<unknown>.

This ends up breaking unrelated route tree typing like tuyau.api.spaces.index and tuyau.api.documents.index.

This looks like a typing bug / design hole around reserved transport keys (query, possibly also params, headers, cookies) in generated registry types.

Environment

  • @tuyau/core: 1.2.2
  • AdonisJS app using generated registry via generateRegistry()
  • TypeScript project consuming createTuyau({ registry })

Minimal Reproduction

Validator

import vine from '@vinejs/vine'

export const ragSearchValidator = vine.create({
  query: vine.string().trim().minLength(1),
})

Controller

import type { HttpContext } from '@adonisjs/core/http'

export default class RagController {
  async search({ request }: HttpContext) {
    const payload = await request.validateUsing(ragSearchValidator)

    return {
      data: {
        query: payload.query,
        hits: [],
      },
    }
  }
}

Route

router.post('/api/rag/search', [RagController, 'search'])

Client

import { registry } from '@my-api/registry'
import { createTuyau } from '@tuyau/core/client'

export const tuyau = createTuyau({
  registry,
  baseUrl: '/',
})

Then unrelated usages start failing:

tuyau.api.spaces.index({})
tuyau.api.documents.index({})

Actual Behavior

Generated registry contains something like:

types: {
  body: ExtractBody<InferInput<typeof ragSearchValidator>>
  paramsTuple: []
  params: {}
  query: ExtractQuery<InferInput<typeof ragSearchValidator>>
  response: ...
}

Because the validator has a field named query, ExtractQuery<...> resolves to string instead of an object shape.

But Tuyau's endpoint type requires:

query: Record<string, any>

So the generated route no longer satisfies AdonisEndpoint, and the registry becomes invalid.

This eventually causes createTuyau({ registry }) to infer unknown, so the whole typed route tree collapses.

TypeScript error looks like:

Property 'spaces' does not exist on type 'TransformApiDefinition<unknown>'

and/or:

Type '{ ... }' is not assignable to type 'AdonisEndpoint'
Types of property 'query' are incompatible.
Type 'string' is not assignable to type 'Record<string, any>'

Expected Behavior

One of these should happen:

  1. A body field named query should remain a body field for non-GET endpoints, and not poison the generated registry.
  2. If query is intentionally reserved by Tuyau, the generator should emit a clear, local error explaining that this key is reserved.
  3. In any case, a single route naming conflict should not cause the entire createTuyau({ registry }) API tree to collapse to unknown.

Why this seems like a bug

I understand Tuyau treats query as transport metadata, but the current failure mode is too broad:

  • the route-level conflict is not surfaced clearly
  • unrelated routes lose typing
  • the consumer only sees downstream errors like TransformApiDefinition<unknown>

This makes the issue hard to diagnose.

Workaround

Renaming the field from query to something like searchText fixes the issue immediately.

export const ragSearchValidator = vine.create({
  searchText: vine.string().trim().minLength(1),
})

Related Context

This may be related to non-GET query extraction behavior discussed here:

Suggested Fix Direction

Possible fixes:

  • reserve query/params/headers/cookies explicitly and fail generation with a clear message
  • improve ExtractQuery / registry generation so body fields on non-GET routes don't invalidate endpoint typing
  • avoid collapsing the full route tree to unknown when one route is malformed

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