-
I'm encountering type inference issues specifically when using .anyOf with t.KeyOf() inside a generic function: import * as t from '@sinclair/typebox';
import type { TObject } from '@sinclair/typebox';
const getListParams = <T extends TObject>(schema: T) => {
return t.Object({
queryColumns: t.Optional(t.Tuple(t.KeyOf(schema).anyOf)) // .anyOf breaks inference
});
};
const schema = t.Object({
foo: t.String(),
bar: t.String(),
baz: t.Integer(),
});
// What I get (loses literal types):
const params = getListParams(schema);
// queryColumns: t.TOptional<t.TTuple<[t.TLiteral<string>, t.TLiteral<number>]>>
// What I want (preserve literal types):
// queryColumns: t.TOptional<
// t.TTuple<[t.TLiteral<'foo'>, t.TLiteral<'bar'>, t.TLiteral<'baz'>]>
// > The .anyOf seems necessary to convert the union from KeyOf into something Tuple can accept, but it's causing us to lose the specific literal types in the generic context. Is there a better way to handle this conversion while maintaining type inference? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Nvm this is a dumb thing, this should not be a Tuple, I misunderstood the definition of a Tuple. This should just be an array with uniqueItems: true as the options then just use a union derived from keyof. import type { TObject } from '@sinclair/typebox';
import { t } from 'elysia';
interface ModelOptions {
resourceId: string;
list: TObject;
}
const getListParams = <const T extends TObject>(list: T) => {
return t.Object(
{
query: t.Optional(t.String()),
queryColumns: t.Optional(
t.Array(t.KeyOf(list), {
uniqueItems: true,
}),
),
orderBy: t.KeyOf(list),
order: t.Union([t.Literal('asc'), t.Literal('desc')], { default: 'asc' }),
offset: t.Numeric({ default: 0 }),
limit: t.Numeric({ default: 50, minimum: 1, maximum: 1000 }),
},
{
readOnly: true,
},
);
};
export const createModel = <const T extends Readonly<ModelOptions>>(
options: T,
) => {
return {
resourceId: options.resourceId,
listParams: getListParams<T['list']>(options.list),
};
};
const schema = t.Object({
foo: t.String(),
bar: t.Union([t.Literal('a'), t.Literal('b')]),
});
const listParams = getListParams(schema);
// type StaticListParams = {
// query?: string;
// queryColumns?: ('foo' | 'bar')[];
// orderBy: 'foo' | 'bar';
// order: 'asc' | 'desc';
// offset: number;
// limit: number;
// };
type StaticListParams = typeof listParams.static;
const model = createModel({
resourceId: 'foo',
list: schema,
}); Passing a generic explicitly to the getListParams from the createModel also let's us keep the nice type inference with literals. |
Beta Was this translation helpful? Give feedback.
Nvm this is a dumb thing, this should not be a Tuple, I misunderstood the definition of a Tuple. This should just be an array with uniqueItems: true as the options then just use a union derived from keyof.