Something went wrong. Try again.
Yet another Fluxer bot built with TypeScript and Bun
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302import type { StandardSchemaV1 } from "@standard-schema/spec";
// ---------------------------------------------------------------------------// Optional wrapper for external Standard Schema schemas//// External schemas (Zod, Valibot, etc.) are treated as required by default.// Wrap with arg.optional(schema) to make them optional.// ---------------------------------------------------------------------------
export interface OptionalStandardSchema<S extends StandardSchemaV1> { readonly _tag: "optional"; readonly schema: S; readonly _rest?: true;}
// ---------------------------------------------------------------------------// Our native ArgDef — implements StandardSchemaV1 so it's interoperable// with any tooling that accepts Standard Schema.//// Input is always `string` (a raw positional arg from the message).// Output is whatever the parser produces.// ---------------------------------------------------------------------------
export interface ArgDef<T> extends StandardSchemaV1<string, T> { readonly required: true; optional(): OptionalArgDef<T>;}
export interface OptionalArgDef<T> extends StandardSchemaV1<string, T | undefined> { readonly required: false;}
// ---------------------------------------------------------------------------// What can appear as a value in an arg schema// ---------------------------------------------------------------------------
export type ArgEntry = | ArgDef<unknown> | OptionalArgDef<unknown> | StandardSchemaV1 // any external schema — required by default | OptionalStandardSchema<StandardSchemaV1>; // external schema wrapped as optional
export type ArgSchema = Record<string, ArgEntry>;
// Infer the parsed output type for a single entrytype InferEntry<E extends ArgEntry> = E extends OptionalStandardSchema<infer S> ? StandardSchemaV1.InferOutput<S> | undefined : E extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<E> : never;
// Infer the fully parsed object type from a schemaexport type ParsedArgs<S extends ArgSchema> = { [K in keyof S]: InferEntry<S[K]>;};
// ---------------------------------------------------------------------------// Internal helpers// ---------------------------------------------------------------------------
function makeStandardProps<T>( required: boolean, parser: (raw: string) => T,): StandardSchemaV1<string, T>["~standard"] { return { version: 1 as const, vendor: "hydro-args", validate(value: unknown) { if (value === undefined || value === "") { if (required) { return { issues: [{ message: "Missing required argument" }] }; } return { value: undefined as T }; } try { return { value: parser(value as string) }; } catch (e) { return { issues: [{ message: e instanceof Error ? e.message : String(e) }] }; } }, types: undefined, };}
function makeOptional<T>(parser: (raw: string) => T): OptionalArgDef<T> { return { required: false as const, "~standard": makeStandardProps(false, parser) as StandardSchemaV1< string, T | undefined >["~standard"], };}
function makeRequired<T>(parser: (raw: string) => T): ArgDef<T> { return { required: true as const, "~standard": makeStandardProps(true, parser), optional(): OptionalArgDef<T> { return makeOptional(parser); }, };}
// ---------------------------------------------------------------------------// Arg factories — the public API// ---------------------------------------------------------------------------
export const arg = { /** Raw string — returned as-is */ string(): ArgDef<string> { return makeRequired((raw) => raw); },
/** Parsed as a float — throws if not a valid number */ number(): ArgDef<number> { return makeRequired((raw) => { const n = Number(raw); if (Number.isNaN(n)) throw new Error(`Expected a number, got "${raw}"`); return n; }); },
/** Parses "true"/"yes"/"1" as true, "false"/"no"/"0" as false */ boolean(): ArgDef<boolean> { return makeRequired((raw) => { const lower = raw.toLowerCase(); if (lower === "true" || lower === "yes" || lower === "1") return true; if (lower === "false" || lower === "no" || lower === "0") return false; throw new Error(`Expected a boolean (true/false/yes/no/1/0), got "${raw}"`); }); },
/** * Greedily consumes all remaining positional args into a single string. * Must be the last entry in the schema. * !ban @user being rude in chat → reason = "being rude in chat" */ rest(): ArgDef<string> & { readonly _rest: true } { return { ...makeRequired((raw) => raw), _rest: true as const }; },
/** * Wraps any Standard Schema compatible schema (Zod, Valibot, ArkType, etc.) * as an optional arg. Without this wrapper, external schemas are required. * * @example * const schema = { * tag: z.string().regex(/^[a-z]+$/), // required, validated by Zod * note: arg.optional(z.string().max(100)), // optional, validated by Zod * }; */ optional<S extends StandardSchemaV1>(schema: S): OptionalStandardSchema<S> { return { _tag: "optional", schema }; },
/** * Like arg.optional(schema), but greedily consumes all remaining tokens * as a single string before passing to the schema. Must be the last entry. * * @example * const schema = { * target: arg.string(), * reason: arg.restOptional(z.string().min(1).max(500)), // "Being rude in chat" * }; */ restOptional<S extends StandardSchemaV1>(schema: S): OptionalStandardSchema<S> { return { _tag: "optional", schema, _rest: true as const }; },
/** * Like a required external schema arg, but greedily consumes all remaining * tokens as a single string before passing to the schema. Must be the last entry. * * Use this when you want Zod (or another library) to validate a required * multi-word value, e.g. a message that must be at least 1 character. * * @example * const schema = { * content: arg.restRequired(z.string().min(1)), // "Hello world from chat" * }; */ restRequired<S extends StandardSchemaV1>(schema: S): S & { readonly _rest: true } { return { ...schema, _rest: true as const }; },} as const;
// ---------------------------------------------------------------------------// Type guards// ---------------------------------------------------------------------------
function isOptionalWrapper(entry: ArgEntry): entry is OptionalStandardSchema<StandardSchemaV1> { return "_tag" in entry && (entry as OptionalStandardSchema<StandardSchemaV1>)._tag === "optional";}
function isNativeArgDef(entry: ArgEntry): entry is ArgDef<unknown> | OptionalArgDef<unknown> { return "required" in entry;}
function isRest(entry: ArgEntry): boolean { return ( "_rest" in entry && (entry as { _rest?: boolean })._rest === true && !isOptionalWrapper(entry) );}
function isRestOptionalWrapper( entry: ArgEntry,): entry is OptionalStandardSchema<StandardSchemaV1> & { _rest: true } { return isOptionalWrapper(entry) && entry._rest === true;}
// ---------------------------------------------------------------------------// Parser — called by the registry before execute()// ---------------------------------------------------------------------------
async function validateEntry( key: string, entry: ArgEntry, rawValue: string | undefined,): Promise<unknown> { // Optional wrapper (including restOptional) around an external schema if (isOptionalWrapper(entry) || isRestOptionalWrapper(entry)) { if (rawValue === undefined || rawValue === "") return undefined; return runValidate(key, entry.schema, rawValue); }
// Our native ArgDef / OptionalArgDef (also StandardSchemaV1) if (isNativeArgDef(entry)) { // Delegate to the ~standard.validate() we set up — it handles required/optional internally return runValidate(key, entry, rawValue ?? ""); }
// Plain external Standard Schema — treated as required if (rawValue === undefined || rawValue === "") { throw new Error(`Missing required argument: ${key}`); } return runValidate(key, entry, rawValue);}
async function runValidate( key: string, schema: StandardSchemaV1, value: unknown,): Promise<unknown> { const standard = schema["~standard"];
if (standard && typeof standard.validate === "function") { let result = standard.validate(value); if (result instanceof Promise) result = await result; if (result.issues) { const messages = result.issues.map((i) => i.message).join(", "); throw new Error(`Argument "${key}": ${messages}`); } return result.value; }
if ("safeParse" in schema && typeof schema.safeParse === "function") { const result = await schema.safeParse(value); if (!result.success) { const messages = (result as { error?: { issues?: Array<{ message: string }> } }).error?.issues ?.map((i) => i.message) .join(", "); throw new Error(`Argument "${key}": ${messages ?? "Validation failed"}`); } return (result as { data: unknown }).data; }
throw new Error(`Argument "${key}": Schema does not implement StandardSchemaV1 or safeParse`);}
export async function parseArgs<S extends ArgSchema>( schema: S, raw: string[],): Promise<ParsedArgs<S>> { const keys = Object.keys(schema); const result: Record<string, unknown> = {};
for (let i = 0; i < keys.length; i++) { const key = keys[i]!; const entry = schema[key]!; const isLastEntry = i === keys.length - 1;
let rawValue: string | undefined;
const isRestEntry = isRest(entry) || isRestOptionalWrapper(entry) || ("_rest" in entry && (entry as { _rest?: boolean })._rest === true); if (isRestEntry && isLastEntry) { // Consume all remaining tokens as one string rawValue = raw.slice(i).join(" ") || undefined; } else { rawValue = raw[i]; }
result[key] = await validateEntry(key, entry, rawValue); }
return result as ParsedArgs<S>;}