diff --git a/ark/dark/injected.tmLanguage.json b/ark/dark/injected.tmLanguage.json
index 41443833bc..1ea08f9f22 100644
--- a/ark/dark/injected.tmLanguage.json
+++ b/ark/dark/injected.tmLanguage.json
@@ -11,7 +11,7 @@
"repository": {
"arkDefinition": {
"contentName": "meta.embedded.arktype.definition",
- "begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when))(\\()",
+ "begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when|\\.extends|\\.intersect))(\\()",
"beginCaptures": {
"1": {
"name": "entity.name.function.ts"
diff --git a/ark/dark/package.json b/ark/dark/package.json
index cb962960e2..32d8cddce8 100644
--- a/ark/dark/package.json
+++ b/ark/dark/package.json
@@ -2,7 +2,7 @@
"name": "arkdark",
"displayName": "ArkDark",
"description": "ArkType syntax highlighting and themeāµ",
- "version": "5.1.6",
+ "version": "5.1.7",
"publisher": "arktypeio",
"type": "module",
"scripts": {
diff --git a/ark/dark/tsWithArkType.tmLanguage.json b/ark/dark/tsWithArkType.tmLanguage.json
index d2ee4687cf..99018645a0 100644
--- a/ark/dark/tsWithArkType.tmLanguage.json
+++ b/ark/dark/tsWithArkType.tmLanguage.json
@@ -22,7 +22,7 @@
"repository": {
"arkDefinition": {
"contentName": "meta.embedded.arktype.definition",
- "begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when))(\\()",
+ "begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when|\\.extends|\\.intersect))(\\()",
"beginCaptures": {
"1": {
"name": "entity.name.function.ts"
diff --git a/ark/docs/astro.config.js b/ark/docs/astro.config.js
index 3973a852a6..d24d77f9ce 100644
--- a/ark/docs/astro.config.js
+++ b/ark/docs/astro.config.js
@@ -3,7 +3,7 @@
import react from "@astrojs/react"
import starlight from "@astrojs/starlight"
import { defineConfig } from "astro/config"
-import { shikiConfig } from "./shiki.config.js"
+import { shikiConfig } from "./src/components/shiki.config.js"
// https://astro.build/config
export default defineConfig({
diff --git a/ark/docs/shiki.config.js b/ark/docs/shiki.config.js
deleted file mode 100644
index a8d90fb4c9..0000000000
--- a/ark/docs/shiki.config.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// @ts-check
-
-import { transformerTwoslash } from "@shikijs/twoslash"
-import arkdarkColors from "arkdark/color-theme.json"
-import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json"
-import { defaultCompilerOptions } from "twoslash"
-
-// Theme adjustments
-
-arkdarkColors.colors["editor.background"] = "#00000027"
-// @ts-expect-error
-arkdarkColors.tokenColors.push({
- // this is covered by editorBracketHighlight.foreground1 etc. in VSCode,
- // but it's not available in Shiki so add a replacement
- scope: ["meta.brace"],
- settings: {
- foreground: "#f5cf8f"
- }
-})
-
-const twoslashPropertyPrefix = "(property) "
-
-export const twoslash = transformerTwoslash({
- langs: ["ts", "js"],
- twoslashOptions: {
- compilerOptions: {
- ...defaultCompilerOptions,
- exactOptionalPropertyTypes: true
- },
- filterNode: node => {
- if (node.type !== "hover") return true
- if (node.text.startsWith("const")) {
- if (node.text.endsWith(", {}>"))
- // omit default scope param from type display
- node.text = node.text.slice(0, -5) + ">"
- // filter out the type of Type's invocation
- // as opposed to the Type itself
- return !node.text.includes("(data: unknown)")
- }
- if (node.text.startsWith(twoslashPropertyPrefix)) {
- const expression = node.text.slice(twoslashPropertyPrefix.length)
- if (expression.startsWith("ArkErrors.summary"))
- // this helps demonstrate narrowing on discrimination
- return true
- if (expression.endsWith("typeof ArkErrors"))
- // also helps clarify how discrimination works
- return true
- return false
- }
- return false
- }
- }
-})
-
-/** @type { import("astro").ShikiConfig } */
-export const shikiConfig = {
- theme: arkdarkColors,
- // @ts-expect-error
- langs: [arktypeTextmate],
- transformers: [twoslash]
-}
diff --git a/ark/docs/src/assets/HowToUpdateArktypeGifs.md b/ark/docs/src/assets/HowToUpdateArktypeGifs.md
deleted file mode 100644
index 895dbc276a..0000000000
--- a/ark/docs/src/assets/HowToUpdateArktypeGifs.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# How to update these GIFs
-
-1. Used modified VSCode:
- - Deleted fileName navbar and added `margin-top: 24px` to the editor from devtools within VsCode
- - Enabled Quokka, but disabled "Show Expression Value on Select" (this has to be done every time Quokka is restarted)
- - Applied these settings:
-
-```ts
-// DEMO ONLY (revert)
-// previously 225
-"glassit.alpha": 255,
-// previously 1
-"window.zoomLevel": 2,
-// previously smooth
-"editor.cursorBlinking": "solid",
-"scm.diffDecorations": "none",
-"breadcrumbs.enabled": false,
-"editor.minimap.enabled": false,
-"editor.scrollBeyondLastLine": false,
-// "typescript.suggest.enabled": false,
-```
-
-2. Once finished recording, generate a high-quality MP4 from ClipChamp called `arktype.mp4`. This MP4 should have a #1b1b1b background, and can be used directly on arktype.io.
-3. Import the new MP4 into your existing VSDC project
-4. Generate `arktype.apng` as a 1750x750 100% quality MP4/MOV/APNG etc. based on the existing project (has transparency filters etc.). The GitHub version should have a background of #0d1117, exactly matching GitHub in dark mode. You should experiment with multiple output formats until you ensure that #0d1117 is exactly the background. E.g. for GIFs, you can check the palette to ensure it is there ahead of time.
-5. Check `arktype.apng` to ensure it is exactly #0d1117, then save the VSDC project
-6. Run `arktype.apng` through [GifTuna](https://github.com/dudewheresmycode/giftuna) with "Dither" disabled. Ensure the output background is still the same and that the GIF is ~20MB, and name the result `arktype.gif`.
-7. Replace `arktype.mp4` and `arktype.gif` in (repo-root)/dev/arktype.io/static/img.
diff --git a/ark/docs/src/components/BenchmarksGraph.tsx b/ark/docs/src/components/BenchmarksGraph.tsx
new file mode 100644
index 0000000000..b66db13d25
--- /dev/null
+++ b/ark/docs/src/components/BenchmarksGraph.tsx
@@ -0,0 +1,99 @@
+import React from "react"
+
+const barStyles: React.CSSProperties = {
+ height: "30px",
+ borderRadius: "5px",
+ display: "flex",
+ alignItems: "baseline",
+ marginRight: "1rem",
+ color: "black"
+}
+
+const arkBarStyles = {
+ ...barStyles,
+ background:
+ "repeating-linear-gradient(135deg, #40decc, #40decc 10px, #34c8b9 10px, #34c8b9 20px)"
+}
+
+const zodBarStyles = {
+ ...barStyles,
+ background:
+ "repeating-linear-gradient(135deg, #b084f6, #b084f6 10px, #9a6fe3 10px, #9a6fe3 20px)"
+}
+
+export const RuntimeBenchmarksGraph = () => (
+
+
+
+ ArkType (68,581,169 ops)
+
+
+
+)
+
+export const TypeBenchmarksGraph = () => (
+
+
+
+ Union Type Instantiations, TypeScript 5.4.5 (
+
+ source
+
+ )
+
+
+
+
+ ArkType Auto-Discriminated (7,801)
+
+
+
+ Zod Discriminated (71,312)
+
+
+)
diff --git a/ark/docs/src/components/highlight.ts b/ark/docs/src/components/highlight.ts
index c9b5a1551f..9b01c79cea 100644
--- a/ark/docs/src/components/highlight.ts
+++ b/ark/docs/src/components/highlight.ts
@@ -1,7 +1,7 @@
import arkdarkColors from "arkdark/color-theme.json"
import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json"
import { getHighlighter } from "shiki"
-import { twoslash } from "../../shiki.config.js"
+import { twoslash } from "./shiki.config.js"
let highlighter: Awaited> | undefined
diff --git a/ark/docs/src/components/shiki.config.js b/ark/docs/src/components/shiki.config.js
new file mode 100644
index 0000000000..8cb3314b91
--- /dev/null
+++ b/ark/docs/src/components/shiki.config.js
@@ -0,0 +1,109 @@
+// @ts-check
+
+import { transformerTwoslash } from "@shikijs/twoslash"
+import arkdarkColors from "arkdark/color-theme.json"
+import arkdarkPackageJson from "arkdark/package.json"
+import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json"
+import { defaultCompilerOptions } from "twoslash"
+
+// Theme adjustments
+
+arkdarkColors.colors["editor.background"] = "#00000027"
+// @ts-expect-error
+arkdarkColors.tokenColors.push({
+ // this is covered by editorBracketHighlight.foreground1 etc. in VSCode,
+ // but it's not available in Shiki so add a replacement
+ scope: ["meta.brace"],
+ settings: {
+ foreground: "#f5cf8f"
+ }
+})
+
+const twoslashPropertyPrefix = "(property) "
+
+export const twoslash = transformerTwoslash({
+ langs: ["ts", "js"],
+ twoslashOptions: {
+ compilerOptions: {
+ ...defaultCompilerOptions,
+ exactOptionalPropertyTypes: true
+ },
+ filterNode: node => {
+ switch (node.type) {
+ case "hover":
+ if (node.text.startsWith("const")) {
+ if (node.text.endsWith(", {}>"))
+ // omit default scope param from type display
+ node.text = node.text.slice(0, -5) + ">"
+ // show type with completions populated for known examples
+ node.text = node.text.replace(
+ "isAdmin?: never",
+ "isAdmin?: boolean | null"
+ )
+ node.text = node.text.replace(
+ "luckyNumbers: never",
+ "luckyNumbers: (number | bigint)[]"
+ )
+ // filter out the type of Type's invocation
+ // as opposed to the Type itself
+ return !node.text.includes("(data: unknown)")
+ }
+ if (node.text.startsWith(twoslashPropertyPrefix)) {
+ const expression = node.text.slice(twoslashPropertyPrefix.length)
+ if (expression.startsWith("ArkErrors.summary")) {
+ // cleanup runtime errors for display
+ const runtimeErrorSummary = /^ArkErrors\.summary: "(.*)"/.exec(
+ expression
+ )
+ if (runtimeErrorSummary) {
+ node.text = runtimeErrorSummary[1].split("\\n").join("\n")
+ }
+ // this helps demonstrate narrowing on discrimination
+ return true
+ }
+ if (expression === "luckyNumbers: (number | bigint)[]")
+ // this helps demonstrate narrowing on discrimination
+ return true
+ if (expression.endsWith("typeof ArkErrors"))
+ // also helps clarify how discrimination works
+ return true
+ return false
+ }
+ return false
+ case "error":
+ // adapted from my ErrorLens implementation at
+ // https://github.com/usernamehw/vscode-error-lens/blob/d1786ddeedee23d70f5f75b16415a6579b554b59/src/utils/extUtils.ts#L127
+ for (const transformation of arkdarkPackageJson.contributes
+ .configurationDefaults["errorLens.replace"]) {
+ const regex = new RegExp(transformation.matcher)
+ const matchResult = regex.exec(node.text)
+ if (matchResult) {
+ node.text = transformation.message
+ // Replace groups like $0 and $1 with groups from the match
+ for (
+ let groupIndex = 0;
+ groupIndex < matchResult.length;
+ groupIndex++
+ ) {
+ node.text = node.text.replace(
+ new RegExp(`\\$${groupIndex}`, "gu"),
+ matchResult[Number(groupIndex)]
+ )
+ }
+ node.text = `TypeScript: ${node.text}`
+ }
+ }
+ default:
+ return true
+ }
+ }
+ }
+})
+
+/** @type { import("astro").ShikiConfig } */
+export const shikiConfig = {
+ theme: arkdarkColors,
+ // @ts-expect-error
+ langs: [arktypeTextmate],
+ transformers: [twoslash]
+}
diff --git a/ark/docs/src/content/docs/betterErrors.twoslash.ts b/ark/docs/src/content/docs/betterErrors.twoslash.ts
new file mode 100644
index 0000000000..719e279d8b
--- /dev/null
+++ b/ark/docs/src/content/docs/betterErrors.twoslash.ts
@@ -0,0 +1,29 @@
+import { type } from "arktype"
+
+const user = type({
+ name: "string",
+ luckyNumbers: "(number | bigint)[]",
+ "isAdmin?": "boolean | null"
+})
+
+// ---cut---
+const out = user({
+ luckyNumbers: [31, "255", 1337n],
+ isAdmin: 1
+})
+
+if (out instanceof type.errors) {
+ // ---cut-start---
+ // just a trick to display the runtime error
+ if (
+ out.summary !==
+ `luckyNumbers[1] must be a bigint or a number (was string)
+name must be a string (was missing)
+isAdmin must be false, null or true (was 1)`
+ )
+ throw new Error()
+
+ // ---cut-end---
+ // hover out.summary to see validation errors
+ console.error(out.summary)
+} else console.log(out.luckyNumbers)
diff --git a/ark/docs/src/content/docs/clearAndConcise.twoslash.ts b/ark/docs/src/content/docs/clearAndConcise.twoslash.ts
new file mode 100644
index 0000000000..cbda5e6277
--- /dev/null
+++ b/ark/docs/src/content/docs/clearAndConcise.twoslash.ts
@@ -0,0 +1,9 @@
+// @errors: 2322
+import { type } from "arktype"
+// ---cut---
+// hover me
+const user = type({
+ name: "string",
+ luckyNumbers: "number | bigint)[]",
+ "isAdmin?": "boolean | null"
+})
diff --git a/ark/docs/src/content/docs/deepIntrospectability.twoslash.ts b/ark/docs/src/content/docs/deepIntrospectability.twoslash.ts
new file mode 100644
index 0000000000..844f8cae8b
--- /dev/null
+++ b/ark/docs/src/content/docs/deepIntrospectability.twoslash.ts
@@ -0,0 +1,26 @@
+import { type } from "arktype"
+
+const user = type({
+ name: "string",
+ luckyNumbers: "(number | bigint)[]",
+ "isAdmin?": "boolean | null"
+})
+
+// ---cut---
+user.extends("object") // true
+user.extends("string") // false
+// ---cut-start---
+// prettier-ignore
+// ---cut-end---
+user.extends({ // true
+ luckyNumbers: "unknown[]",
+})
+// ---cut-start---
+// prettier-ignore
+// ---cut-end---
+user.extends({ // false
+ luckyNumbers: "number[]"
+})
+
+// get transitively referenced TypeNodes
+console.log(user.raw.references)
diff --git a/ark/docs/src/content/docs/index.mdx b/ark/docs/src/content/docs/index.mdx
index 2ea6642a10..96377abecb 100644
--- a/ark/docs/src/content/docs/index.mdx
+++ b/ark/docs/src/content/docs/index.mdx
@@ -18,8 +18,18 @@ hero:
icon: external
---
-import { Card, CardGrid } from "@astrojs/starlight/components"
+import { Card, CardGrid, TabItem, Tabs } from "@astrojs/starlight/components"
+import {
+ RuntimeBenchmarksGraph,
+ TypeBenchmarksGraph
+} from "../../components/BenchmarksGraph.tsx"
import { HeroBackdrop } from "../../components/HeroBackdrop.tsx"
+import TsBlock from "../../components/TsBlock.astro"
+import betterErrorsSrc from "./betterErrors.twoslash.ts?raw"
+import clearAndConciseSrc from "./clearAndConcise.twoslash.ts?raw"
+import deepIntrospectability from "./deepIntrospectability.twoslash.ts?raw"
+import intrinsicOptimization from "./intrinsicOptimization.twoslash.ts?raw"
+import unparalleledDxSrc from "./unparalleledDx.twoslash.ts?raw"
@@ -29,27 +39,40 @@ import { HeroBackdrop } from "../../components/HeroBackdrop.tsx"
- Type syntax you already know with safety unlike anything you've ever seen
+ Type syntax you already know with safety and completions unlike anything
+ you've ever seen
+
-
- [100x faster than
- Zod](https://moltar.github.io/typescript-runtime-type-benchmarks/) with
- editor performance that will remind you how autocomplete is supposed to feel
+
+ 100x faster than Zod at runtime with editor performance that will remind you
+ how autocomplete is supposed to feel
+
+
+
+
+
+
+
+
-
- Schemas are half as long and twice as readable with hovers that tell you
- just what really matters
+
+ Definitions are half as long, type errors are twice as readable, and hovers
+ tell you just what really matters
+
-
- Deeply customizable and composable messages with great defaults
+
+ Deeply customizable messages with great defaults
+
-
+
ArkType uses set theory to understand and expose the relationships between
your types at runtime the way TypeScript does at compile time
+
-
- Every schema you define is internally optimized to provide the clearest,
- fastest validation possible
+
+ Every schema is internally normalized and reduced to its purest and fastest
+ representation
+
{/*
Most definitions are just objects and strings- take them across the stack or
diff --git a/ark/docs/src/content/docs/intrinsicOptimization.twoslash.ts b/ark/docs/src/content/docs/intrinsicOptimization.twoslash.ts
new file mode 100644
index 0000000000..c8fbcfb6be
--- /dev/null
+++ b/ark/docs/src/content/docs/intrinsicOptimization.twoslash.ts
@@ -0,0 +1,15 @@
+import { type } from "arktype"
+// ---cut---
+// all unions are optimally discriminated-
+// even at nested paths or in multiple passes!
+const account = type({
+ kind: "'admin'",
+ "powers?": "string[]"
+})
+ .or({
+ kind: "'superadmin'",
+ "superpowers?": "string[]"
+ })
+ .or({
+ kind: "'pleb'"
+ })
diff --git a/ark/docs/src/content/docs/unparalleledDx.twoslash.ts b/ark/docs/src/content/docs/unparalleledDx.twoslash.ts
new file mode 100644
index 0000000000..4536595573
--- /dev/null
+++ b/ark/docs/src/content/docs/unparalleledDx.twoslash.ts
@@ -0,0 +1,12 @@
+import { type } from "arktype"
+// ---cut---
+// hover me
+const user = type({
+ name: "string",
+ luckyNumbers: "number[]",
+ // ---cut-start---
+ // @ts-expect-error
+ // ---cut-end---
+ "isAdmin?": "boolean | n"
+ // ^|
+})
diff --git a/ark/docs/src/styles.css b/ark/docs/src/styles.css
index af3bec6d9f..abd2c04277 100644
--- a/ark/docs/src/styles.css
+++ b/ark/docs/src/styles.css
@@ -26,6 +26,9 @@
--sl-color-gray-5: #003b62;
--sl-color-gray-6: #002946;
--sl-color-black: #001a2f;
+ /* Based on ArkDark ErrorLens */
+ --ark-error: #9558f8a0;
+ --ark-success: #40decca0;
/** @shikijs/twoslash/style-rich.css overrides */
--twoslash-border-color: #ba7e4127;
--twoslash-underline-color: currentColor;
@@ -42,9 +45,9 @@
--twoslash-code-font-size: 1em;
--twoslash-matched-color: inherit;
--twoslash-unmatched-color: #888;
- --twoslash-cursor-color: #8888;
- --twoslash-error-color: #d45656;
- --twoslash-error-bg: #d4565620;
+ --twoslash-cursor-color: var(--sl-color-gray-2);
+ --twoslash-error-color: var(--ark-error);
+ --twoslash-error-bg: #9558f818;
--twoslash-warn-color: #c37d0d;
--twoslash-warn-bg: #c37d0d20;
--twoslash-tag-color: #3772cf;
@@ -71,7 +74,9 @@
--sl-color-black: #fffff0;
}
-pre.astro-code {
+pre.astro-code,
+pre.shiki,
+.twoslash-popup-container {
border-radius: 1rem;
border-color: #ba7e4127;
border-width: 1px;
@@ -92,17 +97,18 @@ starlight-theme-select,
display: none;
}
-.twoslash .twoslash-hover:hover .twoslash-popup-container {
+.twoslash .twoslash-hover:hover .twoslash-popup-container,
+.twoslash .twoslash-completion-cursor .twoslash-completion-list {
border-radius: 1rem;
opacity: 1;
- backdrop-filter: blur(10px);
- border: 1px solid #ba7e4127;
+ background: #001323aa;
+ backdrop-filter: blur(8px);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
-/* FireFox specific rules */
+/* Firefox specific rules */
@-moz-document url-prefix() {
- /* The backdrop-filter above doesn't work by default yet on FireFox so we do this instead */
+ /* The backdrop-filter above doesn't work by default yet on Firefox so we do this instead */
.twoslash .twoslash-hover:hover .twoslash-popup-container {
background: #001323ee;
}
diff --git a/ark/docs/tsconfig.json b/ark/docs/tsconfig.json
index 56a73e3ff4..67410a862b 100644
--- a/ark/docs/tsconfig.json
+++ b/ark/docs/tsconfig.json
@@ -1,6 +1,11 @@
{
- "extends": ["astro/tsconfigs/strict"],
+ "extends": "../../tsconfig.json",
"compilerOptions": {
- "jsx": "react"
- }
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "allowJs": true,
+ "jsx": "preserve"
+ },
+ "exclude": ["**/out", "**/node_modules"]
}
diff --git a/ark/repo/.eslintrc.cjs b/ark/repo/.eslintrc.cjs
index 9f1b9711ab..57a30496cf 100644
--- a/ark/repo/.eslintrc.cjs
+++ b/ark/repo/.eslintrc.cjs
@@ -147,7 +147,12 @@ module.exports = defineConfig({
}
},
{
- files: ["**/__tests__/**", "**/*.bench.ts", "**/*.test.ts"],
+ files: [
+ "**/__tests__/**",
+ "**/*.bench.ts",
+ "**/*.test.ts",
+ "**/*.twoslash.ts"
+ ],
rules: {
// Assignment to a variable is required to ensure types are parsed
"@typescript-eslint/no-unused-vars": "off",
diff --git a/ark/repo/package.json b/ark/repo/package.json
index fdee6358bb..c5482311e5 100644
--- a/ark/repo/package.json
+++ b/ark/repo/package.json
@@ -6,7 +6,8 @@
"arktype": "workspace:*",
"@arktype/attest": "workspace:*",
"@arktype/fs": "workspace:*",
- "ts-morph": "20.0.0"
+ "ts-morph": "20.0.0",
+ "zod": "3.23.8"
},
"scripts": {
"build": "echo No build required!"
diff --git a/ark/repo/scratch/discriminatedComparison.ts b/ark/repo/scratch/discriminatedComparison.ts
index 30fac84d8d..aedab5885f 100644
--- a/ark/repo/scratch/discriminatedComparison.ts
+++ b/ark/repo/scratch/discriminatedComparison.ts
@@ -1,6 +1,8 @@
-// import z from "zod"
import { bench } from "@arktype/attest"
import { type } from "arktype"
+import z from "zod"
+
+/** Measured with typescript@5.4.5, zod@3.23.8, arktype@2.0.0-dev.16 */
bench("arktype", () => {
// Union is automatically discriminated using shallow or deep keys
@@ -15,21 +17,37 @@ bench("arktype", () => {
.or({
kind: "'pleb'"
})
-}).types([4182, "instantiations"])
+}).types([7801, "instantiations"])
+
+bench("zod", () => {
+ const user = z.union([
+ z.object({
+ kind: z.literal("admin"),
+ powers: z.string().array().optional()
+ }),
+ z.object({
+ kind: z.literal("superadmin"),
+ superpowers: z.string().array().optional()
+ }),
+ z.object({
+ kind: z.literal("pleb")
+ })
+ ])
+}).types([24944, "instantiations"])
-// bench("zod", () => {
-// // Union must be manually discriminated using only shallow keys
-// const user = z.discriminatedUnion("kind", [
-// z.object({
-// kind: z.literal("admin"),
-// powers: z.string().array().optional()
-// }),
-// z.object({
-// kind: z.literal("superadmin"),
-// superpowers: z.string().array().optional()
-// }),
-// z.object({
-// kind: z.literal("pleb")
-// })
-// ])
-// }).types([86522, "instantiations"])
+bench("zod discriminated", () => {
+ // Union must be manually discriminated using only shallow keys
+ const user = z.discriminatedUnion("kind", [
+ z.object({
+ kind: z.literal("admin"),
+ powers: z.string().array().optional()
+ }),
+ z.object({
+ kind: z.literal("superadmin"),
+ superpowers: z.string().array().optional()
+ }),
+ z.object({
+ kind: z.literal("pleb")
+ })
+ ])
+}).types([71312, "instantiations"])
diff --git a/ark/repo/scratch/realWorldComparison.ts b/ark/repo/scratch/realWorldComparison.ts
index eb601d102d..885228ba96 100644
--- a/ark/repo/scratch/realWorldComparison.ts
+++ b/ark/repo/scratch/realWorldComparison.ts
@@ -1,4 +1,3 @@
-// @ts-nocheck
import { bench } from "@arktype/attest"
import { scope, type } from "arktype"
import { z } from "zod"
diff --git a/ark/schema/shared/errors.ts b/ark/schema/shared/errors.ts
index 4839a03ef4..0dffe32bb7 100644
--- a/ark/schema/shared/errors.ts
+++ b/ark/schema/shared/errors.ts
@@ -128,7 +128,7 @@ export class ArkErrors extends ReadonlyArray {
}
throw(): never {
- throw this
+ throw new AggregateError(this, this.message)
}
}
diff --git a/ark/type/CHANGELOG.md b/ark/type/CHANGELOG.md
index 9f4fac5d68..1bb9241e91 100644
--- a/ark/type/CHANGELOG.md
+++ b/ark/type/CHANGELOG.md
@@ -1,5 +1,9 @@
# arktype
+## 2.0.0-dev.17
+
+- Error thrown by `.assert` or `out.throw()` is now an instance of [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError), with the cause being an `ArkErrors` array.
+
## 2.0.0-dev.16
- Fix an incorrect return value on pipe sequences like the following:
diff --git a/ark/type/__tests__/traverse.test.ts b/ark/type/__tests__/traverse.test.ts
index 9d5531776b..7a5829b15b 100644
--- a/ark/type/__tests__/traverse.test.ts
+++ b/ark/type/__tests__/traverse.test.ts
@@ -146,6 +146,24 @@ contextualize(() => {
)
})
+ it("homepage example", () => {
+ const user = type({
+ name: "string",
+ luckyNumbers: "(number | bigint)[]",
+ "isAdmin?": "boolean | null"
+ })
+
+ const out = user({
+ luckyNumbers: [31, "255", 1337n],
+ isAdmin: 1
+ })
+
+ attest(out.toString())
+ .snap(`luckyNumbers[1] must be a bigint or a number (was string)
+name must be a string (was missing)
+isAdmin must be false, null or true (was 1)`)
+ })
+
it("relative path", () => {
const signup = type({
email: "email",
diff --git a/ark/type/__tests__/type.test.ts b/ark/type/__tests__/type.test.ts
index 45b08aa851..408b32864e 100644
--- a/ark/type/__tests__/type.test.ts
+++ b/ark/type/__tests__/type.test.ts
@@ -27,9 +27,18 @@ contextualize(() => {
const result = t("invalid")
attest(result instanceof type.errors && result.throw())
} catch (e) {
- attest(e instanceof type.errors).equals(true)
+ attest(e instanceof AggregateError).equals(true)
+ attest((e as AggregateError).errors instanceof type.errors)
return
}
throw new AssertionError({ message: "Expected to throw" })
})
+
+ it("assert", () => {
+ const t = type({ a: "string" })
+ attest(t.assert({ a: "1" })).equals({ a: "1" })
+ attest(() => t.assert({ a: 1 })).throws.snap(
+ "AggregateError: a must be a string (was number)"
+ )
+ })
})
diff --git a/ark/util/tsconfig.base.json b/ark/util/tsconfig.base.json
index 4db01a8762..f5e3d32853 100644
--- a/ark/util/tsconfig.base.json
+++ b/ark/util/tsconfig.base.json
@@ -12,6 +12,7 @@
"esModuleInterop": true,
"resolveJsonModule": true,
"exactOptionalPropertyTypes": true,
+ "forceConsistentCasingInFileNames": true,
"stripInternal": true
}
}