From f57bf5d61dfdfcac44a32894bc18d1b4c8165d47 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 19 Sep 2026 21:20:41 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=90=9B:=20query-less=20table's=20Query?= =?UTF-8?q?=20type=20broke=20FK=20dropdown=20fetches=20(P0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3625ce8 fixed the no-empty-object-type lint error by switching a zero-IsQuery table's {ClassName}Query to `Record`, the same default useTable.ts's own `TQuery extends object = Record` uses. That default is safe there only because useTable.ts's one internal `TQuery & PageQuery` goes through an `as` cast rather than a structural check. Code that builds the object literal directly does not get that protection - and vue.go.template's foreign-key dropdown fetch does exactly that: `list{FkClass}({ pageIndex: 1, pageSize: 100 })`. Record is a mapped type over every string key, each mapped to never, so `Record & PageQuery` does not leave PageQuery's own properties alone: pageIndex becomes `never & number`, i.e. never, and no value can be passed for it - a straight type error, not a lint warning, so the previous fix's pnpm lint pass did not catch it. Trips on any query-less table referenced by a foreign key - a plain lookup/dict table used as a dropdown source, an ordinary shape, not a rare one. Switched to Record: a mapped type over the empty key set, which behaves as the empty object type `{}` under intersection (PageQuery's properties come through unchanged) while still satisfying `TQuery extends object` and not tripping no-empty-object-type (it is a generic instantiation, not a literal `{}` type annotation) - confirmed all three separately before touching the template. Verified with node 24.11.0: generated a real zero-IsQuery table, copied its .ts into go-admin-ui alongside a throwaway file reproducing vue.go.template's exact FK call site (`await list{Class}({ pageIndex: 1, pageSize: 100 })`), and ran both eslint and vue-tsc --noEmit - the type-check step lint alone cannot cover, which is what let this through the first time. Confirmed red first (swapped the generated file's Record back to Record): vue-tsc reported the exact "Property 'pageIndex' is incompatible with index signature" error. Restored the fix - both clean. --- template/v4/ts.go.template | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/template/v4/ts.go.template b/template/v4/ts.go.template index 4387eff1..d5e70a0f 100644 --- a/template/v4/ts.go.template +++ b/template/v4/ts.go.template @@ -58,12 +58,31 @@ export interface {{.ClassName}}Query { `export interface {ClassName}Query {}` is what naturally falls out of the range above finding nothing to iterate, but an empty interface trips @typescript-eslint/no-empty-object-type and fails pnpm lint. - Record is go-admin-ui's own answer to the same shape - - see composables/useTable.ts's `TQuery extends object = Record` - - and it intersects with PageQuery the same way an empty interface would - have, so callers do not have to special-case a query-less table. + + Record (this file's first attempt, and the type + useTable.ts's own `TQuery extends object = Record` default + uses) looks like the obvious match but is wrong here: it is a mapped type + over *every* string key, each mapped to never, so intersecting it with + PageQuery does not leave PageQuery alone - `pageIndex` becomes + `never & number`, i.e. never, and no value can be passed for it at all. + useTable.ts itself never hits this because its one internal use of + `TQuery & PageQuery` goes through an `as` cast rather than a structural + check (composables/useTable.ts ~line 160); code that builds the object + literal directly - such as a foreign-key column's + `list{FkClass}({ pageIndex: 1, pageSize: 100 })` call in vue.go.template - + is not casting anything and hits the real error, only when the referenced + table happens to have no query columns of its own (a plain lookup/dict + table used as a dropdown source, not a rare shape). + + Record is the type with the same intent - "no query + columns" - but the mapped-type domain is `never`, so it has no keys at + all rather than "every key, mapped to never": it behaves as the empty + object type `{}` under intersection, leaving PageQuery's own pageIndex/ + pageSize untouched, and confirmed separately not to trip + no-empty-object-type either (it is a generic instantiation, not a + literal `{}` type annotation). */ -}} -export type {{.ClassName}}Query = Record +export type {{.ClassName}}Query = Record {{- end}} export function list{{.ClassName}}(query: {{.ClassName}}Query & PageQuery) {