diff --git a/ark/docs/content/docs/expressions/index.mdx b/ark/docs/content/docs/expressions/index.mdx index 5989edffb..0375352e8 100644 --- a/ark/docs/content/docs/expressions/index.mdx +++ b/ark/docs/content/docs/expressions/index.mdx @@ -141,7 +141,7 @@ const sameError = type({ a: "string.numeric.parse" }).or({ b: "string.numeric.pa ```
- Learn the basic set theory behind this restriction + Learn the set theory behind this restriction If you're relatively new to set-based types, that error might be daunting, but if you take a second to think through the example, it becomes clear why this isn't allowed. The logic of `bad` is essentially: @@ -168,10 +168,13 @@ Add a type-only symbol to an existing type so that the only values that satisfy ```ts -// hover to see ArkType's representation of a branded type -const myObject = type({ - brandedString: "string#id" -}) +// @noErrors +const even = type("(number % 2)#even") +type Even = typeof even.infer + +const good: Even = even.assert(2) +// TypeScript: Type 'number' is not assignable to type 'Brand' +const bad: Even = 5 ``` @@ -179,16 +182,23 @@ const myObject = type({ ```ts -// hover to see ArkType's representation of a branded type -const myObject = type({ - brandedString: type.string.brand("id") -}) +// @noErrors +const even = type.number.divisibleBy(2).brand("even") +type Even = typeof even.infer + +const good: Even = even.assert(2) +// TypeScript: Type 'number' is not assignable to type 'Brand' +const bad: Even = 5 ``` +Brands can be a great way to represent constraints that fall outside the scope TypeScript, but remember they don't change anything about what is enforced at runtime! + +For more information on branding in general, check out [this excellent article](https://www.learningtypescript.com/articles/branded-types) from [Josh Goldberg](https://github.com/joshuakgoldberg). + ### Narrow Narrow expressions allow you to add custom validation logic and error messages. You can read more about them in [their intro section](/docs/intro/adding-constraints#narrow). diff --git a/ark/repo/scratch.ts b/ark/repo/scratch.ts index 8041a3dca..f98cc89ba 100644 --- a/ark/repo/scratch.ts +++ b/ark/repo/scratch.ts @@ -1,3 +1,3 @@ import { type } from "arktype" -type("string") +const even = type.number.divisibleBy(2).brand("even") diff --git a/ark/type/__tests__/brand.test.ts b/ark/type/__tests__/brand.test.ts index baecaff3c..307f8b6e4 100644 --- a/ark/type/__tests__/brand.test.ts +++ b/ark/type/__tests__/brand.test.ts @@ -70,4 +70,15 @@ contextualize(() => { attest(string.json).equals(fluent.json) attest(string.t) }) + + it("docs example", () => { + const fluent = type.number.divisibleBy(2).brand("even") + + attest>(fluent.t) + attest(fluent.inferIn) + attest>(fluent.infer) + + const string = type("(number % 2)#even") + attest(string).is(fluent) + }) }) diff --git a/ark/type/__tests__/match.test.ts b/ark/type/__tests__/match.test.ts index fe6d47cfc..04c1f1d19 100644 --- a/ark/type/__tests__/match.test.ts +++ b/ark/type/__tests__/match.test.ts @@ -1,31 +1,31 @@ -import { attest } from "@ark/attest" -import { match } from "arktype" - -it("cases only", () => { - const sizeOf = match({ - "string|Array": v => v.length, - number: v => v, - bigint: v => v - }).orThrow() - - attest(sizeOf("abc")).equals(3) - attest(sizeOf([1, 2, 3])).equals(3) - attest(sizeOf(5n)).equals(5n) -}) - -it("properly infers types of inputs/outputs", () => { - const matcher = match({ string: s => s, number: n => n }) - .when("boolean", b => b) - .orThrow() - - // properly infers the type of the output based on the input - attest(matcher("abc")).equals("abc") - attest(matcher(4)).equals(4) - attest(matcher(true)).equals(true) - - // and properly handles unions in the input type - attest(matcher(0 as string | number)) -}) +// import { attest } from "@ark/attest" +// import { match } from "arktype" + +// it("cases only", () => { +// const sizeOf = match({ +// "string|Array": v => v.length, +// number: v => v, +// bigint: v => v +// }).orThrow() + +// attest(sizeOf("abc")).equals(3) +// attest(sizeOf([1, 2, 3])).equals(3) +// attest(sizeOf(5n)).equals(5n) +// }) + +// it("properly infers types of inputs/outputs", () => { +// const matcher = match({ string: s => s, number: n => n }) +// .when("boolean", b => b) +// .orThrow() + +// // properly infers the type of the output based on the input +// attest(matcher("abc")).equals("abc") +// attest(matcher(4)).equals(4) +// attest(matcher(true)).equals(true) + +// // and properly handles unions in the input type +// attest(matcher(0 as string | number)) +// }) // describe('"finalizations"', () => { // it(".orThrow()", () => { diff --git a/ark/type/index.ts b/ark/type/index.ts index 1a8bf4d45..cd4f6843b 100644 --- a/ark/type/index.ts +++ b/ark/type/index.ts @@ -15,7 +15,6 @@ export { define, generic, keywords, - match, type, type Ark } from "./keywords/keywords.ts" diff --git a/ark/type/keywords/keywords.ts b/ark/type/keywords/keywords.ts index 8fbfe1e67..52ba513a5 100644 --- a/ark/type/keywords/keywords.ts +++ b/ark/type/keywords/keywords.ts @@ -2,7 +2,6 @@ import type { ArkErrors, arkKind } from "@ark/schema" import type { Brand, inferred } from "@ark/util" import type { distill, InferredMorph, Out, To } from "../attributes.ts" import type { GenericParser } from "../generic.ts" -import type { MatchParser } from "../match.ts" import type { BaseType } from "../methods/base.ts" import type { BoundModule, Module } from "../module.ts" import type { @@ -129,7 +128,7 @@ export declare namespace type { export type type = Type -export const match: MatchParser<{}> = ark.match as never +// export const match: MatchParser<{}> = ark.match as never export const generic: GenericParser<{}> = ark.generic as never diff --git a/ark/type/scope.ts b/ark/type/scope.ts index dc595426d..6d45efb4c 100644 --- a/ark/type/scope.ts +++ b/ark/type/scope.ts @@ -52,7 +52,7 @@ import { type parseValidGenericParams } from "./generic.ts" import type { Ark, type } from "./keywords/keywords.ts" -import { InternalMatchParser, type MatchParser } from "./match.ts" +import { InternalMatchParser } from "./match.ts" import type { BoundModule, Module, @@ -362,7 +362,7 @@ export interface Scope<$ = {}> { type: TypeParser<$> - match: MatchParser<$> + // match: MatchParser<$> declare: DeclarationParser<$> diff --git a/ark/util/errors.ts b/ark/util/errors.ts index b3d5b9d83..b11f707a8 100644 --- a/ark/util/errors.ts +++ b/ark/util/errors.ts @@ -1,4 +1,4 @@ -import type { keyNonimal } from "./generics.ts" +import type { brand } from "./generics.ts" export class InternalArktypeError extends Error {} @@ -48,7 +48,7 @@ export interface ErrorType< message extends string = string, ctx extends {} = {} > { - [keyNonimal]: "ErrorObject" + [brand]: "ErrorObject" message: message ctx: ctx } diff --git a/ark/util/generics.ts b/ark/util/generics.ts index 349ef35ff..c41a7a31d 100644 --- a/ark/util/generics.ts +++ b/ark/util/generics.ts @@ -64,13 +64,10 @@ export type exactEquals = (<_>() => _ extends l ? 1 : 2) extends <_>() => _ extends r ? 1 : 2 ? true : false -/** You can avoid suggesting completions by prefixing a string key with whitespace. - * Isn't that keyNominal? - */ -export const keyNonimal = " keyNonimal" +export const brand = noSuggest("brand") export type Brand = t & { - readonly [keyNonimal]: [t, id] + readonly [brand]: [t, id] } export type unbrand = t extends Brand ? base : never