import type { BaseNode, BaseRoot, Disjoint, JsonSchema, NodeSelector, Predicate, StandardSchemaV1, ToJsonSchema, TypeMeta, UndeclaredKeyBehavior } from "@ark/schema"; import type { anyOrNever, array, Callable, ErrorMessage, inferred, JsonStructure, unset } from "@ark/util"; import type { defaultFor, distill, inferIntersection, inferPipe, InferredMorph, Out, To } from "../attributes.ts"; import type { ArkAmbient } from "../config.ts"; import type { type } from "../keywords/keywords.ts"; import type { NaryPipeParser } from "../nary.ts"; import type { Scope } from "../scope.ts"; import type { ArrayType } from "./array.ts"; import type { instantiateType } from "./instantiate.ts"; /** @ts-ignore cast variance */ export interface Inferred { internal: BaseRoot; [inferred]: t; /** * precompiled JS used to optimize validation * * ⚠️ will be `undefined` in [jitless](https://arktype.io/docs/configuration#jitless) mode */ precompilation: string | undefined; /** * generic parameter representing this Type * * 🥸 inference-only property that will be `undefined` at runtime * * ⚠️ May contain types representing morphs or default values that would * be inaccurate if used directly for runtime values. In those cases, * you should use {@link infer} or {@link inferIn} on this object instead. */ t: t; /** * #### {@link Scope} in which chained methods are parsed */ $: Scope<$>; /** * #### type of output this returns * * 🥸 inference-only property that will be `undefined` at runtime * * @example * const parseNumber = type("string").pipe(s => Number.parseInt(s)) * type ParsedNumber = typeof parseNumber.infer // number */ infer: this["inferOut"]; /** * type of output this returns * * 🔗 alias of {@link infer} * 🥸 inference-only property that will be `undefined` at runtime * * * @example * const parseNumber = type("string").pipe(s => Number.parseInt(s)) * type ParsedNumber = typeof parseNumber.infer // number */ inferOut: distill.Out; /** * type of output that can be introspected at runtime (e.g. via {@link out}) * * ⚠️ If your Type contains morphs, they will be inferred as `unknown` unless * they are an ArkType keyword or have an explicitly defined output validator. * * 🥸 inference-only property that will be `undefined` at runtime * * @example * const Unmorphed = type("string") * // with no morphs, we can introspect the input and output as a single Type * type UnmorphedOut = typeof Unmorphed.inferIntrospectableOut // string * * const Morphed = type("string").pipe(s => s.length) * // with a standard user-defined morph, TypeScript can infer a * // return type from your function, but we have no way to * // know the shape at runtime * type MorphOut = typeof Morphed.inferIntrospectableOut // unknown * * const Validated = type("string").pipe(s => s.length).to("number") * // morphs with validated output, including all morph keywords, are introspectable * type ValidatedMorphOut = typeof Validated.inferIntrospectableOut */ inferIntrospectableOut: distill.introspectable.Out; /** * #### type of input this allows * * 🥸 inference-only property that will be `undefined` at runtime * * @example * const parseNumber = type("string").pipe(s => Number.parseInt(s)) * type UnparsedNumber = typeof parseNumber.inferIn // string */ inferIn: distill.In; /** * #### internal JSON representation */ json: JsonStructure; /** * alias of {@link json} for `JSON.stringify` compatibility */ toJSON(): JsonStructure; /** * #### generate a JSON Schema * * @throws {ToJsonSchema.Error} if this cannot be converted to JSON Schema */ toJsonSchema(options?: ToJsonSchema.Options): JsonSchema; /** * #### metadata like custom descriptions and error messages * * ✅ type {@link https://arktype.io/docs/configuration#custom | can be customized} for your project */ meta: ArkAmbient.meta; /** * #### human-readable English description * * ✅ works best for primitive values * * @example * const N = type("0 < number <= 100") * console.log(N.description) // positive and at most 100 */ description: string; /** * #### syntax string similar to native TypeScript * * ✅ works well for both primitives and structures * * @example * const Loc = type({ coords: ["number", "number"] }) * console.log(Loc.expression) // { coords: [number, number] } */ expression: string; /** * #### validate and return transformed data or throw * * ✅ sugar to avoid checking for {@link type.errors} if they are unrecoverable * * @example * const CriticalPayload = type({ * superImportantValue: "string" * }) * // throws TraversalError: superImportantValue must be a string (was missing) * const data = CriticalPayload.assert({ irrelevantValue: "whoops" }) * console.log(data.superImportantValue) // valid output can be accessed directly * * @throws {TraversalError} */ assert: (data: unknown) => this["infer"]; /** * #### check input without applying morphs * * ✅ good for stuff like filtering that doesn't benefit from detailed errors * * @example * const Numeric = type("number | bigint") * // [0, 2n] * const numerics = [0, "one", 2n].filter(Numeric.allows) */ allows: (data: unknown) => data is this["inferIn"]; /** * #### add metadata to shallow references * * ⚠️ does not affect error messages within properties of an object * * @example * const NotOdd = type("number % 2").configure({ description: "not odd" }) * // all constraints at the root are affected * const odd = NotOdd(3) // must be not odd (was 3) * const nonNumber = NotOdd("two") // must be not odd (was "two") * * const NotOddBox = type({ * // we should have referenced notOdd or added meta here * notOdd: "number % 2", * // but instead chained from the root object * }).configure({ description: "not odd" }) * // error message at path notOdd is not affected * const oddProp = NotOddBox({ notOdd: 3 }) // notOdd must be even (was 3) * // error message at root is affected, leading to a misleading description * const nonObject = NotOddBox(null) // must be not odd (was null) */ configure: NodeSelector.SelectableFn; /** * #### add description to shallow references * * 🔗 equivalent to `.configure({ description })` (see {@link configure}) * ⚠️ does not affect error messages within properties of an object * * @example * const AToZ = type(/^a.*z$/).describe("a string like 'a...z'") * const good = AToZ("alcatraz") // "alcatraz" * // ArkErrors: must be a string like 'a...z' (was "albatross") * const badPattern = AToZ("albatross") */ describe: NodeSelector.SelectableFn; /** * #### apply undeclared key behavior * * - `"ignore"` (default) - allow and preserve extra properties * - `"reject"` - disallow extra properties * - `"delete"` - clone and remove extra properties from output */ onUndeclaredKey(behavior: UndeclaredKeyBehavior): this; /** * #### deeply apply undeclared key behavior * * - `"ignore"` (default) - allow and preserve extra properties * - `"reject"` - disallow extra properties * - `"delete"` - clone and remove extra properties from output */ onDeepUndeclaredKey(behavior: UndeclaredKeyBehavior): this; /** * #### alias for {@link assert} with typed input * * @example * const T = type({ foo: "string" }); * // TypeScript: foo must be a string (was 5) * const data = T.from({ foo: 5 }); */ from(literal: this["inferIn"]): this["infer"]; /** * #### deeply extract inputs * * ✅ will never include morphs * ✅ good for generating JSON Schema or other non-transforming formats * * @example * const User = type({ * age: "string.numeric.parse" * }) * // { age: 25 } (age parsed to a number) * const out = User({ age: "25" }) * // { age: "25" } (age is still a string) * const inOut = User.in({ age: "25" }) */ get in(): instantiateType; /** * #### deeply extract outputs * * ✅ will never include morphs * ⚠️ if your type includes morphs, their output will likely be unknown unless they * were defined with an explicit output validator via `.to(outputDef)` or `.pipe(morph, outputType)` * * @example * const join = type("string[]").pipe(a => a.join(",")) * * const T = type({ * // all keywords have introspectable output * keyword: "string.numeric.parse", * // TypeScript knows this returns a string, but we can't introspect that at runtime * unvalidated: join, * // if needed, it can be made introspectable with an output validator * validated: join.to("string") * }) * * // Type<{ keyword: number; unvalidated: unknown; validated: string }> * const baseOut = base.out */ get out(): instantiateType; /** * #### add a compile-time brand to output * * 🥸 inference-only function that does nothing runtime * * @example * const Palindrome = type("string") * .narrow(s => s === [...s].reverse().join("")) * .brand("palindrome") * // Brand * const out = Palindrome.assert("racecar") */ brand, $>>(name: name): r extends infer _ ? _ : never; /** * #### an array of this * * @example * // Type<{ rebmun: number }[]> * const T = type({ rebmun: "number" }).array(); */ array(): ArrayType; /** * #### {@link https://arktype.io/docs/objects#properties-optional | optional definition} * * ⚠️ unlike most other methods, this creates a definition rather than a Type (read why) * * @example * const Prop = type({ foo: "number" }) * // Type<{ bar?: { foo: number } }> * const Obj = type({ bar: Prop.optional() }) */ optional(): [this, "?"]; /** * #### {@link https://arktype.io/docs/objects#properties-defaultable | defaultable definition} * * ✅ object defaults can be returned from a function * ⚠️ throws if the default value is not allowed * ⚠️ unlike most other methods, this creates a definition rather than a Type (read why) * * @example * // Type<{ count: Default }> * const State = type({ count: type.number.default(0) }) * const Prop = type({ nested: "boolean" }) * const ForObj = type({ * key: Prop.default(() => ({ nested: false })) * }) */ default>(value: value): [this, "=", value]; /** * #### apply a predicate function to input * * ⚠️ the behavior of {@link narrow}, this method's output counterpart, is usually more desirable * ✅ most useful for morphs with input types that are re-used externally * 🥸 {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates | Type predicates} can be used as casts * * @example * const stringifyUser = type({ name: "string" }).pipe(user => JSON.stringify(user)) * const stringifySafe = stringifyUser.filter(user => user.name !== "Bobby Tables") * // Type<(In: `${string}Z`) => To> * const WithPredicate = type("string.date.parse").filter((s): s is `${string}Z` => * s.endsWith("Z") * ) */ filter ? (In: narrowed) => o : narrowed, $>>(predicate: Predicate.Castable): r extends infer _ ? _ : never; /** * #### apply a predicate function to output * * ✅ go-to fallback for validation not composable via built-in types and operators * ✅ runs after all other validators and morphs, if present * 🥸 {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates | Type predicates} can be used as casts * * @example * const Palindrome = type("string").narrow(s => s === [...s].reverse().join("")) * * const PalindromicEmail = type("string.date.parse").narrow((date, ctx) => * date.getFullYear() === 2025 || ctx.mustBe("the current year") * ) * // Type<`${string}.tsx`> * const WithPredicate = type("string").narrow((s): s is `${string}.tsx` => /\.tsx?$/.test(s)) */ narrow ? o extends To ? (In: i) => To : (In: i) => Out : narrowed, $>>(predicate: Predicate.Castable): r extends infer _ ? _ : never; /** * #### pipe output through arbitrary transformations or other Types * * @example * const User = type({ name: "string" }) * * // parse a string and validate that the result as a user * const parseUser = type("string").pipe(s => JSON.parse(s), user) */ pipe: ChainedPipeParser<$, t>; /** * #### parse a definition as an output validator * * 🔗 `to({ name: "string" })` is equivalent to `.pipe(type({ name: "string" }))` * * @example * // parse a string and validate that the result as a user * const parseUser = type("string").pipe(s => JSON.parse(s)).to({ name: "string" }) */ to>, $>>(def: type.validate): r extends infer _ ? _ : never; /** * #### query internal node references * * @experimental filters and returns the Type's internal representation from `@ark/schema` * * @example * // ["blue", "red"] * const values = type("'red' | 'blue'").select("unit").map(u => u.unit) */ select: BaseNode["select"]; } /** @ts-ignore cast variance */ interface Type extends Callable<(data: unknown) => distill.Out | ArkEnv.onFail>, Inferred { /** * #### cast the way this is inferred * * 🥸 inference-only function that does nothing runtime * * @example * // Type<`LEEEEEEEE${string}ROY`> * const Leeroy = type(/^LE{8,}ROY$/).as<`LEEEEEEEE${string}ROY`>() */ as(...args: validateChainedAsArgs): instantiateType; /** * #### intersect the parsed Type, throwing if the result is unsatisfiable * * @example * // Type<{ foo: number; bar: string }> * const T = type({ foo: "number" }).and({ bar: "string" }) * // ParseError: Intersection at foo of number and string results in an unsatisfiable type * const Bad = type({ foo: "number" }).and({ foo: "string" }) */ and>, $>>(def: type.validate): r extends infer _ ? _ : never; /** * #### union with the parsed Type * * ⚠️ a union that could apply different morphs to the same data is a ParseError ({@link https://arktype.io/docs/expressions#union-morphs | docs}) * * @example * // Type * const T = type("string").or({ box: "string" }) */ or, $>>(def: type.validate): r extends infer _ ? _ : never; /** * #### intersect the parsed Type, returning an introspectable {@link Disjoint} if the result is unsatisfiable * * @example * // Type<{ foo: number; bar: string }> * const T = type({ foo: "number" }).intersect({ bar: "string" }) * const Bad = type("number > 10").intersect("number < 5") * // logs "Intersection of > 10 and < 5 results in an unsatisfiable type" * if (Bad instanceof Disjoint) console.log(`${bad.summary}`) */ intersect>, $>>(def: type.validate): r extends infer _ ? _ | Disjoint : never; /** * #### check if the parsed Type's constraints are identical * * ✅ equal types have identical input and output constraints and transforms * ✅ ignores associated {@link meta}, which does not affect the set of allowed values * * @example * const DivisibleBy6 = type.number.divisibleBy(6).moreThan(0) * // false (left side must also be positive) * DivisibleBy6.equals("number % 6") * // false (right side has an additional <100 constraint) * console.log(DivisibleBy6.equals("0 < (number % 6) < 100")) * const ThirdTry = type("(number % 2) > 0").divisibleBy(3) * // true (types are normalized and reduced) * console.log(DivisibleBy6.equals(ThirdTry)) */ equals(def: type.validate): boolean; /** * #### narrow this based on an {@link equals} check * * ✅ ignores associated {@link meta}, which does not affect the set of allowed values * * @example * const N = type.raw(`${Math.random()}`) * // Type<0.5> | undefined * const Ez = N.ifEquals("0.5") */ ifEquals>(def: type.validate): r extends infer _ ? _ | undefined : never; /** * #### check if this is a subtype of the parsed Type * * ✅ a subtype must include all constraints from the base type * ✅ unlike {@link equals}, additional constraints may be present * ✅ ignores associated {@link meta}, which does not affect the set of allowed values * * @example * type.string.extends("unknown") // true * type.string.extends(/^a.*z$/) // false */ extends(other: type.validate): boolean; /** * #### narrow this based on an {@link extends} check * * ✅ ignores associated {@link meta}, which does not affect the set of allowed values * * @example * const N = type(Math.random() > 0.5 ? "true" : "0") // Type<0 | true> * const Ez = N.ifExtends("boolean") // Type | undefined */ ifExtends>(other: type.validate): r extends infer _ ? _ | undefined : never; /** * #### check if a value could satisfy this and the parsed Type * * ⚠️ will return true unless a {@link Disjoint} can be proven * * @example * type.string.overlaps("string | number") // true (e.g. "foo") * type("string | number").overlaps("1") // true (1) * type("number > 0").overlaps("number < 0") // false (no values exist) * * const NoAt = type("string").narrow(s => !s.includes("@")) * NoAt.overlaps("string.email") // true (no values exist, but not provable) */ overlaps(r: type.validate): boolean; /** * #### extract branches {@link extend}ing the parsed Type * * @example * // Type * const T = type("boolean | 0 | 'one' | 2 | bigint").extract("number | 0n | true") */ extract ? t : never, $>>(r: type.validate): r extends infer _ extends r ? _ : never; /** * #### exclude branches {@link extend}ing the parsed Type * * @example * * // Type * const T = type("boolean | 0 | 'one' | 2 | bigint").exclude("number | 0n | true") */ exclude ? never : t, $>>(r: type.validate): r extends infer _ ? _ : never; /** * @experimental * Map and optionally reduce branches of a union. Types that are not unions * are treated as a single branch. * * @param mapBranch - the mapping function, accepting a branch Type * Returning another `Type` is common, but any value can be returned and * inferred as part of the output. * * @param [reduceMapped] - an operation to perform on the mapped branches * Can be used to e.g. merge an array of returned Types representing * branches back to a single union. */ distribute(mapBranch: (branch: Type, i: number, branches: array) => mapOut, reduceMapped?: (mappedBranches: mapOut[]) => reduceOut): reduceOut; /** The Type's [StandardSchema](https://github.com/standard-schema/standard-schema) properties */ "~standard": StandardSchemaV1.ArkTypeProps; /** @deprecated */ apply: Function["apply"]; /** @deprecated */ bind: Function["bind"]; /** @deprecated */ call: Function["call"]; /** @deprecated */ caller: Function; /** @deprecated */ length: number; /** @deprecated */ name: string; /** @deprecated */ prototype: Function["prototype"]; /** @deprecated */ arguments: Function["arguments"]; /** @deprecated */ Symbol: never; } export interface ChainedPipeParser<$, t> extends NaryPipeParser<$, t> { try: NaryPipeParser<$, t>; } type validateChainedAsArgs = [ t ] extends [unset] ? [ t ] extends [anyOrNever] ? [ ] : [ ErrorMessage<"as requires an explicit type parameter like myType.as()"> ] : []; export type { Type as BaseType };