Comparing version 2.0.0-dev.1 to 2.0.0-dev.2
@@ -6,3 +6,3 @@ import { attest } from "@arktype/attest" | ||
import { | ||
prematureRestMessage, | ||
multipleVariadicMesage, | ||
writeNonArrayRestMessage | ||
@@ -13,10 +13,19 @@ } from "../parser/tuple.js" | ||
describe("base", () => { | ||
it("base", () => { | ||
it("allows and apply", () => { | ||
const t = type("string[]") | ||
attest<string[]>(t.infer) | ||
attest(t.allows([])).equals(true) | ||
attest(t([]).out).snap([]) | ||
attest(t.allows(["foo", "bar"])).equals(true) | ||
attest(t(["foo", "bar"]).out).snap(["foo", "bar"]) | ||
attest(t.allows(["foo", "bar", 5])).equals(false) | ||
attest(t(["foo", "bar", 5]).errors?.summary).snap( | ||
"Value at [2] must be a string (was number)" | ||
) | ||
attest(t.allows([5, "foo", "bar"])).equals(false) | ||
attest(t([5, "foo", "bar"]).errors?.summary).snap( | ||
"Value at [0] must be a string (was number)" | ||
) | ||
}) | ||
it("nested", () => { | ||
@@ -26,5 +35,13 @@ const t = type("string[][]") | ||
attest(t.allows([])).equals(true) | ||
attest(t([]).out).snap([]) | ||
attest(t.allows([["foo"]])).equals(true) | ||
attest(t([["foo"]]).out).snap([["foo"]]) | ||
attest(t.allows(["foo"])).equals(false) | ||
attest(t(["foo"]).errors?.summary).snap( | ||
"Value at [0] must be an array (was string)" | ||
) | ||
attest(t.allows([["foo", 5]])).equals(false) | ||
attest(t([["foo", 5]]).errors?.summary).snap( | ||
"Value at [0][1] must be a string (was number)" | ||
) | ||
}) | ||
@@ -37,2 +54,3 @@ | ||
}) | ||
describe("optional tuple literals", () => { | ||
@@ -98,8 +116,13 @@ it("string optional", () => { | ||
attest(t.allows(["", 0])).equals(true) | ||
attest(t(["", 0]).out).snap() | ||
attest(t.allows([true, 0])).equals(false) | ||
attest(t([true, 0]).errors?.summary).snap() | ||
attest(t.allows([0, false])).equals(false) | ||
attest(t([0, false]).errors?.summary).snap() | ||
// too short | ||
attest(t.allows([""])).equals(false) | ||
attest(t([""]).errors?.summary).snap() | ||
// too long | ||
attest(t.allows(["", 0, 1])).equals(false) | ||
attest(t(["", 0, 1]).errors?.summary).snap() | ||
// non-array | ||
@@ -113,3 +136,11 @@ attest( | ||
).equals(false) | ||
attest( | ||
t({ | ||
length: 2, | ||
0: "", | ||
1: 0 | ||
}).errors?.summary | ||
).snap() | ||
}) | ||
it("nested", () => { | ||
@@ -130,3 +161,10 @@ const t = type([["string", "number"], [{ a: "boolean", b: ["null"] }]]) | ||
attest(t.allows([["", 0], [{ a: true, b: [null] }]])).equals(true) | ||
attest(t([["foo", 1], [{ a: true, b: [null] }]]).out).snap([ | ||
["foo", 1], | ||
[{ a: true, b: [null] }] | ||
]) | ||
attest(t.allows([["", 0], [{ a: true, b: [undefined] }]])).equals(false) | ||
attest( | ||
t([["foo", 1], [{ a: true, b: [undefined] }]]).errors?.summary | ||
).snap("[1][0].b[0] must be null (was undefined)") | ||
}) | ||
@@ -181,7 +219,7 @@ }) | ||
type(["...number[]", "string"]) | ||
).throwsAndHasTypeError(prematureRestMessage) | ||
).throwsAndHasTypeError(multipleVariadicMesage) | ||
attest(() => | ||
// @ts-expect-error | ||
type([["...", "string[]"], "number"]) | ||
).throwsAndHasTypeError(prematureRestMessage) | ||
).throwsAndHasTypeError(multipleVariadicMesage) | ||
}) | ||
@@ -188,0 +226,0 @@ }) |
@@ -0,168 +1,77 @@ | ||
import { attest } from "@arktype/attest" | ||
import { scope, type } from "arktype" | ||
describe("config traversal", () => { | ||
// it("tuple expression", () => { | ||
// const mustBe = "a series of characters" | ||
// const types = scope({ | ||
// a: ["string", ":", { mustBe }], | ||
// b: { | ||
// a: "a" | ||
// } | ||
// }).compile() | ||
// attest<string>(types.a.infer) | ||
// // attest(types.a.flat).snap([ | ||
// // [ | ||
// // "config", | ||
// // { | ||
// // config: [["mustBe", "a series of characters"]], | ||
// // node: "string" | ||
// // } | ||
// // ] | ||
// // ]) | ||
// attest(types.a(1).errors?.summary).snap( | ||
// "Must be a series of characters (was number)" | ||
// ) | ||
// attest<{ a: string }>(types.b.infer) | ||
// // attest(types.b.flat).equals([ | ||
// // ["domain", "object"], | ||
// // [ | ||
// // "requiredProp", | ||
// // [ | ||
// // "a", | ||
// // [ | ||
// // [ | ||
// // "config", | ||
// // { | ||
// // config: [["mustBe", mustBe]], | ||
// // node: "string" | ||
// // } | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ]) | ||
// attest(types.b({ a: true }).errors?.summary).snap( | ||
// "a must be a series of characters (was boolean)" | ||
// ) | ||
// }) | ||
// it("tuple expression at path", () => { | ||
// const t = type({ | ||
// monster: [ | ||
// "196883", | ||
// ":", | ||
// { | ||
// mustBe: "the number of dimensions in the monster group" | ||
// } | ||
// ] | ||
// }) | ||
// attest<{ monster: 196883 }>(t.infer) | ||
// // attest(t.node).snap({ | ||
// // object: { | ||
// // props: { | ||
// // monster: { | ||
// // node: { number: { value: 196883 } }, | ||
// // config: { | ||
// // mustBe: "the number of dimensions in the monster group" | ||
// // } | ||
// // } | ||
// // } | ||
// // } | ||
// // }) | ||
// // attest(t.flat).snap([ | ||
// // ["domain", "object"], | ||
// // [ | ||
// // "requiredProp", | ||
// // [ | ||
// // "monster", | ||
// // [ | ||
// // [ | ||
// // "config", | ||
// // { | ||
// // config: [ | ||
// // [ | ||
// // "mustBe", | ||
// // "the number of dimensions in the monster group" | ||
// // ] | ||
// // ], | ||
// // node: [["value", 196883]] | ||
// // } | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ]) | ||
// attest(t({ monster: 196882 }).errors?.summary).snap( | ||
// "monster must be the number of dimensions in the monster group (was 196882)" | ||
// ) | ||
// }) | ||
// it("anonymous type config", () => { | ||
// const t = type(type("true", { mustBe: "unfalse" })) | ||
// attest<true>(t.infer) | ||
// // attest(t.flat).snap([ | ||
// // [ | ||
// // "config", | ||
// // { config: [["mustBe", "unfalse"]], node: [["value", true]] } | ||
// // ] | ||
// // ]) | ||
// attest(t(false).errors?.summary).snap("Must be unfalse (was false)") | ||
// }) | ||
// it("anonymous type config at path", () => { | ||
// const unfalse = type("true", { mustBe: "unfalse" }) | ||
// const t = type({ myKey: unfalse }) | ||
// // attest(t.flat).snap([ | ||
// // ["domain", "object"], | ||
// // [ | ||
// // "requiredProp", | ||
// // [ | ||
// // "myKey", | ||
// // [ | ||
// // [ | ||
// // "config", | ||
// // { | ||
// // config: [["mustBe", "unfalse"]], | ||
// // node: [["value", true]] | ||
// // } | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ]) | ||
// attest(t({ myKey: "500" }).errors?.summary).snap( | ||
// "myKey must be unfalse (was '500')" | ||
// ) | ||
// // config only applies within myKey | ||
// attest(t({ yourKey: "500" }).errors?.summary).snap( | ||
// "myKey must be defined" | ||
// ) | ||
// }) | ||
// it("anonymous type thunk", () => { | ||
// const t = type(() => type("false", { mustBe: "untrue" })) | ||
// attest<false>(t.infer) | ||
// // attest(t.flat).snap([ | ||
// // [ | ||
// // "config", | ||
// // { config: [["mustBe", "untrue"]], node: [["value", false]] } | ||
// // ] | ||
// // ]) | ||
// }) | ||
// it("anonymous type thunk at path", () => { | ||
// const t = type({ myKey: () => type("false", { mustBe: "untrue" }) }) | ||
// attest<{ myKey: false }>(t.infer) | ||
// // attest(t.flat).snap([ | ||
// // ["domain", "object"], | ||
// // [ | ||
// // "requiredProp", | ||
// // [ | ||
// // "myKey", | ||
// // [ | ||
// // [ | ||
// // "config", | ||
// // { | ||
// // config: [["mustBe", "untrue"]], | ||
// // node: [["value", false]] | ||
// // } | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ] | ||
// // ]) | ||
// }) | ||
it("tuple expression", () => { | ||
const description = "a series of characters" | ||
const types = scope({ | ||
a: ["string", "@", description], | ||
b: { | ||
a: "a" | ||
} | ||
}).export() | ||
attest<string>(types.a.infer) | ||
attest(types.a.description).equals(description) | ||
attest(types.a(1).errors?.summary).snap( | ||
"Must be a series of characters (was number)" | ||
) | ||
attest<{ a: string }>(types.b.infer) | ||
attest(types.b({ a: true }).errors?.summary).snap( | ||
"a must be a series of characters (was boolean)" | ||
) | ||
}) | ||
it("tuple expression at path", () => { | ||
const description = "the number of dimensions in the monster group" | ||
const t = type({ | ||
monster: ["196883", "@", description] | ||
}) | ||
attest<{ monster: 196883 }>(t.infer) | ||
attest(t.description).equals(description) | ||
attest(t({ monster: 196882 }).errors?.summary).snap( | ||
"monster must be the number of dimensions in the monster group (was 196882)" | ||
) | ||
}) | ||
it("anonymous type config", () => { | ||
const t = type(type("true", "@", { description: "unfalse" })) | ||
attest<true>(t.infer) | ||
attest(t(false).errors?.summary).snap("Must be unfalse (was false)") | ||
}) | ||
it("anonymous type config at path", () => { | ||
const unfalse = type("true", "@", { description: "unfalse" }) | ||
const t = type({ myKey: unfalse }) | ||
attest(t({ myKey: "500" }).errors?.summary).snap( | ||
`myKey must be unfalse (was "500")` | ||
) | ||
}) | ||
it("anonymous type thunk", () => { | ||
const t = type(() => type("false", "@", { description: "untrue" })) | ||
attest<false>(t.infer) | ||
attest(t.description).snap("untrue") | ||
}) | ||
it("anonymous type thunk at path", () => { | ||
const t = type({ | ||
myKey: () => type("false", "@", { description: "untrue" }) | ||
}) | ||
attest<{ myKey: false }>(t.infer) | ||
attest(t({ myKey: true }).errors?.summary).snap( | ||
"myKey must be untrue (was true)" | ||
) | ||
}) | ||
it("applies config to shallow descendants", () => { | ||
const user = type({ | ||
name: "string", | ||
age: "number" | ||
}).describe("a valid user") | ||
// should give the original error at a path | ||
attest( | ||
user({ | ||
name: "david", | ||
age: true | ||
}).errors?.summary | ||
).snap("age must be a number (was boolean)") | ||
// should give the shallow custom error | ||
attest(user(null).errors?.summary).snap("Must be a valid user (was null)") | ||
}) | ||
}) |
@@ -54,6 +54,7 @@ import { attest } from "@arktype/attest" | ||
it("regexp", () => { | ||
const t = declare<string>().type(/.*/) | ||
attest<string>(t.infer) | ||
}) | ||
// TODO: figure out narrowing | ||
// it("regexp", () => { | ||
// const t = declare<string>().type(/.*/) | ||
// attest<string>(t.infer) | ||
// }) | ||
@@ -60,0 +61,0 @@ it("Inferred<t>", () => { |
import { attest } from "@arktype/attest" | ||
import { type } from "arktype" | ||
describe("key traversal", () => { | ||
const getExtraneousB = () => ({ a: "ok", b: "why?" }) | ||
it("loose by default", () => { | ||
const t = type({ | ||
a: "string" | ||
}) | ||
const dataWithExtraneousB = getExtraneousB() | ||
attest(t(dataWithExtraneousB).out).equals(dataWithExtraneousB) | ||
}) | ||
it("invalid union", () => { | ||
const o = type([{ a: "string" }, "|", { b: "boolean" }]).configure({ | ||
keys: "strict" | ||
}) | ||
attest(o({ a: 2 }).errors?.summary).snap( | ||
'a must be a string or removed (was {"a":2})' | ||
) | ||
}) | ||
it("distilled type", () => { | ||
const t = type({ | ||
a: "string" | ||
}).configure({ keys: "distilled" }) | ||
attest(t({ a: "ok" }).out).equals({ a: "ok" }) | ||
attest(t(getExtraneousB()).out).snap({ a: "ok" }) | ||
}) | ||
it("distilled array", () => { | ||
const o = type({ a: "email[]" }).configure({ | ||
keys: "distilled" | ||
}) | ||
attest(o({ a: ["shawn@arktype.io"] }).out).snap({ | ||
a: ["shawn@arktype.io"] | ||
}) | ||
attest(o({ a: ["notAnEmail"] }).errors?.summary).snap( | ||
"a/0 must be a valid email (was 'notAnEmail')" | ||
) | ||
// can handle missing keys | ||
attest(o({ b: ["shawn"] }).errors?.summary).snap("a must be defined") | ||
}) | ||
it("distilled union", () => { | ||
const o = type([{ a: "string" }, "|", { b: "boolean" }]).configure({ | ||
keys: "distilled" | ||
}) | ||
// can distill to first branch | ||
attest(o({ a: "to", z: "bra" }).out).snap({ a: "to" }) | ||
// can distill to second branch | ||
attest(o({ b: true, c: false }).out).snap({ b: true }) | ||
// can handle missing keys | ||
attest(o({ a: 2 }).errors?.summary).snap( | ||
'a must be a string or b must be defined (was {"a":2})' | ||
) | ||
}) | ||
it("strict type", () => { | ||
const t = type({ | ||
a: "string" | ||
}).configure({ keys: "strict" }) | ||
attest(t({ a: "ok" }).out).equals({ a: "ok" }) | ||
attest(t(getExtraneousB()).errors?.summary).snap("b must be removed") | ||
}) | ||
it("strict array", () => { | ||
const o = type({ a: "string[]" }).configure({ | ||
keys: "strict" | ||
}) | ||
attest(o({ a: ["shawn"] }).out).snap({ a: ["shawn"] }) | ||
attest(o({ a: [2] }).errors?.summary).snap( | ||
"a/0 must be a string (was number)" | ||
) | ||
attest(o({ b: ["shawn"] }).errors?.summary).snap( | ||
"b must be removed\na must be defined" | ||
) | ||
}) | ||
}) | ||
// TODO: reenable | ||
// describe("key traversal", () => { | ||
// const getExtraneousB = () => ({ a: "ok", b: "why?" }) | ||
// it("loose by default", () => { | ||
// const t = type({ | ||
// a: "string" | ||
// }) | ||
// const dataWithExtraneousB = getExtraneousB() | ||
// attest(t(dataWithExtraneousB).out).equals(dataWithExtraneousB) | ||
// }) | ||
// it("invalid union", () => { | ||
// const o = type([{ a: "string" }, "|", { b: "boolean" }]).configure({ | ||
// keys: "strict" | ||
// }) | ||
// attest(o({ a: 2 }).errors?.summary).snap( | ||
// 'a must be a string or removed (was {"a":2})' | ||
// ) | ||
// }) | ||
// it("distilled type", () => { | ||
// const t = type({ | ||
// a: "string" | ||
// }).configure({ keys: "distilled" }) | ||
// attest(t({ a: "ok" }).out).equals({ a: "ok" }) | ||
// attest(t(getExtraneousB()).out).snap({ a: "ok" }) | ||
// }) | ||
// it("distilled array", () => { | ||
// const o = type({ a: "email[]" }).configure({ | ||
// keys: "distilled" | ||
// }) | ||
// attest(o({ a: ["shawn@arktype.io"] }).out).snap({ | ||
// a: ["shawn@arktype.io"] | ||
// }) | ||
// attest(o({ a: ["notAnEmail"] }).errors?.summary).snap( | ||
// "a/0 must be a valid email (was 'notAnEmail')" | ||
// ) | ||
// // can handle missing keys | ||
// attest(o({ b: ["shawn"] }).errors?.summary).snap("a must be defined") | ||
// }) | ||
// it("distilled union", () => { | ||
// const o = type([{ a: "string" }, "|", { b: "boolean" }]).configure({ | ||
// keys: "distilled" | ||
// }) | ||
// // can distill to first branch | ||
// attest(o({ a: "to", z: "bra" }).out).snap({ a: "to" }) | ||
// // can distill to second branch | ||
// attest(o({ b: true, c: false }).out).snap({ b: true }) | ||
// // can handle missing keys | ||
// attest(o({ a: 2 }).errors?.summary).snap( | ||
// 'a must be a string or b must be defined (was {"a":2})' | ||
// ) | ||
// }) | ||
// it("strict type", () => { | ||
// const t = type({ | ||
// a: "string" | ||
// }).configure({ keys: "strict" }) | ||
// attest(t({ a: "ok" }).out).equals({ a: "ok" }) | ||
// attest(t(getExtraneousB()).errors?.summary).snap("b must be removed") | ||
// }) | ||
// it("strict array", () => { | ||
// const o = type({ a: "string[]" }).configure({ | ||
// keys: "strict" | ||
// }) | ||
// attest(o({ a: ["shawn"] }).out).snap({ a: ["shawn"] }) | ||
// attest(o({ a: [2] }).errors?.summary).snap( | ||
// "a/0 must be a string (was number)" | ||
// ) | ||
// attest(o({ b: ["shawn"] }).errors?.summary).snap( | ||
// "b must be removed\na must be defined" | ||
// ) | ||
// }) | ||
// }) |
@@ -1,34 +0,224 @@ | ||
// import { match } from "arktype" | ||
import { attest } from "@arktype/attest" | ||
import { match } from "arktype" | ||
import { scope } from "../ark.js" | ||
// const matcher = match({ | ||
// boolean: (b) => !b, | ||
// semver: (s) => s.length | ||
// }).when({ condition: "true" }, (data) => { | ||
// return data.condition | ||
// }) | ||
describe("match", () => { | ||
it("properly infers types of inputs/outputs", () => { | ||
const matcher = match({ string: (s) => s, number: (n) => n }) | ||
.when("boolean", (b) => b) | ||
.orThrow() | ||
// const greeting = match({ | ||
// "integer<12": () => "Good morning", | ||
// "integer<18": () => "Good afternoon", | ||
// default: () => "Good evening" | ||
// }) | ||
// properly infers the type of the output based on the input | ||
attest<string>(matcher("abc")).equals("abc") | ||
attest<number>(matcher(4)).equals(4) | ||
attest<boolean>(matcher(true)).equals(true) | ||
// const result = greeting(5) | ||
// and properly handles unions in the input type | ||
attest<string | number>(matcher(0 as string | number)) | ||
}) | ||
// const base = matcher({} as unknown) //=>? | ||
it("`.when` errors on redundant cases", () => { | ||
const matcher = match().when("string", (s) => s) | ||
// const oneResult = matcher({} as object) //=>? | ||
// @ts-expect-error | ||
attest(() => matcher.when("string", (s) => s)).throwsAndHasTypeError( | ||
"This branch is redundant and will never be reached" // TODO: rewrite error message | ||
) | ||
}) | ||
// const twoResults = matcher({} as boolean | string) //=>? | ||
it("errors on cases redundant to a previous `cases` block", () => { | ||
const matcher = match({ string: (s) => s }) | ||
// const validMatcher = match({ | ||
// // ^? | ||
// number: (data) => data, | ||
// "string|unknown[]": (data) => data.length | ||
// }) | ||
// @ts-expect-error | ||
attest(() => matcher.cases({ string: (s) => s })).throwsAndHasTypeError( | ||
"This branch is redundant and will never be reached" | ||
) | ||
}) | ||
// const invalidMatcher = match({ | ||
// // ^? | ||
// "string|numbr": (data) => data, | ||
// "1<boolean<5": (data) => data | ||
// }) | ||
describe("constraint handling", () => { | ||
it("properly considers constrained types as different from their base", () => { | ||
const matcher = match | ||
.only<number>() | ||
.when("number>2", (n) => { | ||
attest<number>(n) | ||
return n | ||
}) | ||
.when("number", (n) => n) | ||
.finalize() | ||
// for assertions | ||
matcher(5) | ||
}) | ||
}) | ||
describe('"finalizations"', () => { | ||
it(".orThrow()", () => { | ||
const matcher = match() | ||
.when("string", (s) => s) | ||
.orThrow() | ||
// properly returns the `never` type and throws given a guaranteed-to-be-invalid input | ||
attest<never>(matcher(4)) | ||
attest(() => matcher(4)).throws("TODO: what's the error message?") // TODO | ||
}) | ||
describe(".default", () => { | ||
it("chained, given a callback", () => { | ||
const matcher = match() | ||
.when("string", (s) => s) | ||
.default((_) => 0) | ||
attest<string>(matcher("abc")).equals("abc") | ||
attest<number>(matcher(4)).equals(0) | ||
attest<string | number>(matcher(0 as unknown)) | ||
}) | ||
it("chained, given a value", () => { | ||
const matcher = match() | ||
.when("string", (s) => s) | ||
.default(0) | ||
attest<string>(matcher("abc")).equals("abc") | ||
attest<number>(matcher(4)).equals(0) | ||
attest<string | number>(matcher(0 as unknown)) | ||
}) | ||
it("in `cases`, given a callback", () => { | ||
const matcher = match({ string: (s) => s, default: (_) => 0 }) | ||
attest<string>(matcher("abc")).equals("abc") | ||
attest<number>(matcher(4)).equals(0) | ||
attest<string | number>(matcher(0 as unknown)) | ||
}) | ||
}) | ||
it("errors when attempting to `.finalize()` a non-exhaustive matcher", () => { | ||
const matcher = match().when("string", (s) => s) | ||
// @ts-expect-error | ||
attest(() => matcher.finalize()).throwsAndHasTypeError( | ||
"Cannot manually finalize a non-exhaustive matcher: consider adding a `.default` case, using one of the `.orX` methods, or using `match.only<T>`" // TODO: rewrite message | ||
) | ||
}) | ||
it("considers `unknown` exhaustive", () => { | ||
const matcher = match() | ||
.when("unknown", (x) => x) | ||
.finalize() | ||
attest(matcher(4)).equals(4) | ||
}) | ||
}) | ||
describe(".only<T>", () => { | ||
it("does not accept invalid inputs at a type-level", () => { | ||
const matcher = match | ||
.only<string | number>() | ||
.when("string", (s) => s) | ||
.when("number", (n) => n) | ||
.finalize() | ||
// @ts-expect-error | ||
attest(() => matcher(true)).throwsAndHasTypeError( | ||
"Argument of type 'true' is not assignable to parameter of type 'string | number'" | ||
) // TODO: what's the runtime error? | ||
}) | ||
it("errors when attempting to `.finalize()` a non-exhaustive matcher", () => { | ||
const matcher = match.only<string | number>().when("string", (s) => s) | ||
// @ts-expect-error | ||
attest(() => matcher.finalize()).throwsAndHasTypeError( | ||
"Cannot manually finalize a non-exhaustive matcher: consider adding a `.default` case, using one of the `.orX` methods, or handling the cases explicitly" // TODO: rewrite message. at runtime can we even show a counterexample (serialize the cases not handled)? | ||
) | ||
}) | ||
it("allows finalizing exhaustive matchers", (_) => { | ||
const matcher = match | ||
.only<string | number>() | ||
.when("string", (s) => s) | ||
.when("number", (n) => n) | ||
.finalize() | ||
attest<string>(matcher("abc")).equals("abc") | ||
attest<number>(matcher(4)).equals(4) | ||
attest<string | number>(matcher(0 as string | number)) | ||
}) | ||
it("infers the parameter to chained .default as the remaining cases", () => { | ||
const matcher = match | ||
.only<string | number | boolean>() | ||
.when("string", (s) => s) | ||
.default((n) => { | ||
attest<number | boolean>(n) | ||
return n | ||
}) | ||
// for assertions | ||
matcher(4) | ||
}) | ||
it("infers the parameter to in-cases .default", () => { | ||
const matcher = match.only<string | number | boolean>().cases({ | ||
string: (s) => s, | ||
default: (n) => { | ||
// TS doesn't understand sequentiality in cases, so it's inferred as the in-type | ||
attest<string | number | boolean>(n) | ||
return n | ||
} | ||
}) | ||
// for assertions | ||
matcher(4) | ||
}) | ||
it("returns `never` on only the specific cases handled by `.orThrow`", () => { | ||
const matcher = match | ||
.only<string | number>() | ||
.when("string", (s) => s) | ||
.orThrow() | ||
attest<never>(matcher(4)) | ||
}) | ||
}) | ||
it("within scope", () => { | ||
const threeSixtyNoScope = scope({ three: "3", sixty: "60", no: "'no'" }) | ||
const matcher = threeSixtyNoScope | ||
.match({ | ||
three: (three) => { | ||
attest<3>(three) | ||
return 3 | ||
} | ||
}) | ||
.when("sixty", (sixty) => { | ||
attest<60>(sixty) | ||
return 60 | ||
}) | ||
.orThrow() | ||
// for assertions | ||
matcher(3) | ||
matcher(60) | ||
}) | ||
it("properly propagates errors from invalid type definitions in `when`", () => { | ||
// @ts-expect-error | ||
attest(() => match().when("strong", (s) => s)).type.errors( | ||
"'strong' is unresolvable" | ||
) | ||
}) | ||
it("properly propagates errors from invalid type definitions in `cases`", () => { | ||
// @ts-expect-error | ||
attest(() => match({ strong: (s) => s })).type.errors( | ||
"'strong' is unresolvable" | ||
) | ||
}) | ||
}) |
import { attest } from "@arktype/attest" | ||
import type { Out } from "@arktype/schema" | ||
import type { Out, is } from "@arktype/schema" | ||
import type { equals } from "@arktype/util" | ||
@@ -55,3 +55,3 @@ import { type, type Type } from "arktype" | ||
const one = type(["number", ":", (n): n is 1 => n === 1]) | ||
attest<Type<1>>(one) | ||
attest<1>(one.infer) | ||
}) | ||
@@ -92,3 +92,3 @@ it("functional parameter inference", () => { | ||
.narrow((n): n is 5 => n === 5) | ||
attest<Type<(In: string) => Out<5>>>(t) | ||
attest<Type<(In: string) => Out<is<5, { anonymousPredicate: true }>>>>(t) | ||
attest(t.json).snap({ domain: "string" }) | ||
@@ -95,0 +95,0 @@ }) |
import { attest } from "@arktype/attest" | ||
import { registry } from "@arktype/schema" | ||
import { printable } from "@arktype/util" | ||
import { printable, reference } from "@arktype/util" | ||
import { scope, type } from "arktype" | ||
@@ -39,3 +38,3 @@ import { | ||
const s = Symbol() | ||
const name = registry.register(s) | ||
const name = reference(s) | ||
const t = type({ | ||
@@ -52,3 +51,3 @@ [s]: "string" | ||
const s = Symbol() | ||
const name = registry.register(s) | ||
const name = reference(s) | ||
const t = type({ | ||
@@ -274,8 +273,9 @@ [s]: "number?" | ||
}) | ||
it("traverse optional", () => { | ||
const o = type({ "a?": "string" }).configure({ keys: "strict" }) | ||
attest(o({ a: "a" }).out).snap({ a: "a" }) | ||
attest(o({}).out).snap({}) | ||
attest(o({ a: 1 }).errors?.summary).snap("a must be a string (was number)") | ||
}) | ||
// TODO: reenable | ||
// it("traverse optional", () => { | ||
// const o = type({ "a?": "string" }).configure({ keys: "strict" }) | ||
// attest(o({ a: "a" }).out).snap({ a: "a" }) | ||
// attest(o({}).out).snap({}) | ||
// attest(o({ a: 1 }).errors?.summary).snap("a must be a string (was number)") | ||
// }) | ||
it("intersection", () => { | ||
@@ -295,10 +295,11 @@ const t = type({ a: "number" }).and({ b: "boolean" }) | ||
}) | ||
it("multiple bad strict", () => { | ||
const t = type({ a: "string", b: "boolean" }).configure({ | ||
keys: "strict" | ||
}) | ||
attest(t({ a: 1, b: 2 }).errors?.summary).snap( | ||
"a must be a string (was number)\nb must be boolean (was number)" | ||
) | ||
}) | ||
// TODO: reenable | ||
// it("multiple bad strict", () => { | ||
// const t = type({ a: "string", b: "boolean" }).configure({ | ||
// keys: "strict" | ||
// }) | ||
// attest(t({ a: 1, b: 2 }).errors?.summary).snap( | ||
// "a must be a string (was number)\nb must be boolean (was number)" | ||
// ) | ||
// }) | ||
}) |
285
match.ts
@@ -1,7 +0,14 @@ | ||
import type { Morph } from "@arktype/schema" | ||
import type { | ||
Fn, | ||
AnonymousRefinementKey, | ||
Morph, | ||
distill, | ||
intersectConstrainables, | ||
is | ||
} from "@arktype/schema" | ||
import type { | ||
ErrorMessage, | ||
UnknownUnion, | ||
isDisjoint, | ||
numericStringKeyOf, | ||
replaceKey, | ||
returnOf, | ||
unionToTuple, | ||
@@ -13,36 +20,119 @@ valueOf | ||
type MatchContext = { | ||
inConstraint: unknown | ||
outConstraint: unknown | ||
thens: readonly Fn[] | ||
type MatchParserContext = { | ||
thens: readonly ((In: unknown) => unknown)[] | ||
$: unknown | ||
exhaustiveOver: unknown | ||
} | ||
type validateCases<cases, ctx extends MatchContext> = { | ||
[k in keyof cases | keyof ctx["$"] | "default"]?: k extends "default" | ||
? (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
: k extends validateTypeRoot<k, ctx["$"]> | ||
? ( | ||
In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]> | ||
) => ctx["outConstraint"] | ||
: validateTypeRoot<k, ctx["$"]> | ||
export type MatchParser<$> = CaseMatchParser<{ | ||
thens: [] | ||
// "match()" is the same as "match.only<unknown>()" | ||
exhaustiveOver: unknown | ||
$: $ | ||
}> & { | ||
(): ChainableMatchParser<{ | ||
thens: [] | ||
// "match()" is the same as "match.only<unknown>()" | ||
exhaustiveOver: unknown | ||
$: $ | ||
}> | ||
only: <inputs>() => ChainableMatchParser<{ | ||
thens: [] | ||
exhaustiveOver: inputs | ||
$: $ | ||
}> | ||
} | ||
type errorCases<cases, ctx extends MatchContext> = { | ||
[k in keyof cases]?: k extends "default" | ||
? (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
: k extends validateTypeRoot<k, ctx["$"]> | ||
? ( | ||
In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]> | ||
) => ctx["outConstraint"] | ||
: validateTypeRoot<k, ctx["$"]> | ||
type matcherInputs<ctx extends MatchParserContext> = Parameters< | ||
ctx["thens"][number] | ||
>[0] | ||
type AnonymouslyRefined = { | ||
[k in AnonymousRefinementKey]: { [_ in k]: true } | ||
}[AnonymousRefinementKey] | ||
type getHandledBranches<ctx extends MatchParserContext> = Exclude< | ||
matcherInputs<ctx>, | ||
is<unknown, AnonymouslyRefined> | ||
> | ||
type getUnhandledBranches<ctx extends MatchParserContext> = Exclude< | ||
unknown extends ctx["exhaustiveOver"] ? UnknownUnion : ctx["exhaustiveOver"], | ||
getHandledBranches<ctx> | ||
> | ||
type addBranches< | ||
ctx extends MatchParserContext, | ||
branches extends unknown[] | ||
> = replaceKey<ctx, "thens", [...ctx["thens"], ...branches]> | ||
type validateWhenDefinition< | ||
def, | ||
ctx extends MatchParserContext | ||
> = def extends validateTypeRoot<def, ctx["$"]> | ||
? inferMatchBranch<def, ctx> extends getHandledBranches<ctx> | ||
? ErrorMessage<"This branch is redundant and will never be reached"> | ||
: def | ||
: validateTypeRoot<def, ctx["$"]> | ||
// infer the types handled by a match branch, which is identical to `inferTypeRoot` while properly | ||
// excluding cases that are already handled by other branches | ||
type inferMatchBranch< | ||
def, | ||
ctx extends MatchParserContext | ||
> = intersectConstrainables< | ||
getUnhandledBranches<ctx>, | ||
inferTypeRoot<def, ctx["$"]> | ||
> | ||
export type ChainableMatchParser<ctx extends MatchParserContext> = { | ||
// chainable methods | ||
when: <def, ret>( | ||
when: validateWhenDefinition<def, ctx>, | ||
then: (In: distill<inferMatchBranch<def, ctx>>) => ret | ||
) => ChainableMatchParser< | ||
addBranches<ctx, [(In: inferMatchBranch<def, ctx>) => ret]> | ||
> | ||
cases: CaseMatchParser<ctx> | ||
// finalizing methods | ||
orThrow: () => finalizeMatchParser< | ||
addBranches<ctx, [(In: getHandledBranches<ctx>) => never]> | ||
> | ||
default: MatchParserDefaultInvocation<ctx> | ||
finalize: ( | ||
this: getUnhandledBranches<ctx> extends never | ||
? ChainableMatchParser<ctx> | ||
: ErrorMessage<"Cannot manually finalize a non-exhaustive matcher: consider adding a `.default` case, using one of the `.orX` methods, or using `match.only<T>`"> | ||
) => finalizeMatchParser<ctx> | ||
} | ||
type MatchParserDefaultInvocation<ctx extends MatchParserContext> = { | ||
<f extends (In: getUnhandledBranches<ctx>) => unknown>( | ||
f: f | ||
): finalizeWithDefault<ctx, ReturnType<f>> | ||
<const value>(value: value): finalizeWithDefault<ctx, value> | ||
} | ||
type validateCases<cases, ctx extends MatchParserContext> = { | ||
[def in keyof cases | keyof ctx["$"] | "default"]?: def extends "default" | ||
? (In: distill<getUnhandledBranches<ctx>>) => unknown | ||
: def extends validateWhenDefinition<def, ctx> | ||
? (In: distill<inferMatchBranch<def, ctx>>) => unknown | ||
: validateWhenDefinition<def, ctx> | ||
} | ||
type errorCases<cases, ctx extends MatchParserContext> = { | ||
[def in keyof cases]?: def extends "default" | ||
? (In: distill<getUnhandledBranches<ctx>>) => unknown | ||
: def extends validateWhenDefinition<def, ctx> | ||
? (In: distill<inferMatchBranch<def, ctx>>) => unknown | ||
: validateWhenDefinition<def, ctx> | ||
} & { | ||
[k in Exclude<keyof ctx["$"], keyof cases>]?: ( | ||
In: ctx["inConstraint"] & ctx["$"][k] | ||
) => ctx["outConstraint"] | ||
In: distill<intersectConstrainables<getUnhandledBranches<ctx>, ctx["$"][k]>> | ||
) => unknown | ||
} & { | ||
default?: (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
default?: (In: distill<getUnhandledBranches<ctx>>) => unknown | ||
} | ||
export type CaseMatchParser<ctx extends MatchContext> = { | ||
export type CaseMatchParser<ctx extends MatchParserContext> = { | ||
<cases>( | ||
@@ -52,67 +142,104 @@ def: cases extends validateCases<cases, ctx> | ||
: errorCases<cases, ctx> | ||
): ChainableMatchParser< | ||
replaceKey<ctx, "thens", [...ctx["thens"], ...unionToTuple<valueOf<cases>>]> | ||
> | ||
): cases extends { default: (...args: never[]) => infer defaultReturn } | ||
? finalizeWithDefault< | ||
addBranches<ctx, unionToTuple<cases[Exclude<keyof cases, "default">]>>, | ||
defaultReturn | ||
> | ||
: ChainableMatchParser<addBranches<ctx, unionToTuple<valueOf<cases>>>> | ||
} | ||
export type MatchParser<$> = CaseMatchParser<{ | ||
inConstraint: unknown | ||
outConstraint: unknown | ||
thens: [] | ||
$: $ | ||
}> & { | ||
<In = unknown, Out = unknown>(): ChainableMatchParser<{ | ||
inConstraint: In | ||
outConstraint: Out | ||
thens: [] | ||
$: $ | ||
}> | ||
type finalizeWithDefault< | ||
ctx extends MatchParserContext, | ||
defaultReturn | ||
> = finalizeMatchParser< | ||
addBranches<ctx, [(_: getUnhandledBranches<ctx>) => defaultReturn]> | ||
> | ||
type finalizeMatchParser<ctx extends MatchParserContext> = MatchInvocation<{ | ||
thens: ctx["thens"] | ||
initialInputs: ctx["exhaustiveOver"] | ||
}> | ||
type MatchInvocationContext = { | ||
thens: readonly ((...args: never[]) => unknown)[] | ||
initialInputs: unknown | ||
} | ||
export type WhenMatchParser<ctx extends MatchContext> = < | ||
def, | ||
then extends ( | ||
In: ctx["inConstraint"] & inferTypeRoot<def, ctx["$"]> | ||
) => ctx["outConstraint"] | ||
export type MatchInvocation<ctx extends MatchInvocationContext> = < | ||
data extends ctx["initialInputs"] | ||
>( | ||
when: validateTypeRoot<def, ctx["$"]>, | ||
then: then | ||
) => ChainableMatchParser<replaceKey<ctx, "thens", [...ctx["thens"], then]>> | ||
export type MatchInvocation<ctx extends MatchContext> = < | ||
data extends ctx["inConstraint"] | ||
>( | ||
data: data | ||
) => { | ||
[i in Extract<keyof ctx["thens"], `${number}`>]: ctx["thens"][i] extends Fn< | ||
[infer In], | ||
infer Out | ||
> | ||
? isDisjoint<data, In> extends true | ||
? never | ||
: Out | ||
: returnOf<ctx["thens"][i]> | ||
}[Extract<keyof ctx["thens"], `${number}`>] | ||
[i in numericStringKeyOf<ctx["thens"]>]: isDisjoint< | ||
data, | ||
Parameters<ctx["thens"][i]>[0] | ||
> extends true | ||
? never | ||
: ReturnType<ctx["thens"][i]> | ||
}[numericStringKeyOf<ctx["thens"]>] | ||
export type ChainableMatchParser<ctx extends MatchContext> = | ||
MatchInvocation<ctx> & { | ||
cases: CaseMatchParser<ctx> | ||
when: WhenMatchParser<ctx> | ||
} | ||
export const createMatchParser = <$>(scope: Scope): MatchParser<$> => { | ||
const matchParser = (isRestricted: boolean) => { | ||
const handledCases: { when: Type; then: Morph }[] = [] | ||
let defaultCase: ((x: unknown) => unknown) | null = null | ||
export const createMatchParser = <$>(scope: Scope): MatchParser<$> => { | ||
const parser = (cases: Record<string, Morph>) => { | ||
const caseArray = Object.entries(cases).map(([def, morph]) => ({ | ||
when: new Type(def, scope).allows, | ||
then: morph | ||
})) | ||
return (data: unknown) => { | ||
for (const c of caseArray) { | ||
if (c.when(data)) { | ||
return c.then(data, {} as never) | ||
const parser = { | ||
when: (when: unknown, then: Morph) => { | ||
handledCases.push({ when: new Type(when, scope), then }) | ||
return parser | ||
}, | ||
finalize: () => { | ||
// TODO: exhaustiveness checking | ||
// const branches = handledCases.flatMap(({ when, then }) => { | ||
// if (when.root.kind === "union") { | ||
// return when.root.branches.map((branch) => ({ | ||
// in: branch, | ||
// morph: then | ||
// })) | ||
// } | ||
// if (when.root.kind === "morph") { | ||
// return [{ in: when, morph: [when.root.morph, then] }] | ||
// } | ||
// return [{ in: when.root, morph: then }] | ||
// }) | ||
// if (defaultCase) { | ||
// branches.push({ in: new Type("unknown", scope), morph: defaultCase }) | ||
// } | ||
// const matchers = schema.union({ | ||
// branches, | ||
// ordered: true | ||
// }) | ||
// return (data: unknown) => { | ||
// const result = matchers.apply(data) | ||
// if (result.errors) { | ||
// throw result.errors | ||
// } | ||
// return result.out | ||
// } | ||
}, | ||
orThrow: () => { | ||
// implicitly finalize, we don't need to do anything else because we throw either way | ||
return parser.finalize() | ||
}, | ||
default: (x: unknown) => { | ||
if (x instanceof Function) { | ||
defaultCase = x as never | ||
} else { | ||
defaultCase = () => x | ||
} | ||
return parser.finalize() | ||
} | ||
} | ||
return parser | ||
} | ||
return parser as never | ||
return Object.assign(() => matchParser(false), { | ||
only: () => matchParser(true) | ||
}) as never | ||
} |
@@ -1,48 +0,77 @@ | ||
import type { Fn, isDisjoint, replaceKey, returnOf, unionToTuple, valueOf } from "@arktype/util"; | ||
import type { AnonymousRefinementKey, distill, intersectConstrainables, is } from "@arktype/schema"; | ||
import type { ErrorMessage, UnknownUnion, isDisjoint, numericStringKeyOf, replaceKey, unionToTuple, valueOf } from "@arktype/util"; | ||
import type { Scope } from "./scope.js"; | ||
import { type inferTypeRoot, type validateTypeRoot } from "./type.js"; | ||
type MatchContext = { | ||
inConstraint: unknown; | ||
outConstraint: unknown; | ||
thens: readonly Fn[]; | ||
type MatchParserContext = { | ||
thens: readonly ((In: unknown) => unknown)[]; | ||
$: unknown; | ||
exhaustiveOver: unknown; | ||
}; | ||
type validateCases<cases, ctx extends MatchContext> = { | ||
[k in keyof cases | keyof ctx["$"] | "default"]?: k extends "default" ? (In: ctx["inConstraint"]) => ctx["outConstraint"] : k extends validateTypeRoot<k, ctx["$"]> ? (In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]>) => ctx["outConstraint"] : validateTypeRoot<k, ctx["$"]>; | ||
}; | ||
type errorCases<cases, ctx extends MatchContext> = { | ||
[k in keyof cases]?: k extends "default" ? (In: ctx["inConstraint"]) => ctx["outConstraint"] : k extends validateTypeRoot<k, ctx["$"]> ? (In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]>) => ctx["outConstraint"] : validateTypeRoot<k, ctx["$"]>; | ||
} & { | ||
[k in Exclude<keyof ctx["$"], keyof cases>]?: (In: ctx["inConstraint"] & ctx["$"][k]) => ctx["outConstraint"]; | ||
} & { | ||
default?: (In: ctx["inConstraint"]) => ctx["outConstraint"]; | ||
}; | ||
export type CaseMatchParser<ctx extends MatchContext> = { | ||
<cases>(def: cases extends validateCases<cases, ctx> ? cases : errorCases<cases, ctx>): ChainableMatchParser<replaceKey<ctx, "thens", [...ctx["thens"], ...unionToTuple<valueOf<cases>>]>>; | ||
}; | ||
export type MatchParser<$> = CaseMatchParser<{ | ||
inConstraint: unknown; | ||
outConstraint: unknown; | ||
thens: []; | ||
exhaustiveOver: unknown; | ||
$: $; | ||
}> & { | ||
<In = unknown, Out = unknown>(): ChainableMatchParser<{ | ||
inConstraint: In; | ||
outConstraint: Out; | ||
(): ChainableMatchParser<{ | ||
thens: []; | ||
exhaustiveOver: unknown; | ||
$: $; | ||
}>; | ||
only: <inputs>() => ChainableMatchParser<{ | ||
thens: []; | ||
exhaustiveOver: inputs; | ||
$: $; | ||
}>; | ||
}; | ||
export type WhenMatchParser<ctx extends MatchContext> = <def, then extends (In: ctx["inConstraint"] & inferTypeRoot<def, ctx["$"]>) => ctx["outConstraint"]>(when: validateTypeRoot<def, ctx["$"]>, then: then) => ChainableMatchParser<replaceKey<ctx, "thens", [...ctx["thens"], then]>>; | ||
export type MatchInvocation<ctx extends MatchContext> = <data extends ctx["inConstraint"]>(data: data) => { | ||
[i in Extract<keyof ctx["thens"], `${number}`>]: ctx["thens"][i] extends Fn<[ | ||
infer In | ||
], infer Out> ? isDisjoint<data, In> extends true ? never : Out : returnOf<ctx["thens"][i]>; | ||
}[Extract<keyof ctx["thens"], `${number}`>]; | ||
export type ChainableMatchParser<ctx extends MatchContext> = MatchInvocation<ctx> & { | ||
type matcherInputs<ctx extends MatchParserContext> = Parameters<ctx["thens"][number]>[0]; | ||
type AnonymouslyRefined = { | ||
[k in AnonymousRefinementKey]: { | ||
[_ in k]: true; | ||
}; | ||
}[AnonymousRefinementKey]; | ||
type getHandledBranches<ctx extends MatchParserContext> = Exclude<matcherInputs<ctx>, is<unknown, AnonymouslyRefined>>; | ||
type getUnhandledBranches<ctx extends MatchParserContext> = Exclude<unknown extends ctx["exhaustiveOver"] ? UnknownUnion : ctx["exhaustiveOver"], getHandledBranches<ctx>>; | ||
type addBranches<ctx extends MatchParserContext, branches extends unknown[]> = replaceKey<ctx, "thens", [...ctx["thens"], ...branches]>; | ||
type validateWhenDefinition<def, ctx extends MatchParserContext> = def extends validateTypeRoot<def, ctx["$"]> ? inferMatchBranch<def, ctx> extends getHandledBranches<ctx> ? ErrorMessage<"This branch is redundant and will never be reached"> : def : validateTypeRoot<def, ctx["$"]>; | ||
type inferMatchBranch<def, ctx extends MatchParserContext> = intersectConstrainables<getUnhandledBranches<ctx>, inferTypeRoot<def, ctx["$"]>>; | ||
export type ChainableMatchParser<ctx extends MatchParserContext> = { | ||
when: <def, ret>(when: validateWhenDefinition<def, ctx>, then: (In: distill<inferMatchBranch<def, ctx>>) => ret) => ChainableMatchParser<addBranches<ctx, [(In: inferMatchBranch<def, ctx>) => ret]>>; | ||
cases: CaseMatchParser<ctx>; | ||
when: WhenMatchParser<ctx>; | ||
orThrow: () => finalizeMatchParser<addBranches<ctx, [(In: getHandledBranches<ctx>) => never]>>; | ||
default: MatchParserDefaultInvocation<ctx>; | ||
finalize: (this: getUnhandledBranches<ctx> extends never ? ChainableMatchParser<ctx> : ErrorMessage<"Cannot manually finalize a non-exhaustive matcher: consider adding a `.default` case, using one of the `.orX` methods, or using `match.only<T>`">) => finalizeMatchParser<ctx>; | ||
}; | ||
type MatchParserDefaultInvocation<ctx extends MatchParserContext> = { | ||
<f extends (In: getUnhandledBranches<ctx>) => unknown>(f: f): finalizeWithDefault<ctx, ReturnType<f>>; | ||
<const value>(value: value): finalizeWithDefault<ctx, value>; | ||
}; | ||
type validateCases<cases, ctx extends MatchParserContext> = { | ||
[def in keyof cases | keyof ctx["$"] | "default"]?: def extends "default" ? (In: distill<getUnhandledBranches<ctx>>) => unknown : def extends validateWhenDefinition<def, ctx> ? (In: distill<inferMatchBranch<def, ctx>>) => unknown : validateWhenDefinition<def, ctx>; | ||
}; | ||
type errorCases<cases, ctx extends MatchParserContext> = { | ||
[def in keyof cases]?: def extends "default" ? (In: distill<getUnhandledBranches<ctx>>) => unknown : def extends validateWhenDefinition<def, ctx> ? (In: distill<inferMatchBranch<def, ctx>>) => unknown : validateWhenDefinition<def, ctx>; | ||
} & { | ||
[k in Exclude<keyof ctx["$"], keyof cases>]?: (In: distill<intersectConstrainables<getUnhandledBranches<ctx>, ctx["$"][k]>>) => unknown; | ||
} & { | ||
default?: (In: distill<getUnhandledBranches<ctx>>) => unknown; | ||
}; | ||
export type CaseMatchParser<ctx extends MatchParserContext> = { | ||
<cases>(def: cases extends validateCases<cases, ctx> ? cases : errorCases<cases, ctx>): cases extends { | ||
default: (...args: never[]) => infer defaultReturn; | ||
} ? finalizeWithDefault<addBranches<ctx, unionToTuple<cases[Exclude<keyof cases, "default">]>>, defaultReturn> : ChainableMatchParser<addBranches<ctx, unionToTuple<valueOf<cases>>>>; | ||
}; | ||
type finalizeWithDefault<ctx extends MatchParserContext, defaultReturn> = finalizeMatchParser<addBranches<ctx, [(_: getUnhandledBranches<ctx>) => defaultReturn]>>; | ||
type finalizeMatchParser<ctx extends MatchParserContext> = MatchInvocation<{ | ||
thens: ctx["thens"]; | ||
initialInputs: ctx["exhaustiveOver"]; | ||
}>; | ||
type MatchInvocationContext = { | ||
thens: readonly ((...args: never[]) => unknown)[]; | ||
initialInputs: unknown; | ||
}; | ||
export type MatchInvocation<ctx extends MatchInvocationContext> = <data extends ctx["initialInputs"]>(data: data) => { | ||
[i in numericStringKeyOf<ctx["thens"]>]: isDisjoint<data, Parameters<ctx["thens"][i]>[0]> extends true ? never : ReturnType<ctx["thens"][i]>; | ||
}[numericStringKeyOf<ctx["thens"]>]; | ||
export declare const createMatchParser: <$>(scope: Scope) => MatchParser<$>; | ||
export {}; | ||
//# sourceMappingURL=match.d.ts.map |
import { Type } from "./type.js"; | ||
export const createMatchParser = (scope) => { | ||
const parser = (cases) => { | ||
const caseArray = Object.entries(cases).map(([def, morph]) => ({ | ||
when: new Type(def, scope).allows, | ||
then: morph | ||
})); | ||
return (data) => { | ||
for (const c of caseArray) { | ||
if (c.when(data)) { | ||
return c.then(data, {}); | ||
const matchParser = (isRestricted) => { | ||
const handledCases = []; | ||
let defaultCase = null; | ||
const parser = { | ||
when: (when, then) => { | ||
handledCases.push({ when: new Type(when, scope), then }); | ||
return parser; | ||
}, | ||
finalize: () => { | ||
// TODO: exhaustiveness checking | ||
// const branches = handledCases.flatMap(({ when, then }) => { | ||
// if (when.root.kind === "union") { | ||
// return when.root.branches.map((branch) => ({ | ||
// in: branch, | ||
// morph: then | ||
// })) | ||
// } | ||
// if (when.root.kind === "morph") { | ||
// return [{ in: when, morph: [when.root.morph, then] }] | ||
// } | ||
// return [{ in: when.root, morph: then }] | ||
// }) | ||
// if (defaultCase) { | ||
// branches.push({ in: new Type("unknown", scope), morph: defaultCase }) | ||
// } | ||
// const matchers = schema.union({ | ||
// branches, | ||
// ordered: true | ||
// }) | ||
// return (data: unknown) => { | ||
// const result = matchers.apply(data) | ||
// if (result.errors) { | ||
// throw result.errors | ||
// } | ||
// return result.out | ||
// } | ||
}, | ||
orThrow: () => { | ||
// implicitly finalize, we don't need to do anything else because we throw either way | ||
return parser.finalize(); | ||
}, | ||
default: (x) => { | ||
if (x instanceof Function) { | ||
defaultCase = x; | ||
} | ||
else { | ||
defaultCase = () => x; | ||
} | ||
return parser.finalize(); | ||
} | ||
}; | ||
return parser; | ||
}; | ||
return parser; | ||
return Object.assign(() => matchParser(false), { | ||
only: () => matchParser(true) | ||
}); | ||
}; | ||
//# sourceMappingURL=match.js.map |
@@ -1,3 +0,3 @@ | ||
import { type TypeNode } from "@arktype/schema"; | ||
import { type defined, type equals, type ErrorMessage, type evaluate, type isAny, type isUnknown, type objectKindOrDomainOf, type optionalKeyOf, type Primitive, type requiredKeyOf } from "@arktype/util"; | ||
import { type TypeNode, type is } from "@arktype/schema"; | ||
import { type ErrorMessage, type Primitive, type defined, type equals, type evaluate, type isAny, type isUnknown, type objectKindOrDomainOf, type optionalKeyOf, type requiredKeyOf } from "@arktype/util"; | ||
import type { type } from "../ark.js"; | ||
@@ -8,5 +8,7 @@ import type { ParseContext } from "../scope.js"; | ||
import type { BaseCompletions, inferString } from "./string/string.js"; | ||
import { type inferTuple, type TupleExpression, type validateTuple } from "./tuple.js"; | ||
import { type TupleExpression, type inferTuple, type validateTuple } from "./tuple.js"; | ||
export declare const parseObject: (def: object, ctx: ParseContext) => TypeNode; | ||
export type inferDefinition<def, $, args> = isAny<def> extends true ? never : def extends type.cast<infer t> | ThunkCast<infer t> ? t : def extends string ? inferString<def, $, args> : def extends readonly unknown[] ? inferTuple<def, $, args> : def extends RegExp ? string : def extends object ? inferObjectLiteral<def, $, args> : never; | ||
export type inferDefinition<def, $, args> = isAny<def> extends true ? never : def extends type.cast<infer t> | ThunkCast<infer t> ? t : def extends string ? inferString<def, $, args> : def extends readonly unknown[] ? inferTuple<def, $, args> : def extends RegExp ? is<string, { | ||
anonymousPattern: true; | ||
}> : def extends object ? inferObjectLiteral<def, $, args> : never; | ||
export type validateDefinition<def, $, args> = null extends undefined ? ErrorMessage<`'strict' or 'strictNullChecks' must be set to true in your tsconfig's 'compilerOptions'`> : [def] extends [Terminal] ? unknown extends def ? never : def : def extends string ? validateString<def, $, args> : def extends readonly unknown[] ? validateTuple<def, $, args> : def extends BadDefinitionType ? writeBadDefinitionTypeMessage<objectKindOrDomainOf<def>> : isUnknown<def> extends true ? // this allows the initial list of autocompletions to be populated when a user writes "type()", | ||
@@ -13,0 +15,0 @@ BaseCompletions<$, args> | {} : validateObjectLiteral<def, $, args>; |
@@ -33,4 +33,4 @@ import { keywords, schema } from "@arktype/schema"; | ||
// because the currently parsed definition will overwrite them. | ||
const requiredEntriesFromSpread = (spreadNode.required ?? []).filter((e) => !entries.some(([k]) => k === e.key)); | ||
const optionalEntriesFromSpread = (spreadNode.optional ?? []).filter((e) => !entries.some(([k]) => k === e.key)); | ||
const requiredEntriesFromSpread = (spreadNode.props?.required ?? []).filter((e) => !entries.some(([k]) => k === e.key)); | ||
const optionalEntriesFromSpread = (spreadNode.props?.optional ?? []).filter((e) => !entries.some(([k]) => k === e.key)); | ||
required.push(...requiredEntriesFromSpread); | ||
@@ -37,0 +37,0 @@ optional.push(...optionalEntriesFromSpread); |
import type { DateLiteral, Refinements, RegexLiteral, distill, is } from "@arktype/schema"; | ||
import type { BigintLiteral, List, NumberLiteral, evaluate, extend } from "@arktype/util"; | ||
import type { BigintLiteral, List, NumberLiteral, evaluate } from "@arktype/util"; | ||
import type { UnparsedScope, resolve, tryInferSubmoduleReference } from "../../scope.js"; | ||
@@ -28,7 +28,7 @@ import type { GenericProps } from "../../type.js"; | ||
export type InfixExpression<operator extends InfixOperator = InfixOperator, l = unknown, r = unknown> = [l, operator, r]; | ||
export type inferTerminal<token, $, args, refinements extends Refinements> = token extends keyof args | keyof $ ? {} extends refinements ? resolve<token, $, args> : is<resolve<token, $, args>, evaluate<refinements>> : token extends StringLiteral<infer text> ? text : token extends RegexLiteral ? is<string, extend<refinements, { | ||
export type inferTerminal<token, $, args, refinements extends Refinements> = token extends keyof args | keyof $ ? {} extends refinements ? resolve<token, $, args> : is<resolve<token, $, args>, evaluate<refinements>> : token extends StringLiteral<infer text> ? text : token extends RegexLiteral ? is<string, evaluate<refinements & { | ||
[_ in token]: true; | ||
}>> : token extends DateLiteral ? is<Date, extend<refinements, { | ||
}>> : token extends DateLiteral ? is<Date, evaluate<refinements & { | ||
[_ in token]: true; | ||
}>> : token extends NumberLiteral<infer value> ? value : token extends BigintLiteral<infer value> ? value : tryInferSubmoduleReference<$, token>; | ||
//# sourceMappingURL=semantic.d.ts.map |
@@ -1,2 +0,3 @@ | ||
import type { extend } from "@arktype/util"; | ||
import type { BaseMeta, Node } from "@arktype/schema"; | ||
import type { evaluate } from "@arktype/util"; | ||
import type { validateDefinition } from "./definition.js"; | ||
@@ -10,3 +11,3 @@ import { Scanner } from "./string/shift/scanner.js"; | ||
}; | ||
export type EntryParseResult<kind extends ParsedKeyKind = ParsedKeyKind> = extend<KeyParseResult<kind>, { | ||
export type EntryParseResult<kind extends ParsedKeyKind = ParsedKeyKind> = evaluate<KeyParseResult<kind> & { | ||
innerValue: unknown; | ||
@@ -54,3 +55,4 @@ }>; | ||
}>; | ||
export declare const configureShallowDescendants: <node extends Node>(node: node, configOrDescription: BaseMeta | string) => node; | ||
export {}; | ||
//# sourceMappingURL=shared.d.ts.map |
@@ -47,2 +47,10 @@ import { Scanner } from "./string/shift/scanner.js"; | ||
}; | ||
export const configureShallowDescendants = (node, configOrDescription) => { | ||
const config = typeof configOrDescription === "string" | ||
? { description: configOrDescription } | ||
: configOrDescription; | ||
return node.transform((kind, inner) => ({ ...inner, ...config }), | ||
// TODO: change to props | ||
(node) => node.kind !== "required" && node.kind !== "optional"); | ||
}; | ||
//# sourceMappingURL=shared.js.map |
@@ -12,3 +12,2 @@ import type { Completion, defined, ErrorMessage } from "@arktype/util"; | ||
}; | ||
export type AutocompletePrefix = `${StringifiablePrefixOperator} `; | ||
type BranchState = { | ||
@@ -20,2 +19,3 @@ prefixes: StringifiablePrefixOperator[]; | ||
}; | ||
export type AutocompletePrefix = `${StringifiablePrefixOperator} `; | ||
export declare namespace state { | ||
@@ -22,0 +22,0 @@ export type initialize<def extends string> = from<{ |
@@ -26,4 +26,4 @@ import { type BaseMeta, type Morph, type Out, type Predicate, type TypeNode, type extractIn, type extractOut, type inferMorphOut, type inferNarrow } from "@arktype/schema"; | ||
type writeNonArrayRestMessage<operand> = `Rest element ${operand extends string ? `'${operand}'` : ""} must be an array`; | ||
export declare const prematureRestMessage = "Rest elements are only allowed at the end of a tuple"; | ||
type prematureRestMessage = typeof prematureRestMessage; | ||
export declare const multipleVariadicMesage = "A tuple may have at most one variadic element"; | ||
type prematureRestMessage = typeof multipleVariadicMesage; | ||
type inferTupleLiteral<def extends List, $, args, result extends unknown[] = []> = def extends readonly [infer head, ...infer tail] ? parseEntry<result["length"], head extends variadicExpression<infer operand> ? operand : head> extends infer entryParseResult extends EntryParseResult ? inferDefinition<entryParseResult["innerValue"], $, args> extends infer element ? head extends variadicExpression ? element extends readonly unknown[] ? inferTupleLiteral<tail, $, args, [...result, ...element]> : never : inferTupleLiteral<tail, $, args, entryParseResult["kind"] extends "optional" ? [...result, element?] : [...result, element]> : never : never : result; | ||
@@ -30,0 +30,0 @@ type variadicExpression<operandDef = unknown> = variadicStringExpression<operandDef & string> | variadicTupleExpression<operandDef>; |
import { keywords, schema } from "@arktype/schema"; | ||
import { isArray, objectKindOrDomainOf, printable, throwParseError } from "@arktype/util"; | ||
import { writeUnsatisfiableExpressionError } from "./semantic/validate.js"; | ||
import { parseEntry } from "./shared.js"; | ||
import { configureShallowDescendants, parseEntry } from "./shared.js"; | ||
import { writeMissingRightOperandMessage } from "./string/shift/operand/unenclosed.js"; | ||
@@ -9,3 +9,3 @@ export const parseTuple = (def, ctx) => maybeParseTupleExpression(def, ctx) ?? parseTupleLiteral(def, ctx); | ||
const props = []; | ||
let isVariadic = false; | ||
let variadicIndex; | ||
for (let i = 0; i < def.length; i++) { | ||
@@ -16,3 +16,6 @@ let elementDef = def[i]; | ||
elementDef = elementDef.slice(3); | ||
isVariadic = true; | ||
if (variadicIndex !== undefined) { | ||
return throwParseError(multipleVariadicMesage); | ||
} | ||
variadicIndex = i; | ||
} | ||
@@ -23,13 +26,13 @@ else if (isArray(elementDef) && | ||
elementDef = elementDef[1]; | ||
isVariadic = true; | ||
if (variadicIndex !== undefined) { | ||
return throwParseError(multipleVariadicMesage); | ||
} | ||
variadicIndex = i; | ||
} | ||
const parsedEntry = parseEntry([`${i}`, elementDef]); | ||
const value = ctx.scope.parse(parsedEntry.innerValue, ctx); | ||
if (isVariadic) { | ||
if (variadicIndex === i) { | ||
if (!value.extends(keywords.Array)) { | ||
return throwParseError(writeNonArrayRestMessage(elementDef)); | ||
} | ||
if (i !== def.length - 1) { | ||
return throwParseError(prematureRestMessage); | ||
} | ||
// TODO: Fix builtins.arrayIndexTypeNode() | ||
@@ -52,3 +55,3 @@ const elementType = value.getPath(); | ||
} | ||
if (!isVariadic) { | ||
if (variadicIndex === undefined) { | ||
props.push({ | ||
@@ -60,7 +63,5 @@ key: { | ||
}, | ||
// , ctx | ||
value: schema({ unit: def.length }) | ||
}); | ||
} | ||
// props , ctx | ||
return schema(Array); | ||
@@ -83,3 +84,3 @@ }; | ||
export const writeNonArrayRestMessage = (operand) => `Rest element ${typeof operand === "string" ? `'${operand}'` : ""} must be an array`; | ||
export const prematureRestMessage = `Rest elements are only allowed at the end of a tuple`; | ||
export const multipleVariadicMesage = `A tuple may have at most one variadic element`; | ||
export const parseKeyOfTuple = (def, ctx) => ctx.scope.parse(def[1], ctx).keyof(); | ||
@@ -115,5 +116,3 @@ const parseBranchTuple = (def, ctx) => { | ||
}; | ||
const parseAttributeTuple = (def, ctx) => { | ||
return ctx.scope.parse(def[0], ctx); | ||
}; | ||
const parseAttributeTuple = (def, ctx) => configureShallowDescendants(ctx.scope.parse(def[0], ctx), def[2]); | ||
const indexOneParsers = { | ||
@@ -120,0 +119,0 @@ "|": parseBranchTuple, |
@@ -1,2 +0,2 @@ | ||
import { type ArkErrorCode, type KeyCheckKind, type TypeNode, type extractIn, type extractOut } from "@arktype/schema"; | ||
import { type ArkConfig, type TypeNode, type extractIn, type extractOut } from "@arktype/schema"; | ||
import { type Dict, type evaluate, type isAny, type nominal } from "@arktype/util"; | ||
@@ -7,3 +7,3 @@ import type { type } from "./ark.js"; | ||
import { parseGenericParams, type GenericDeclaration, type GenericParamsParseError } from "./parser/generic.js"; | ||
import { Type, type DeclarationParser, type DefinitionParser, type Generic, type GenericProps, type TypeConfig, type TypeParser } from "./type.js"; | ||
import { Type, type DeclarationParser, type DefinitionParser, type Generic, type GenericProps, type TypeParser } from "./type.js"; | ||
import { type arkKind } from "./util.js"; | ||
@@ -25,9 +25,5 @@ export type ScopeParser<parent, ambient> = { | ||
}; | ||
export type ScopeConfig = { | ||
export interface ArkScopeConfig extends ArkConfig { | ||
ambient?: Scope | null; | ||
codes?: Record<ArkErrorCode, { | ||
mustBe?: string; | ||
}>; | ||
keys?: KeyCheckKind; | ||
}; | ||
} | ||
type validateScope<def, $> = { | ||
@@ -91,3 +87,3 @@ [k in keyof def]: parseScopeKey<k>["params"] extends [] ? def[k] extends Type | PreparsedResolution ? def[k] : validateDefinition<def[k], $ & bootstrap<def>, {}> : parseScopeKey<k>["params"] extends GenericParamsParseError ? parseScopeKey<k>["params"][0] : validateDefinition<def[k], $ & bootstrap<def>, { | ||
inferIn: extractIn<r["exports"]>; | ||
config: TypeConfig; | ||
config: ArkScopeConfig; | ||
private parseCache; | ||
@@ -100,3 +96,3 @@ private resolutions; | ||
private ambient; | ||
constructor(def: Dict, config: ScopeConfig); | ||
constructor(def: Dict, config: ArkScopeConfig); | ||
static root: ScopeParser<{}, {}>; | ||
@@ -123,3 +119,2 @@ type: TypeParser<$<r>>; | ||
import<names extends exportedName<r>[]>(...names: names): destructuredImportContext<r, names extends [] ? keyof r["exports"] & string : names[number]>; | ||
compile(): string; | ||
bindThis(): { | ||
@@ -126,0 +121,0 @@ this: import("@arktype/schema").IntersectionNode<unknown>; |
@@ -154,23 +154,2 @@ import { BaseType, keywords } from "@arktype/schema"; | ||
} | ||
compile() { | ||
return ""; | ||
// this.export() | ||
// const references: Set<Root> = new Set() | ||
// for (const k in this.exportedResolutions!) { | ||
// const resolution = this.exportedResolutions[k] | ||
// if (hasArkKind(resolution, "node") && !references.has(resolution)) { | ||
// for (const reference of resolution.references) { | ||
// references.add(reference) | ||
// } | ||
// } | ||
// } | ||
// return [...references] | ||
// .map( | ||
// (ref) => `const ${ref.alias} = (${In}) => { | ||
// ${ref.condition} | ||
// return true | ||
// }` | ||
// ) | ||
// .join("\n") | ||
} | ||
bindThis() { | ||
@@ -177,0 +156,0 @@ // TODO: fix |
@@ -1,2 +0,2 @@ | ||
import { inferred, type ArkResult, type BaseMeta, type KeyCheckKind, type Morph, type Out, type Predicate, type TypeNode, type distill, type extractIn, type extractOut, type includesMorphs, type inferMorphOut, type inferNarrow } from "@arktype/schema"; | ||
import { inferred, type ArkResult, type BaseMeta, type Morph, type Out, type Predicate, type TypeNode, type distill, type extractIn, type extractOut, type includesMorphs, type inferMorphOut, type inferNarrow } from "@arktype/schema"; | ||
import { Callable, type Constructor, type Json, type conform } from "@arktype/util"; | ||
@@ -23,6 +23,2 @@ import type { inferDefinition, validateDeclared, validateDefinition } from "./parser/definition.js"; | ||
export type DefinitionParser<$> = <def>(def: validateDefinition<def, $, bindThis<def>>) => def; | ||
export type TypeConfig = { | ||
keys?: KeyCheckKind; | ||
mustBe?: string; | ||
}; | ||
export declare class Type<t = unknown, $ = any> extends Callable<(data: unknown) => ArkResult<distill<extractOut<t>>>> { | ||
@@ -33,9 +29,9 @@ definition: unknown; | ||
infer: distill<extractOut<t>>; | ||
config: TypeConfig; | ||
root: TypeNode<t>; | ||
allows: this["root"]["allows"]; | ||
expected: string; | ||
description: string; | ||
json: Json; | ||
constructor(definition: unknown, scope: Scope); | ||
configure(config: TypeConfig): this; | ||
configure(configOrDescription: BaseMeta | string): this; | ||
describe(description: string): this; | ||
from(literal: this["in"]["infer"]): this["in"]["infer"]; | ||
@@ -56,2 +52,3 @@ and<def>(def: validateTypeRoot<def, $>): Type<inferIntersection<t, inferTypeRoot<def, $>>, $>; | ||
get out(): Type<extractOut<t>, $>; | ||
toString(): string; | ||
} | ||
@@ -58,0 +55,0 @@ export type validateTypeRoot<def, $> = validateDefinition<def, $, bindThis<def>>; |
import { inferred, keywords } from "@arktype/schema"; | ||
import { Callable, CastableBase, map } from "@arktype/util"; | ||
import { Callable, map } from "@arktype/util"; | ||
import { parseGenericParams } from "./parser/generic.js"; | ||
import { configureShallowDescendants } from "./parser/shared.js"; | ||
import { arkKind } from "./util.js"; | ||
@@ -31,6 +32,5 @@ export const createTypeParser = (scope) => { | ||
scope; | ||
config; | ||
root; | ||
allows; | ||
expected; | ||
description; | ||
json; | ||
@@ -44,10 +44,11 @@ constructor(definition, scope) { | ||
this.allows = root.allows; | ||
this.config = scope.config; | ||
this.json = root.json; | ||
this.expected = this.root.description; | ||
this.description = this.root.description; | ||
} | ||
configure(config) { | ||
this.config = { ...this.config, ...config }; | ||
return this; | ||
configure(configOrDescription) { | ||
return new Type(configureShallowDescendants(this.root, configOrDescription), this.scope); | ||
} | ||
describe(description) { | ||
return this.configure(description); | ||
} | ||
// TODO: should return out | ||
@@ -103,2 +104,5 @@ from(literal) { | ||
} | ||
toString() { | ||
return this.description; | ||
} | ||
} | ||
@@ -105,0 +109,0 @@ const parseTypeRoot = (def, scope, args) => scope.parseDefinition(def, { |
{ | ||
"name": "arktype", | ||
"description": "TypeScript's 1:1 validator, optimized from editor to runtime", | ||
"version": "2.0.0-dev.1", | ||
"version": "2.0.0-dev.2", | ||
"license": "MIT", | ||
@@ -37,5 +37,5 @@ "author": { | ||
"dependencies": { | ||
"@arktype/util": "0.0.17", | ||
"@arktype/schema": "0.0.3" | ||
"@arktype/util": "0.0.20", | ||
"@arktype/schema": "0.0.4" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { isNode, schema, type TypeNode } from "@arktype/schema" | ||
import { isNode, schema, type TypeNode, type is } from "@arktype/schema" | ||
import { | ||
@@ -7,13 +7,13 @@ isThunk, | ||
throwParseError, | ||
type Dict, | ||
type ErrorMessage, | ||
type List, | ||
type Primitive, | ||
type defined, | ||
type Dict, | ||
type equals, | ||
type ErrorMessage, | ||
type evaluate, | ||
type isAny, | ||
type isUnknown, | ||
type List, | ||
type objectKindOrDomainOf, | ||
type optionalKeyOf, | ||
type Primitive, | ||
type requiredKeyOf | ||
@@ -33,4 +33,4 @@ } from "@arktype/util" | ||
parseTuple, | ||
type TupleExpression, | ||
type inferTuple, | ||
type TupleExpression, | ||
type validateTuple | ||
@@ -76,3 +76,3 @@ } from "./tuple.js" | ||
: def extends RegExp | ||
? string | ||
? is<string, { anonymousPattern: true }> | ||
: def extends object | ||
@@ -79,0 +79,0 @@ ? inferObjectLiteral<def, $, args> |
@@ -65,9 +65,9 @@ import { keywords, schema, type Inner, type TypeNode } from "@arktype/schema" | ||
// because the currently parsed definition will overwrite them. | ||
const requiredEntriesFromSpread = (spreadNode.required ?? []).filter( | ||
(e) => !entries.some(([k]) => k === e.key) | ||
) | ||
const requiredEntriesFromSpread = ( | ||
spreadNode.props?.required ?? [] | ||
).filter((e) => !entries.some(([k]) => k === e.key)) | ||
const optionalEntriesFromSpread = (spreadNode.optional ?? []).filter( | ||
(e) => !entries.some(([k]) => k === e.key) | ||
) | ||
const optionalEntriesFromSpread = ( | ||
spreadNode.props?.optional ?? [] | ||
).filter((e) => !entries.some(([k]) => k === e.key)) | ||
@@ -74,0 +74,0 @@ required.push(...requiredEntriesFromSpread) |
@@ -12,4 +12,3 @@ import type { | ||
NumberLiteral, | ||
evaluate, | ||
extend | ||
evaluate | ||
} from "@arktype/util" | ||
@@ -135,5 +134,5 @@ import type { | ||
: token extends RegexLiteral | ||
? is<string, extend<refinements, { [_ in token]: true }>> | ||
? is<string, evaluate<refinements & { [_ in token]: true }>> | ||
: token extends DateLiteral | ||
? is<Date, extend<refinements, { [_ in token]: true }>> | ||
? is<Date, evaluate<refinements & { [_ in token]: true }>> | ||
: token extends NumberLiteral<infer value> | ||
@@ -140,0 +139,0 @@ ? value |
@@ -1,2 +0,3 @@ | ||
import type { extend } from "@arktype/util" | ||
import type { BaseMeta, Node } from "@arktype/schema" | ||
import type { evaluate } from "@arktype/util" | ||
import type { validateDefinition } from "./definition.js" | ||
@@ -20,5 +21,4 @@ import { Scanner } from "./string/shift/scanner.js" | ||
export type EntryParseResult<kind extends ParsedKeyKind = ParsedKeyKind> = | ||
extend< | ||
KeyParseResult<kind>, | ||
{ | ||
evaluate< | ||
KeyParseResult<kind> & { | ||
innerValue: unknown | ||
@@ -154,1 +154,16 @@ } | ||
}> | ||
export const configureShallowDescendants = <node extends Node>( | ||
node: node, | ||
configOrDescription: BaseMeta | string | ||
): node => { | ||
const config: BaseMeta = | ||
typeof configOrDescription === "string" | ||
? { description: configOrDescription } | ||
: (configOrDescription as never) | ||
return node.transform( | ||
(kind, inner) => ({ ...inner, ...config }), | ||
// TODO: change to props | ||
(node) => node.kind !== "required" && node.kind !== "optional" | ||
) as never | ||
} |
@@ -27,4 +27,2 @@ import type { Completion, defined, ErrorMessage } from "@arktype/util" | ||
export type AutocompletePrefix = `${StringifiablePrefixOperator} ` | ||
type BranchState = { | ||
@@ -37,2 +35,4 @@ prefixes: StringifiablePrefixOperator[] | ||
export type AutocompletePrefix = `${StringifiablePrefixOperator} ` | ||
export namespace state { | ||
@@ -39,0 +39,0 @@ export type initialize<def extends string> = from<{ |
@@ -6,2 +6,3 @@ import { | ||
type Morph, | ||
type MorphChildKind, | ||
type Out, | ||
@@ -11,3 +12,2 @@ type Predicate, | ||
type TypeNode, | ||
type ValidatorKind, | ||
type extractIn, | ||
@@ -37,2 +37,3 @@ type extractOut, | ||
import { | ||
configureShallowDescendants, | ||
parseEntry, | ||
@@ -50,3 +51,3 @@ type EntryParseResult, | ||
const props: unknown[] = [] | ||
let isVariadic = false | ||
let variadicIndex: number | undefined | ||
for (let i = 0; i < def.length; i++) { | ||
@@ -57,3 +58,6 @@ let elementDef = def[i] | ||
elementDef = elementDef.slice(3) | ||
isVariadic = true | ||
if (variadicIndex !== undefined) { | ||
return throwParseError(multipleVariadicMesage) | ||
} | ||
variadicIndex = i | ||
} else if ( | ||
@@ -65,13 +69,13 @@ isArray(elementDef) && | ||
elementDef = elementDef[1] | ||
isVariadic = true | ||
if (variadicIndex !== undefined) { | ||
return throwParseError(multipleVariadicMesage) | ||
} | ||
variadicIndex = i | ||
} | ||
const parsedEntry = parseEntry([`${i}`, elementDef]) | ||
const value = ctx.scope.parse(parsedEntry.innerValue, ctx) | ||
if (isVariadic) { | ||
if (variadicIndex === i) { | ||
if (!value.extends(keywords.Array)) { | ||
return throwParseError(writeNonArrayRestMessage(elementDef)) | ||
} | ||
if (i !== def.length - 1) { | ||
return throwParseError(prematureRestMessage) | ||
} | ||
// TODO: Fix builtins.arrayIndexTypeNode() | ||
@@ -93,3 +97,3 @@ const elementType = value.getPath() | ||
} | ||
if (!isVariadic) { | ||
if (variadicIndex === undefined) { | ||
props.push({ | ||
@@ -101,7 +105,5 @@ key: { | ||
}, | ||
// , ctx | ||
value: schema({ unit: def.length }) | ||
}) | ||
} | ||
// props , ctx | ||
return schema(Array) | ||
@@ -212,5 +214,5 @@ } | ||
export const prematureRestMessage = `Rest elements are only allowed at the end of a tuple` | ||
export const multipleVariadicMesage = `A tuple may have at most one variadic element` | ||
type prematureRestMessage = typeof prematureRestMessage | ||
type prematureRestMessage = typeof multipleVariadicMesage | ||
@@ -399,3 +401,3 @@ type inferTupleLiteral< | ||
return schema({ | ||
in: ctx.scope.parse(def[0], ctx) as Schema<ValidatorKind>, | ||
in: ctx.scope.parse(def[0], ctx) as Schema<MorphChildKind>, | ||
morph: def[2] as Morph | ||
@@ -430,5 +432,4 @@ }) | ||
const parseAttributeTuple: PostfixParser<"@"> = (def, ctx) => { | ||
return ctx.scope.parse(def[0], ctx) | ||
} | ||
const parseAttributeTuple: PostfixParser<"@"> = (def, ctx) => | ||
configureShallowDescendants(ctx.scope.parse(def[0], ctx), def[2] as never) | ||
@@ -435,0 +436,0 @@ const indexOneParsers: { |
36
scope.ts
import { | ||
BaseType, | ||
keywords, | ||
type ArkErrorCode, | ||
type KeyCheckKind, | ||
type ArkConfig, | ||
type TypeNode, | ||
@@ -50,3 +49,2 @@ type extractIn, | ||
type GenericProps, | ||
type TypeConfig, | ||
type TypeParser | ||
@@ -72,6 +70,4 @@ } from "./type.js" | ||
export type ScopeConfig = { | ||
export interface ArkScopeConfig extends ArkConfig { | ||
ambient?: Scope | null | ||
codes?: Record<ArkErrorCode, { mustBe?: string }> | ||
keys?: KeyCheckKind | ||
} | ||
@@ -233,3 +229,3 @@ | ||
config: TypeConfig | ||
config: ArkScopeConfig | ||
@@ -245,3 +241,3 @@ private parseCache: Record<string, TypeNode> = {} | ||
constructor(def: Dict, config: ScopeConfig) { | ||
constructor(def: Dict, config: ArkScopeConfig) { | ||
for (const k in def) { | ||
@@ -280,3 +276,3 @@ const parsedKey = parseScopeKey(k) | ||
def: Dict, | ||
config: TypeConfig = {} | ||
config: ArkScopeConfig = {} | ||
) => { | ||
@@ -419,24 +415,2 @@ return new Scope(def, { | ||
compile() { | ||
return "" | ||
// this.export() | ||
// const references: Set<Root> = new Set() | ||
// for (const k in this.exportedResolutions!) { | ||
// const resolution = this.exportedResolutions[k] | ||
// if (hasArkKind(resolution, "node") && !references.has(resolution)) { | ||
// for (const reference of resolution.references) { | ||
// references.add(reference) | ||
// } | ||
// } | ||
// } | ||
// return [...references] | ||
// .map( | ||
// (ref) => `const ${ref.alias} = (${In}) => { | ||
// ${ref.condition} | ||
// return true | ||
// }` | ||
// ) | ||
// .join("\n") | ||
} | ||
bindThis() { | ||
@@ -443,0 +417,0 @@ // TODO: fix |
30
type.ts
@@ -6,3 +6,2 @@ import { | ||
type BaseMeta, | ||
type KeyCheckKind, | ||
type Morph, | ||
@@ -21,3 +20,2 @@ type Out, | ||
Callable, | ||
CastableBase, | ||
map, | ||
@@ -38,2 +36,3 @@ type Constructor, | ||
import type { inferIntersection } from "./parser/semantic/intersections.js" | ||
import { configureShallowDescendants } from "./parser/shared.js" | ||
import type { | ||
@@ -127,7 +126,2 @@ IndexOneOperator, | ||
export type TypeConfig = { | ||
keys?: KeyCheckKind | ||
mustBe?: string | ||
} | ||
export class Type<t = unknown, $ = any> extends Callable< | ||
@@ -140,6 +134,5 @@ (data: unknown) => ArkResult<distill<extractOut<t>>> | ||
config: TypeConfig | ||
root: TypeNode<t> | ||
allows: this["root"]["allows"] | ||
expected: string | ||
description: string | ||
json: Json | ||
@@ -155,12 +148,17 @@ | ||
this.allows = root.allows | ||
this.config = scope.config | ||
this.json = root.json | ||
this.expected = this.root.description | ||
this.description = this.root.description | ||
} | ||
configure(config: TypeConfig) { | ||
this.config = { ...this.config, ...config } | ||
return this | ||
configure(configOrDescription: BaseMeta | string): this { | ||
return new Type( | ||
configureShallowDescendants(this.root, configOrDescription), | ||
this.scope | ||
) as never | ||
} | ||
describe(description: string): this { | ||
return this.configure(description) | ||
} | ||
// TODO: should return out | ||
@@ -261,2 +259,6 @@ from(literal: this["in"]["infer"]) { | ||
} | ||
toString() { | ||
return this.description | ||
} | ||
} | ||
@@ -263,0 +265,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
694530
212
15155
+ Added@arktype/schema@0.0.4(transitive)
+ Added@arktype/util@0.0.20(transitive)
- Removed@arktype/schema@0.0.3(transitive)
- Removed@arktype/util@0.0.17(transitive)
Updated@arktype/schema@0.0.4
Updated@arktype/util@0.0.20