A
arktype•2mo ago
Bobakanoosh

Manually declare output type for a validation

Hello - I'm working on a library that emits types inferred from arktype validations. I want the inferred output type to use a named type (which i also export), instead of the literal inferred value of that type. So:
// not this
fruit: "apple" | "banana" | "cherry";
// instead I want this
fruit: Fruit,
// not this
fruit: "apple" | "banana" | "cherry";
// instead I want this
fruit: Fruit,
Given the following code
export const FRUITS = ["apple", "banana", "cherry"] as const;
export type Fruit = (typeof FRUITS)[number];
const FruitSchema = type("===", ...FRUITS);

export const FoodSchema = type({
fruit: FruitSchema,
});

export type Food = typeof FoodSchema.infer;
export const FRUITS = ["apple", "banana", "cherry"] as const;
export type Fruit = (typeof FRUITS)[number];
const FruitSchema = type("===", ...FRUITS);

export const FoodSchema = type({
fruit: FruitSchema,
});

export type Food = typeof FoodSchema.infer;
I get the following output (built with tsdown):
import * as arktype_internal_methods_object_ts0 from "arktype/internal/methods/object.ts";

//#region index.d.ts
declare const FRUITS: readonly ["apple", "banana", "cherry"];
type Fruit = (typeof FRUITS)[number];
declare const FoodSchema: arktype_internal_methods_object_ts0.ObjectType<{
fruit: "apple" | "banana" | "cherry";
}, {}>;
type Food = typeof FoodSchema.infer;
//# sourceMappingURL=index.d.ts.map

//#endregion
export { FRUITS, Food, FoodSchema, Fruit };
//# sourceMappingURL=index.d.mts.map
import * as arktype_internal_methods_object_ts0 from "arktype/internal/methods/object.ts";

//#region index.d.ts
declare const FRUITS: readonly ["apple", "banana", "cherry"];
type Fruit = (typeof FRUITS)[number];
declare const FoodSchema: arktype_internal_methods_object_ts0.ObjectType<{
fruit: "apple" | "banana" | "cherry";
}, {}>;
type Food = typeof FoodSchema.infer;
//# sourceMappingURL=index.d.ts.map

//#endregion
export { FRUITS, Food, FoodSchema, Fruit };
//# sourceMappingURL=index.d.mts.map
Which makes complete sense, but wondering if theres something like Drizzle ORM's. .$type<Fruit> to get this behavior.
9 Replies
TizzySaurus
TizzySaurus•2mo ago
There's type(...).as<Type>(). E.g.
type({fruit: "'apple' | 'banana'"}).as<{fruit: Fruit}>()
type({fruit: "'apple' | 'banana'"}).as<{fruit: Fruit}>()
Bobakanoosh
BobakanooshOP•2mo ago
🤯 Hmm, wish I didn't have to do it on the containing object since I still want inference for everything else on the object. Doing just this doesn't work:
const FruitSchema = type("===", ...FRUITS).as<Fruit>();
const FruitSchema = type("===", ...FRUITS).as<Fruit>();
TizzySaurus
TizzySaurus•2mo ago
What exactly doesn't work about that?
Bobakanoosh
BobakanooshOP•2mo ago
export const FRUITS = ["apple", "banana", "cherry"] as const;
export type Fruit = (typeof FRUITS)[number];
const FruitSchema = type("===", ...FRUITS).as<Fruit>();

export const FoodSchema = type({
fruit: FruitSchema.as<Fruit>(),
});
export const FRUITS = ["apple", "banana", "cherry"] as const;
export type Fruit = (typeof FRUITS)[number];
const FruitSchema = type("===", ...FRUITS).as<Fruit>();

export const FoodSchema = type({
fruit: FruitSchema.as<Fruit>(),
});
yields
declare const FoodSchema: arktype_internal_methods_object_ts0.ObjectType<{
fruit: "apple" | "banana" | "cherry";
}, {}>;
declare const FoodSchema: arktype_internal_methods_object_ts0.ObjectType<{
fruit: "apple" | "banana" | "cherry";
}, {}>;
TizzySaurus
TizzySaurus•2mo ago
You shouldn't need to repeat the .as in the FoodSchema Try that and see what happens
Bobakanoosh
BobakanooshOP•2mo ago
Yeah I tried that too, no luck
TizzySaurus
TizzySaurus•2mo ago
Yeah, not sure then, sorry
Bobakanoosh
BobakanooshOP•2mo ago
All good!
ssalbdivad
ssalbdivad•2mo ago
In general I've found it's a lot easier to get TS to avoid expanding objects than primitive unions. You could also try something like this:
import { declare } from "arktype"

export const FRUITS = ["apple", "banana", "cherry"] as const
export type Fruit = (typeof FRUITS)[number]
const FruitSchema = declare<Fruit>().type(["===", ...FRUITS])
import { declare } from "arktype"

export const FRUITS = ["apple", "banana", "cherry"] as const
export type Fruit = (typeof FRUITS)[number]
const FruitSchema = declare<Fruit>().type(["===", ...FRUITS])
This avoids having to cast (this will enforce the expected inference). Locally though, I still see the literal union when hovering the type (not sure what happens with your build tooling in .d.ts). If you have a way to reliably stop TS from expanding a string literal union like this though would definitely be interested

Did you find this page helpful?