A
arktypeβ€’3mo ago
Roamin

Best way to pass `Type` around

Hello! First, thanks for building this really cool library πŸ™ I first heard of arktype while I was looking at the code of a framework called MUD, and while I was discussing CLI libraries with @frolic the other day, I thought that maybe it'd be a cool idea to have a CLI library that leverages arktype for arguments and options validation/parsing. I basically would like to have a function that receives args that are generated from arktype definitions. This is the code I have so far:
import { type Type, type, validateAmbient } from "arktype";
import { type ErrorMessage } from "@ark/util";

type ValidateArguments<T> = {
[K in keyof T]: {
type: validateAmbient<T[K]>;
};
};

type ArkType = Type | Type<(_: any) => any>;

type InferredArkType<T> = T extends ArkType
? T["infer"]
: ErrorMessage<"invalid Ark type">;

type ParseArguments<T> = {
[K in keyof T]: InferredArkType<T[K]>;
};

type Command<T> = {
arguments: ValidateArguments<T>;
run(args: ParseArguments<T>): void | Promise<void>;
};

function defineCommand<const conf>(command: Command<conf>) {
console.log(command);
}

defineCommand({
arguments: {
a: {
type: type(`string>2`),
},
b: {
type: type("string").narrow((address, ctx) => {
if (address === [...address].reverse().join("")) {
return true;
}
return ctx.mustBe("a palindrome");
}),
},
c: {
type: type(["string", "=>", parseFloat]),
},
d: {
type: type("string.json").pipe.try(
(s): object => JSON.parse(s),
type({
name: "string",
version: "string.semver",
})
),
},
f: {
type: type("string|undefined"),
},
},
run(args) {
args.a;
// ^?
args.b;
// ^?
args.c;
// ^?
args.d;
// ^?
args.f;
// ^?
},
});
import { type Type, type, validateAmbient } from "arktype";
import { type ErrorMessage } from "@ark/util";

type ValidateArguments<T> = {
[K in keyof T]: {
type: validateAmbient<T[K]>;
};
};

type ArkType = Type | Type<(_: any) => any>;

type InferredArkType<T> = T extends ArkType
? T["infer"]
: ErrorMessage<"invalid Ark type">;

type ParseArguments<T> = {
[K in keyof T]: InferredArkType<T[K]>;
};

type Command<T> = {
arguments: ValidateArguments<T>;
run(args: ParseArguments<T>): void | Promise<void>;
};

function defineCommand<const conf>(command: Command<conf>) {
console.log(command);
}

defineCommand({
arguments: {
a: {
type: type(`string>2`),
},
b: {
type: type("string").narrow((address, ctx) => {
if (address === [...address].reverse().join("")) {
return true;
}
return ctx.mustBe("a palindrome");
}),
},
c: {
type: type(["string", "=>", parseFloat]),
},
d: {
type: type("string.json").pipe.try(
(s): object => JSON.parse(s),
type({
name: "string",
version: "string.semver",
})
),
},
f: {
type: type("string|undefined"),
},
},
run(args) {
args.a;
// ^?
args.b;
// ^?
args.c;
// ^?
args.d;
// ^?
args.f;
// ^?
},
});
8 Replies
Roamin
RoaminOPβ€’3mo ago
The first version of the ArkType type's definition was actually type ArkType = Type, but for some reason narrows and morphs weren't passing the check T extends Type, so I had to use type ArkType = Type | Type<(_: any) => any>; I guess my question is about what would be the best way to pass Type around and extract the out/infer type? I'm fairly new to arktype and I may not have made the best choice here πŸ™‚
Dimava
Dimavaβ€’3mo ago
Seems like you'd better use definitions
function defineCommand<const def>(
o: {arguments: validateAmbient<def>
){
type(o.arguments)
}
defineCommant({
first: 'boolean',
default: 'number = 123',
json: 'string.json.parsed',
number: 'string.integer.parse',
'optional?': 'string'
})
function defineCommand<const def>(
o: {arguments: validateAmbient<def>
){
type(o.arguments)
}
defineCommant({
first: 'boolean',
default: 'number = 123',
json: 'string.json.parsed',
number: 'string.integer.parse',
'optional?': 'string'
})
PIat
PIatβ€’3mo ago
What does validateAmbient do?
Dimava
Dimavaβ€’3mo ago
Scopeless validateDefinition (with one generic arg)
PIat
PIatβ€’3mo ago
Nice and concise, thanks ☺️
Roamin
RoaminOPβ€’3mo ago
Thanks! If I were to change your defineCommand definiton to the one below, how would you define args so that its props types all have the inferred types?
function defineCommand<const def>(o: {
arguments: validateAmbient<def>;
run: (args: ???<def>) => void;
}) { }
function defineCommand<const def>(o: {
arguments: validateAmbient<def>;
run: (args: ???<def>) => void;
}) { }
I tried using inferAmbient, but narrows/morphs don't get "fully" inferred:
function defineCommand<const def>(o: {
arguments: validateAmbient<def>;
run: (args: inferAmbient<def>) => void;
}) {}

defineCommand({
arguments: {
first: "boolean",
default: "number = 123",
number: ["string", "=>", parseFloat],
"optional?": "string",
},
run(args) {
args;
// ^?
// (parameter) args: {
// first: boolean;
// default: (In?: number | undefined) => Default<123>;
// number: (In: string) => Out<number>;
// optional?: string | undefined;
// }
},
});
function defineCommand<const def>(o: {
arguments: validateAmbient<def>;
run: (args: inferAmbient<def>) => void;
}) {}

defineCommand({
arguments: {
first: "boolean",
default: "number = 123",
number: ["string", "=>", parseFloat],
"optional?": "string",
},
run(args) {
args;
// ^?
// (parameter) args: {
// first: boolean;
// default: (In?: number | undefined) => Default<123>;
// number: (In: string) => Out<number>;
// optional?: string | undefined;
// }
},
});
Dimava
Dimavaβ€’3mo ago
Type<inferAmbient<def>>.out or whatever the type()() return type is
Roamin
RoaminOPβ€’3mo ago
I changed it to run: (args: Type<inferAmbient<def>>["infer"]) => void; and it's working perfectly! Thanks for your help πŸ˜‰
Want results from more Discord servers?
Add your server