diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index e31e961b2d..445072023f 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -4,6 +4,7 @@ description: Install dependencies and perform setup for https://github.com/arkty inputs: node: default: lts/* + description: Node version to use runs: using: composite diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2e4768a3ed..f4a391bd52 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,12 +23,13 @@ jobs: - name: Setup repo uses: ./.github/actions/setup - - name: Install, build, and upload your site - uses: withastro/action@v2 + - name: Build docs + run: pnpm buildDocs + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 with: - path: ./ark/docs # The root location of your Astro project inside the repository. (optional) - # node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional) - package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) + path: ./ark/docs/out deploy: needs: update-gh-pages diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..3bd3b7de7d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +shell-emulator=true diff --git a/.vscode/settings.json b/.vscode/settings.json index 23a038b45a..f33395de35 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.prettierPath": "./node_modules/prettier", "biome.enabled": false, "editor.codeActionsOnSave": [ "editor.formatOnSave", diff --git a/ark/attest/__tests__/demo.test.ts b/ark/attest/__tests__/demo.test.ts index c9ac3d59e0..64b72b058e 100644 --- a/ark/attest/__tests__/demo.test.ts +++ b/ark/attest/__tests__/demo.test.ts @@ -46,9 +46,7 @@ contextualize(() => { it("type and value assertions", () => { const even = type("number%2") - // asserts even.infer is exactly number - attest(even.infer) - // make assertions about types and values seamlessly + // snapshot types and values seamlessly attest(even.infer).type.toString.snap("number") // including object literals- no more long inline strings! attest(even.json).snap({ domain: "number", divisor: 2 }) diff --git a/ark/attest/__tests__/snapPopulation.test.ts b/ark/attest/__tests__/snapPopulation.test.ts index aa8bade321..1c6b461ed7 100644 --- a/ark/attest/__tests__/snapPopulation.test.ts +++ b/ark/attest/__tests__/snapPopulation.test.ts @@ -10,7 +10,7 @@ contextualize(() => { fromHere("benchExpectedOutput.ts") ).replaceAll("\r\n", "\n") equal(actual, expectedOutput) - }).timeout(20000) + }).timeout(60000) it("snap populates file", () => { const actual = runThenGetContents(fromHere("snapTemplate.ts")) @@ -18,5 +18,5 @@ contextualize(() => { fromHere("snapExpectedOutput.ts") ).replaceAll("\r\n", "\n") equal(actual, expectedOutput) - }).timeout(20000) + }).timeout(60000) }) diff --git a/ark/attest/package.json b/ark/attest/package.json index 70c2499b2e..8c15621b3e 100644 --- a/ark/attest/package.json +++ b/ark/attest/package.json @@ -1,6 +1,6 @@ { "name": "@ark/attest", - "version": "0.32.0", + "version": "0.33.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/docs/CNAME b/ark/docs/CNAME deleted file mode 100644 index 4b2f400576..0000000000 --- a/ark/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -arktype.io \ No newline at end of file diff --git a/ark/docs/README.md b/ark/docs/README.md index b3a63033f9..013629ab2c 100644 --- a/ark/docs/README.md +++ b/ark/docs/README.md @@ -1,115 +1,26 @@ -# arktype.io +# my-app -Source code for ArkType's docs at [arktype.io](https://arktype.io) +This is a Next.js application generated with +[Create Fumadocs](https://github.com/fuma-nama/fumadocs). -Built with [Starlight](https://starlight.astro.build/) +Run development server: -# Definitions +```bash +npm run dev +# or +pnpm dev +# or +yarn dev +``` -- Primitives +Open http://localhost:3000 with your browser to see the result. - - string - - keywords - - Autogenerate from JSDoc - - literals - - patterns - - lengths - - number - - keywords - - Autogenerate from JSDoc - - literals - - ranges - - divisors - - more - - bigint - - boolean - - symbol - - null - - undefined +## Learn More -- Objects +To learn more about Next.js and Fumadocs, take a look at the following +resources: - - properties - - required - - optional - - defaultable - - index - - undeclared - - more - - merge - - keyof - - get - - map - - arrays - - lengths - - tuples - - prefix - - optional - - variadic - - postfix - - dates - - keywords - - Autogenerate from JSDoc - - literals - - ranges - - instanceOf - - keywords - - Autogenerate from JSDoc - -- Expressions - - - intersection - - union - - brand - - narrow - - morph - - more - - unit - - enumerated - - meta - - cast - - parenthetical - - this - -# Other stuff - -- Types (how you can use existing types) - - - Top-level type invocation - - - Autogenerate from JSDoc - - - define - - raw - -- Configuration - - - errors - - clone - - onUndeclaredKey - - jitless - -- Scopes (advanced) - - - syntax - - modules - - visibility - - submodules - - thunks - -- Generics (advanced) - - - keywords - - Autogenerate from JSDoc - - syntax - - hkt (advanced++) - -- Integrations - - - Standard Schema - - tRPC - - react-hook-form - - hono - -- FAQ -- About the project +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js + features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +- [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs diff --git a/ark/fuma/app/(home)/layout.tsx b/ark/docs/app/(home)/layout.tsx similarity index 100% rename from ark/fuma/app/(home)/layout.tsx rename to ark/docs/app/(home)/layout.tsx diff --git a/ark/fuma/app/(home)/page.tsx b/ark/docs/app/(home)/page.tsx similarity index 97% rename from ark/fuma/app/(home)/page.tsx rename to ark/docs/app/(home)/page.tsx index bb9a56b7d9..828cc085da 100644 --- a/ark/fuma/app/(home)/page.tsx +++ b/ark/docs/app/(home)/page.tsx @@ -24,7 +24,7 @@ export default () => ( Type syntax you already know with safety and completions unlike anything you've ever seen

- + }>

diff --git a/ark/fuma/app/api/search/route.ts b/ark/docs/app/api/search/route.ts similarity index 79% rename from ark/fuma/app/api/search/route.ts rename to ark/docs/app/api/search/route.ts index 3d5c2e7a04..efb64311c5 100644 --- a/ark/fuma/app/api/search/route.ts +++ b/ark/docs/app/api/search/route.ts @@ -1,5 +1,5 @@ import { createFromSource } from "fumadocs-core/search/server" -import { source } from "../../../lib/source.ts" +import { source } from "../../../lib/source.tsx" // it should be cached forever export const revalidate = false export const { staticGET: GET } = createFromSource(source) diff --git a/ark/docs/app/docs-og/[...slug]/route.tsx b/ark/docs/app/docs-og/[...slug]/route.tsx new file mode 100644 index 0000000000..04249187ba --- /dev/null +++ b/ark/docs/app/docs-og/[...slug]/route.tsx @@ -0,0 +1,28 @@ +import { generateOGImage } from "fumadocs-ui/og" +import { metadataImage } from "../../../lib/metadata" + +export const GET = metadataImage.createAPI(page => + generateOGImage({ + title: page.data.title === "ArkType" ? "ArkType" : "ArkType Docs", + description: page.data.title === "ArkType" ? "" : page.data.title, + site: "ArkType" + // font: { + // title: { + // families: ["Raleway"], + // weight: "Bold", + // size: 100 + // }, + // description: { + // families: ["Raleway"], + // weight: "SemiBold", + // size: 40 + // } + // } + // fonts: [ + // fromPackageRoot("src", "assets", "Raleway.ttf"), + // "https://fonts.googleapis.com/css?family=Raleway:300,400,500,700&display=swap" + // ] + }) +) + +export const generateStaticParams = () => metadataImage.generateParams() diff --git a/ark/fuma/app/docs/[[...slug]]/page.tsx b/ark/docs/app/docs/[[...slug]]/page.tsx similarity index 96% rename from ark/fuma/app/docs/[[...slug]]/page.tsx rename to ark/docs/app/docs/[[...slug]]/page.tsx index 4aa3a11ccf..263d93bfd9 100644 --- a/ark/fuma/app/docs/[[...slug]]/page.tsx +++ b/ark/docs/app/docs/[[...slug]]/page.tsx @@ -8,7 +8,7 @@ import { DocsTitle } from "fumadocs-ui/page" import { notFound } from "next/navigation" -import { source } from "../../../lib/source.ts" +import { source } from "../../../lib/source.tsx" export default async (props: { params: Promise<{ slug?: string[] }> }) => { const params = await props.params @@ -28,7 +28,6 @@ export default async (props: { params: Promise<{ slug?: string[] }> }) => { Popup, PopupContent, PopupTrigger, - pre: ({ ref: _, ...props }) => (

{props.children}
diff --git a/ark/fuma/app/docs/layout.tsx b/ark/docs/app/docs/layout.tsx similarity index 86% rename from ark/fuma/app/docs/layout.tsx rename to ark/docs/app/docs/layout.tsx index ecdd398f7a..5e84e99848 100644 --- a/ark/fuma/app/docs/layout.tsx +++ b/ark/docs/app/docs/layout.tsx @@ -1,6 +1,6 @@ import { DocsLayout } from "fumadocs-ui/layouts/docs" import type { ReactNode } from "react" -import { source } from "../../lib/source.ts" +import { source } from "../../lib/source.tsx" import { baseOptions } from "../layout.config.tsx" export default ({ children }: { children: ReactNode }) => ( diff --git a/ark/fuma/app/global.css b/ark/docs/app/global.css similarity index 77% rename from ark/fuma/app/global.css rename to ark/docs/app/global.css index 54d369bb87..5a14393140 100644 --- a/ark/fuma/app/global.css +++ b/ark/docs/app/global.css @@ -9,7 +9,8 @@ @tailwind components; @tailwind utilities; -:root { +/** the * ensures this overrides the variable definitions from shiki*/ +:root * { --background: 207 100% 9%; --hover-glow: 0.5rem 0.5rem 2rem 0 rgba(31, 38, 135, 0.37); @@ -33,7 +34,7 @@ --twoslash-code-font-size: 1em; --twoslash-matched-color: inherit; --twoslash-unmatched-color: #888; - --twoslash-cursor-color: var(--sl-color-gray-2); + --twoslash-cursor-color: #888; --twoslash-error-color: var(--ark-error); --twoslash-error-bg: #9558f818; --twoslash-warn-color: #c37d0d; @@ -65,7 +66,14 @@ pre.shiki, overflow-x: visible !important; } +/** should match arkDarkTheme.colors["editor.background"] */ .bg-fd-secondary\/50 { + background-color: #0006; +} + +/** avoid border on hover: + https://github.com/arktypeio/arktype/issues/1217 */ +.twoslash-popup-container .bg-fd-secondary\/50 { background-color: unset; } @@ -76,7 +84,10 @@ div.twoslash-popup-container { box-shadow: var(--hover-glow); } -.error { +/** .error.highlighted matches error lines explicitly added in the snippet + source via [!code error] */ + +.error.highlighted { position: relative; background-color: #f8585822; border-left: 3px solid var(--ark-runtime-error); @@ -87,10 +98,11 @@ div.twoslash-popup-container { width: max-content; } -.error > span { +.error.highlighted > span { color: var(--ark-runtime-error) !important; } +/** .twoslash-error matches errors added by twoslash itself, e.g. type errors */ .twoslash .twoslash-error { /* Override the builtin error squiggle to match our theme */ background: url("/image/errorSquiggle.svg") repeat-x bottom left; @@ -116,6 +128,16 @@ div.twoslash-popup-container { white-space: pre; } +.completions-block code { + padding-bottom: 3rem; +} + +/** avoid a janky white outline on hovers: + https://github.com/arktypeio/arktype/issues/1217 */ +:focus-visible { + outline: none; +} + /* Firefox specific rules */ @-moz-document url-prefix() { /* The backdrop-filter above doesn't work by default yet on Firefox so we do this instead */ @@ -123,3 +145,8 @@ div.twoslash-popup-container { background: #001323ee; } } + +/* allow us to inject a badge at order 1 */ +#nd-sidebar .lucide-chevron-down { + order: 2; +} diff --git a/ark/fuma/app/layout.config.tsx b/ark/docs/app/layout.config.tsx similarity index 100% rename from ark/fuma/app/layout.config.tsx rename to ark/docs/app/layout.config.tsx diff --git a/ark/fuma/app/layout.tsx b/ark/docs/app/layout.tsx similarity index 100% rename from ark/fuma/app/layout.tsx rename to ark/docs/app/layout.tsx diff --git a/ark/docs/astro.config.js b/ark/docs/astro.config.js deleted file mode 100644 index eb0f8b86db..0000000000 --- a/ark/docs/astro.config.js +++ /dev/null @@ -1,366 +0,0 @@ -// @ts-check - -import react from "@astrojs/react" -import starlight from "@astrojs/starlight" -import { defineConfig } from "astro/config" -import { shikiConfig } from "./src/components/shiki.config.js" - -// https://astro.build/config -export default defineConfig({ - site: "https://arktype.io", - redirects: { - "/discord": "https://discord.gg/xEzdc3fJQC", - "/primitives/string": "/string", - "/primitives/number": "/number" - }, - // cannot configure out dir to out to match other packges since dist is hard - // coded into: https://github.com/withastro/action/blob/main/action.yml - integrations: [ - starlight({ - title: "ArkType", - description: - "TypeScript's 1:1 validator, optimized from editor to runtime", - logo: { - src: "./src/assets/logo.svg", - replacesTitle: true - }, - head: [ - // this ensures each page is rendered in dark mode since we - // don't support light yet - { - tag: "script", - content: `localStorage.setItem("starlight-theme", "dark")` - } - ], - social: { - twitch: "https://twitch.tv/arktypeio", - twitter: "https://twitter.com/arktypeio", - discord: "https://arktype.io/discord", - github: "https://github.com/arktypeio/arktype" - }, - sidebar: [ - { - label: "Intro", - autogenerate: { - directory: "intro" - } - }, - { - label: "Primitives", - items: [ - { - label: "string", - collapsed: true, - items: [ - { - label: "keywords", - link: "/primitives#string/keywords" - }, - { label: "literals", link: "/primitives#string/literals" }, - { - label: "patterns", - link: "/primitives#string/patterns" - }, - { - label: "lengths", - link: "/primitives#string/lengths" - } - ] - }, - { - label: "number", - collapsed: true, - items: [ - { - label: "keywords", - link: "/primitives#number/keywords" - }, - { label: "literals", link: "/primitives#number/literals" }, - { - label: "ranges", - link: "/primitives#number/ranges" - }, - { - label: "divisors", - link: "/primitives#number/divisors" - } - ] - }, - { - label: "bigint", - link: "/primitives#bigint" - }, - { - label: "boolean", - link: "/primitives#boolean" - }, - { - label: "symbol", - link: "/primitives#symbol" - }, - { - label: "null", - link: "/primitives#null" - }, - { - label: "undefined", - link: "/primitives#undefined" - } - ] - }, - { - label: "Objects", - items: [ - { - label: "properties", - collapsed: true, - items: [ - { - label: "required", - link: "/objects#properties/required" - }, - { - label: "optional", - link: "/objects#properties/optional" - }, - { - label: "defaultable", - link: "/objects#properties/defaultable" - }, - { - label: "index", - link: "/objects#properties/index" - }, - { - label: "undeclared", - link: "/objects#properties/undeclared" - }, - { - label: "merge", - link: "/objects#properties/merge" - }, - { - label: "keyof", - link: "/objects#properties/keyof" - }, - { - label: "get", - link: "/objects#properties/get" - } - ] - }, - { - label: "arrays", - collapsed: true, - items: [{ label: "lengths", link: "/objects#arrays/lengths" }] - }, - { - label: "tuples", - collapsed: true, - items: [ - { - label: "prefix", - link: "/objects#tuples/prefix" - }, - { - label: "optional", - link: "/objects#tuples/optional" - }, - { - label: "variadic", - link: "/objects#tuples/variadic" - }, - { - label: "postfix", - link: "/objects#tuples/postfix" - } - ] - }, - { - label: "dates", - collapsed: true, - items: [ - { label: "keywords", link: "/objects#dates/keywords" }, - { label: "literals", link: "/objects#dates/literals" }, - { label: "ranges", link: "/objects#dates/ranges" } - ] - }, - { - label: "instanceof", - link: "/objects#instanceof" - } - ] - }, - { - label: "Expressions", - items: [ - { - label: "intersection", - link: "/expressions#intersection" - }, - { - label: "union", - link: "/expressions#union" - }, - { - label: "brand", - link: "/expressions#brand" - }, - { - label: "narrow", - link: "/expressions#narrow" - }, - { - label: "morph", - link: "/expressions#morph" - }, - { - label: "unit", - link: "/expressions#unit" - }, - { - label: "enumerated", - link: "/expressions#enumerated" - }, - { - label: "meta", - link: "/expressions#meta" - }, - { - label: "cast", - link: "/expressions#cast" - }, - { - label: "parenthetical", - link: "/expressions#parenthetical" - }, - { - label: "this", - link: "/expressions#this" - } - ] - }, - { - label: "Types", - items: [ - { - label: "properties", - link: "/types#properties" - }, - { label: "utilities", link: "/types#utilities" } - ] - }, - { - label: "Configuration", - items: [ - { - label: "errors", - link: "/configuration#errors" - }, - { - label: "clone", - link: "/configuration#clone" - }, - { - label: "onUndeclaredKey", - link: "/configuration#onundeclaredkey" - }, - { - label: "jitless", - link: "/configuration#jitless" - } - ] - }, - { - label: "Scopes", - badge: "advanced", - items: [ - { - label: "modules", - link: "/scopes#modules" - }, - { - label: "visibility", - link: "/scopes#visibility" - }, - { - label: "submodules", - link: "/scopes#submodules" - }, - { - label: "thunks", - link: "/scopes#thunks" - } - ] - }, - { - label: "Generics", - badge: "advanced", - items: [ - { - label: "keywords", - link: "/generics#keywords" - }, - { - label: "syntax", - link: "/generics#syntax" - }, - { - label: "hkt", - link: "/generics#hkt", - badge: "advanced++" - } - ] - }, - { - label: "Integrations", - translations: {}, - items: [ - { - label: "Standard Schema", - link: "/integrations#standard-schema" - }, - { - label: "tRPC", - link: "/integrations#trpc" - }, - { - label: "react-hook-form", - link: "/integrations#react-hook-form" - }, - { - label: "hono", - link: "/integrations#hono" - } - ] - }, - { - label: "FAQ", - link: "/faq" - }, - { - label: "About the project", - link: "/about" - } - ], - components: { - Head: "./src/components/Head.astro", - Sidebar: "./src/components/Sidebar.astro" - }, - customCss: ["@shikijs/twoslash/style-rich.css", "./src/styles.css"], - expressiveCode: false, - tableOfContents: { - maxHeadingLevel: 4 - } - }), - react() - ], - markdown: { - shikiConfig - }, - vite: { - resolve: { - conditions: ["ark-ts"] - } - } -}) diff --git a/ark/fuma/components/ArkCard.tsx b/ark/docs/components/ArkCard.tsx similarity index 100% rename from ark/fuma/components/ArkCard.tsx rename to ark/docs/components/ArkCard.tsx diff --git a/ark/docs/components/Asterisk.tsx b/ark/docs/components/Asterisk.tsx new file mode 100644 index 0000000000..47955fd5b2 --- /dev/null +++ b/ark/docs/components/Asterisk.tsx @@ -0,0 +1,5 @@ +export const Asterisk = () => ( +
+ * +
+) diff --git a/ark/docs/components/AutoDocs.tsx b/ark/docs/components/AutoDocs.tsx new file mode 100644 index 0000000000..f3246cf339 --- /dev/null +++ b/ark/docs/components/AutoDocs.tsx @@ -0,0 +1,67 @@ +import { entriesOf, hasKey } from "@ark/util" +import { ark } from "arktype" +import { Fragment } from "react" + +type DescriptionEntry = [alias: string, description: string] + +const root: DescriptionEntry[] = [] +const other: Record< + string, + { description?: string; vals: DescriptionEntry[] } +> = {} + +entriesOf(ark.internal.resolutions).forEach(([k, v]) => { + if (!hasKey(v, "description")) return + + if (!k.includes(".")) return root.push([k, v.description]) + + const [group, ...rest] = k.split(".") + other[group] ??= { vals: [] } + const groupType = rest.join(".") + if (groupType === "root") other[group].description = v.description + else other[group].vals.push([groupType, v.description]) +}) + +const rootElements = root.map(([k, v]) => ( + + {k} + {v} + +)) + +const otherElements = Object.entries(other).map(([k, v]) => ( + + + + + {k} + {v.description ? ` - ${v.description}` : ""} + + + + {v.vals.map(([k, v]) => ( + + {k} + {v} + + ))} + +)) + +export const AutoDocs = () => ( + + + + + + + + + {...rootElements} + {...otherElements} + +
TypeDescription
+) diff --git a/ark/fuma/components/AutoplayDemo.tsx b/ark/docs/components/AutoplayDemo.tsx similarity index 95% rename from ark/fuma/components/AutoplayDemo.tsx rename to ark/docs/components/AutoplayDemo.tsx index b6f09ae322..63408785a5 100644 --- a/ark/fuma/components/AutoplayDemo.tsx +++ b/ark/docs/components/AutoplayDemo.tsx @@ -1,5 +1,3 @@ -import React from "react" - export type AutoplayDemoProps = React.DetailedHTMLProps< React.VideoHTMLAttributes, HTMLVideoElement diff --git a/ark/docs/components/Badge.tsx b/ark/docs/components/Badge.tsx new file mode 100644 index 0000000000..cde16b24de --- /dev/null +++ b/ark/docs/components/Badge.tsx @@ -0,0 +1,31 @@ +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "fumadocs-ui/components/api" +import * as React from "react" + +export const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground" + } + }, + defaultVariants: { + variant: "default" + } + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +export const Badge = ({ className, variant, ...props }: BadgeProps) => ( +
+) diff --git a/ark/docs/components/CodeBlock.tsx b/ark/docs/components/CodeBlock.tsx new file mode 100644 index 0000000000..9c99ada6ca --- /dev/null +++ b/ark/docs/components/CodeBlock.tsx @@ -0,0 +1,91 @@ +import type { propwiseXor } from "@ark/util" +import type { HighlightOptions } from "fumadocs-core/server" +import { Popup, PopupContent, PopupTrigger } from "fumadocs-twoslash/ui" +import { cn } from "fumadocs-ui/components/api" +import { + CodeBlock as FumaCodeBlock, + Pre +} from "fumadocs-ui/components/codeblock" +import { toJsxRuntime } from "hast-util-to-jsx-runtime" +import { Fragment, jsx, jsxs } from "react/jsx-runtime" +import { getSingletonHighlighter } from "shiki" +import { shikiConfig, type BuiltinLang } from "../lib/shiki.ts" +import type { SnippetId } from "../lib/writeSnippetsEntrypoint.ts" +import snippetContentsById from "./snippets/contentsById.ts" + +export type CodeBlockProps = { + /** @default "ts" */ + lang?: BuiltinLang + style?: React.CSSProperties + className?: string + includesCompletions?: boolean +} & propwiseXor<{ children: string }, { fromFile: SnippetId }> + +// preload languages for shiki +// https://github.com/fuma-nama/fumadocs/issues/1095 +const highlighter = await getSingletonHighlighter({ + langs: shikiConfig.langs, + themes: [shikiConfig.themes.dark] +}) + +const components: HighlightOptions["components"] = { + // rounded none is for syntax tabs + pre: ({ className, children, ...props }) => ( +
+			{children}
+		
+ ) +} + +// overriding these custom components allows hovers to render +// correctly in code blocks outside markdown (e.g. on the home page) +Object.assign(components, { + Popup, + PopupContent, + PopupTrigger +}) + +export const CodeBlock: React.FC = ({ + lang = "ts", + children, + fromFile, + style, + className, + includesCompletions +}) => { + children ??= snippetContentsById[fromFile!] + + const highlighted = highlight(lang, children) + + return ( + + {highlighted} + + ) +} + +const highlight = (lang: BuiltinLang, contents: string) => { + try { + const hast = highlighter.codeToHast(contents, { + ...shikiConfig, + lang + }) + + return toJsxRuntime(hast, { + Fragment, + jsx, + jsxs, + components + }) + } catch (e) { + console.error(`Failed to transform the following code:\n${contents}`) + throw e + } +} diff --git a/ark/fuma/components/FloatYourBoat.tsx b/ark/docs/components/FloatYourBoat.tsx similarity index 83% rename from ark/fuma/components/FloatYourBoat.tsx rename to ark/docs/components/FloatYourBoat.tsx index 28729d4abf..9e5b5ad1b9 100644 --- a/ark/fuma/components/FloatYourBoat.tsx +++ b/ark/docs/components/FloatYourBoat.tsx @@ -1,16 +1,16 @@ "use client" import { motion } from "framer-motion" -import React from "react" +import { useLayoutEffect, useMemo, useState } from "react" import { BoatIcon } from "./icons/boat.tsx" const BOB_HEIGHT_PX = 2 const BOB_WIDTH_PX = 16 export const FloatYourBoat = () => { - const [loopDuration, setLoopDuration] = React.useState(null) + const [loopDuration, setLoopDuration] = useState(null) - const bobFrames = React.useMemo(() => { + const bobFrames = useMemo(() => { if (!loopDuration) return [] const frames: number[] = [] for (let i = 0; i < loopDuration; i++) @@ -18,7 +18,7 @@ export const FloatYourBoat = () => { return frames }, [loopDuration]) - React.useLayoutEffect(() => { + useLayoutEffect(() => { const width = window.innerWidth setLoopDuration(width / BOB_WIDTH_PX) }, []) diff --git a/ark/fuma/components/Hero.tsx b/ark/docs/components/Hero.tsx similarity index 100% rename from ark/fuma/components/Hero.tsx rename to ark/docs/components/Hero.tsx diff --git a/ark/docs/components/InstallationTabs.tsx b/ark/docs/components/InstallationTabs.tsx new file mode 100644 index 0000000000..fed0b31f3c --- /dev/null +++ b/ark/docs/components/InstallationTabs.tsx @@ -0,0 +1,25 @@ +import { Tab, Tabs } from "fumadocs-ui/components/tabs" +import { CodeBlock } from "./CodeBlock.tsx" + +const installers = ["pnpm", "npm", "yarn", "bun"] as const satisfies string[] + +export type Installer = (typeof installers)[number] + +type InstallationTabProps = { + name: Installer +} + +const InstallerTab = ({ name }: InstallationTabProps) => ( + + {`${name} install arktype`} + +) + +export const InstallationTabs = () => ( + + + + + + +) diff --git a/ark/fuma/components/LinkCard.tsx b/ark/docs/components/LinkCard.tsx similarity index 100% rename from ark/fuma/components/LinkCard.tsx rename to ark/docs/components/LinkCard.tsx diff --git a/ark/fuma/components/PlatformCloud.tsx b/ark/docs/components/PlatformCloud.tsx similarity index 100% rename from ark/fuma/components/PlatformCloud.tsx rename to ark/docs/components/PlatformCloud.tsx diff --git a/ark/fuma/components/RuntimeBenchmarksGraph.tsx b/ark/docs/components/RuntimeBenchmarksGraph.tsx similarity index 98% rename from ark/fuma/components/RuntimeBenchmarksGraph.tsx rename to ark/docs/components/RuntimeBenchmarksGraph.tsx index 9d0e7e2c3d..09e82fb9f2 100644 --- a/ark/fuma/components/RuntimeBenchmarksGraph.tsx +++ b/ark/docs/components/RuntimeBenchmarksGraph.tsx @@ -1,5 +1,4 @@ import { cn } from "fumadocs-ui/components/api" -import React from "react" const barStyles: React.CSSProperties = { height: "30px", diff --git a/ark/fuma/components/SyntaxTabs.tsx b/ark/docs/components/SyntaxTabs.tsx similarity index 67% rename from ark/fuma/components/SyntaxTabs.tsx rename to ark/docs/components/SyntaxTabs.tsx index 1954f2083e..4abcf27091 100644 --- a/ark/fuma/components/SyntaxTabs.tsx +++ b/ark/docs/components/SyntaxTabs.tsx @@ -1,6 +1,6 @@ import type { omit, unionToPropwiseXor } from "@ark/util" import { Tab, Tabs, type TabsProps } from "fumadocs-ui/components/tabs" -import type React from "react" +import { Children, isValidElement, type FC } from "react" export const syntaxKinds = [ "string", @@ -16,7 +16,17 @@ export const SyntaxTabs: React.FC> = ({ children, ...rest }) => ( - + + Children.toArray(children).some( + child => + isValidElement(child) && + (child.props as SyntaxTabProps | undefined)?.[kind] + ) + )} + > {children} ) @@ -31,6 +41,6 @@ type SyntaxTabProps = DiscriminatedSyntaxKindProps & { children: React.ReactNode } -export const SyntaxTab: React.FC = props => ( +export const SyntaxTab: FC = props => ( props[k])!}>{props.children} ) diff --git a/ark/fuma/components/icons/arktype-logo.tsx b/ark/docs/components/icons/arktype-logo.tsx similarity index 100% rename from ark/fuma/components/icons/arktype-logo.tsx rename to ark/docs/components/icons/arktype-logo.tsx diff --git a/ark/fuma/components/icons/boat.tsx b/ark/docs/components/icons/boat.tsx similarity index 100% rename from ark/fuma/components/icons/boat.tsx rename to ark/docs/components/icons/boat.tsx diff --git a/ark/fuma/components/icons/bun.tsx b/ark/docs/components/icons/bun.tsx similarity index 100% rename from ark/fuma/components/icons/bun.tsx rename to ark/docs/components/icons/bun.tsx diff --git a/ark/fuma/components/icons/chromium.tsx b/ark/docs/components/icons/chromium.tsx similarity index 100% rename from ark/fuma/components/icons/chromium.tsx rename to ark/docs/components/icons/chromium.tsx diff --git a/ark/fuma/components/icons/deno.tsx b/ark/docs/components/icons/deno.tsx similarity index 100% rename from ark/fuma/components/icons/deno.tsx rename to ark/docs/components/icons/deno.tsx diff --git a/ark/fuma/components/icons/intellij.tsx b/ark/docs/components/icons/intellij.tsx similarity index 100% rename from ark/fuma/components/icons/intellij.tsx rename to ark/docs/components/icons/intellij.tsx diff --git a/ark/fuma/components/icons/js.tsx b/ark/docs/components/icons/js.tsx similarity index 100% rename from ark/fuma/components/icons/js.tsx rename to ark/docs/components/icons/js.tsx diff --git a/ark/fuma/components/icons/neovim.tsx b/ark/docs/components/icons/neovim.tsx similarity index 100% rename from ark/fuma/components/icons/neovim.tsx rename to ark/docs/components/icons/neovim.tsx diff --git a/ark/fuma/components/icons/node.tsx b/ark/docs/components/icons/node.tsx similarity index 100% rename from ark/fuma/components/icons/node.tsx rename to ark/docs/components/icons/node.tsx diff --git a/ark/fuma/components/icons/npm.tsx b/ark/docs/components/icons/npm.tsx similarity index 100% rename from ark/fuma/components/icons/npm.tsx rename to ark/docs/components/icons/npm.tsx diff --git a/ark/fuma/components/icons/ts.tsx b/ark/docs/components/icons/ts.tsx similarity index 100% rename from ark/fuma/components/icons/ts.tsx rename to ark/docs/components/icons/ts.tsx diff --git a/ark/fuma/components/icons/vscode.tsx b/ark/docs/components/icons/vscode.tsx similarity index 100% rename from ark/fuma/components/icons/vscode.tsx rename to ark/docs/components/icons/vscode.tsx diff --git a/ark/docs/src/content/docs/snippets/betterErrors.twoslash.ts b/ark/docs/components/snippets/betterErrors.twoslash.ts similarity index 100% rename from ark/docs/src/content/docs/snippets/betterErrors.twoslash.ts rename to ark/docs/components/snippets/betterErrors.twoslash.ts diff --git a/ark/docs/src/content/docs/snippets/clarityAndConcision.twoslash.js b/ark/docs/components/snippets/clarityAndConcision.twoslash.js similarity index 100% rename from ark/docs/src/content/docs/snippets/clarityAndConcision.twoslash.js rename to ark/docs/components/snippets/clarityAndConcision.twoslash.js diff --git a/ark/docs/components/snippets/contentsById.ts b/ark/docs/components/snippets/contentsById.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ark/docs/src/content/docs/snippets/deepIntrospectability.twoslash.js b/ark/docs/components/snippets/deepIntrospectability.twoslash.js similarity index 100% rename from ark/docs/src/content/docs/snippets/deepIntrospectability.twoslash.js rename to ark/docs/components/snippets/deepIntrospectability.twoslash.js diff --git a/ark/docs/src/content/docs/snippets/intrinsicOptimization.twoslash.js b/ark/docs/components/snippets/intrinsicOptimization.twoslash.js similarity index 100% rename from ark/docs/src/content/docs/snippets/intrinsicOptimization.twoslash.js rename to ark/docs/components/snippets/intrinsicOptimization.twoslash.js diff --git a/ark/docs/src/content/docs/snippets/unparalleledDx.twoslash.js b/ark/docs/components/snippets/unparalleledDx.twoslash.js similarity index 100% rename from ark/docs/src/content/docs/snippets/unparalleledDx.twoslash.js rename to ark/docs/components/snippets/unparalleledDx.twoslash.js diff --git a/ark/docs/src/content/docs/about.mdx b/ark/docs/content/docs/about.mdx similarity index 100% rename from ark/docs/src/content/docs/about.mdx rename to ark/docs/content/docs/about.mdx diff --git a/ark/docs/content/docs/auto.mdx b/ark/docs/content/docs/auto.mdx new file mode 100644 index 0000000000..373ce1fcfe --- /dev/null +++ b/ark/docs/content/docs/auto.mdx @@ -0,0 +1,7 @@ +--- +title: AutoDocs +--- + +import { AutoDocs } from "../../components/AutoDocs.tsx" + + diff --git a/ark/docs/src/content/docs/configuration.mdx b/ark/docs/content/docs/configuration/index.mdx similarity index 100% rename from ark/docs/src/content/docs/configuration.mdx rename to ark/docs/content/docs/configuration/index.mdx diff --git a/ark/fuma/content/docs/configuration/meta.json b/ark/docs/content/docs/configuration/meta.json similarity index 100% rename from ark/fuma/content/docs/configuration/meta.json rename to ark/docs/content/docs/configuration/meta.json diff --git a/ark/fuma/content/docs/definitions.mdx b/ark/docs/content/docs/definitions.mdx similarity index 98% rename from ark/fuma/content/docs/definitions.mdx rename to ark/docs/content/docs/definitions.mdx index 6584ad3550..1248c114d0 100644 --- a/ark/fuma/content/docs/definitions.mdx +++ b/ark/docs/content/docs/definitions.mdx @@ -8,7 +8,7 @@ import { SyntaxTab, SyntaxTabs } from "../../components/SyntaxTabs.tsx" ### Kitchen Sink -```ts twoslash +```ts export const currentTsSyntax = type({ keyword: "null", stringLiteral: "'TS'", @@ -37,7 +37,7 @@ export const currentTsSyntax = type({ ## Constraints -```ts twoslash +```ts // runtime-specific syntax and builtin keywords with great error messages export const validationSyntax = type({ diff --git a/ark/fuma/content/docs/expressions/index.mdx b/ark/docs/content/docs/expressions/index.mdx similarity index 100% rename from ark/fuma/content/docs/expressions/index.mdx rename to ark/docs/content/docs/expressions/index.mdx diff --git a/ark/fuma/content/docs/expressions/meta.json b/ark/docs/content/docs/expressions/meta.json similarity index 100% rename from ark/fuma/content/docs/expressions/meta.json rename to ark/docs/content/docs/expressions/meta.json diff --git a/ark/docs/src/content/docs/faq.mdx b/ark/docs/content/docs/faq.mdx similarity index 100% rename from ark/docs/src/content/docs/faq.mdx rename to ark/docs/content/docs/faq.mdx diff --git a/ark/docs/src/content/docs/generics.mdx b/ark/docs/content/docs/generics/index.mdx similarity index 99% rename from ark/docs/src/content/docs/generics.mdx rename to ark/docs/content/docs/generics/index.mdx index a355d869fa..9c69a2835d 100644 --- a/ark/docs/src/content/docs/generics.mdx +++ b/ark/docs/content/docs/generics/index.mdx @@ -1,7 +1,5 @@ --- title: Generics -sidebar: - order: 3 --- ### Keywords @@ -32,6 +30,7 @@ const createBox = (of: type.Any) => }) const boxType = createBox(type("string")) +// ^? // @ts-expect-error const badBox = createBox(type("number")) diff --git a/ark/fuma/content/docs/generics/meta.json b/ark/docs/content/docs/generics/meta.json similarity index 87% rename from ark/fuma/content/docs/generics/meta.json rename to ark/docs/content/docs/generics/meta.json index 01e6a02103..4fbbc987d2 100644 --- a/ark/fuma/content/docs/generics/meta.json +++ b/ark/docs/content/docs/generics/meta.json @@ -1,5 +1,6 @@ { "title": "Generics", + "icon": "Advanced", "pages": [ "[keywords](/docs/generics#keywords)", "[syntax](/docs/generics#syntax)", diff --git a/ark/docs/src/content/docs/integrations.mdx b/ark/docs/content/docs/integrations/index.mdx similarity index 100% rename from ark/docs/src/content/docs/integrations.mdx rename to ark/docs/content/docs/integrations/index.mdx diff --git a/ark/fuma/content/docs/integrations/meta.json b/ark/docs/content/docs/integrations/meta.json similarity index 100% rename from ark/fuma/content/docs/integrations/meta.json rename to ark/docs/content/docs/integrations/meta.json diff --git a/ark/docs/src/content/docs/intro/adding-constraints.mdx b/ark/docs/content/docs/intro/adding-constraints.mdx similarity index 97% rename from ark/docs/src/content/docs/intro/adding-constraints.mdx rename to ark/docs/content/docs/intro/adding-constraints.mdx index 3ff357a7df..7046b1ed0d 100644 --- a/ark/docs/src/content/docs/intro/adding-constraints.mdx +++ b/ark/docs/content/docs/intro/adding-constraints.mdx @@ -10,8 +10,6 @@ In ArkType, conditions that narrow a type beyond its **basis** are called **cons Constraints are a first-class citizen of ArkType. They are fully composable with TypeScript's built-in operators and governed by the same underlying principles of set-theory. -In other words, **they just work**. - ## Define Let's create a new `contact` Type that enforces our example constraints. @@ -45,8 +43,9 @@ type _Contact = typeof _contact.t interface Contact extends _Contact {} export const contact: type = _contact -// ---cut--- +// ---cut-start--- // a non-empty list of Contact +// ---cut-end--- const contacts = contact.array().atLeastLength(1) ``` @@ -96,11 +95,12 @@ score (133.7) must be... } const narrowMessage = (e: type.errors): e is RuntimeErrors => true -// ---cut--- +// ---cut-start--- const out = palindromicContact({ email: "david@arktype.io", score: 133.7 }) +// ---cut-end--- if (out instanceof type.errors) { // ---cut-start--- diff --git a/ark/docs/content/docs/intro/meta.json b/ark/docs/content/docs/intro/meta.json new file mode 100644 index 0000000000..a0ace20283 --- /dev/null +++ b/ark/docs/content/docs/intro/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Intro", + "defaultOpen": true, + "pages": ["setup", "your-first-type", "adding-constraints", "morphs-and-more"] +} diff --git a/ark/docs/src/content/docs/intro/morphs-and-more.mdx b/ark/docs/content/docs/intro/morphs-and-more.mdx similarity index 100% rename from ark/docs/src/content/docs/intro/morphs-and-more.mdx rename to ark/docs/content/docs/intro/morphs-and-more.mdx diff --git a/ark/fuma/content/docs/intro/setup.mdx b/ark/docs/content/docs/intro/setup.mdx similarity index 79% rename from ark/fuma/content/docs/intro/setup.mdx rename to ark/docs/content/docs/intro/setup.mdx index 929906ee6e..605fae4cd9 100644 --- a/ark/fuma/content/docs/intro/setup.mdx +++ b/ark/docs/content/docs/intro/setup.mdx @@ -5,24 +5,11 @@ sidebar: --- import { Tab, Tabs } from "fumadocs-ui/components/tabs" -import { CodeBlock } from "../../../components/CodeBlock.tsx" +import { InstallationTabs } from "../../../components/InstallationTabs.tsx" ## Installation - - - pnpm install arktype - - - npm install arktype - - - yarn install arktype - - - bun install arktype - - + You'll also need... diff --git a/ark/fuma/content/docs/intro/your-first-type.mdx b/ark/docs/content/docs/intro/your-first-type.mdx similarity index 97% rename from ark/fuma/content/docs/intro/your-first-type.mdx rename to ark/docs/content/docs/intro/your-first-type.mdx index 931b4aabd9..69723b2317 100644 --- a/ark/fuma/content/docs/intro/your-first-type.mdx +++ b/ark/docs/content/docs/intro/your-first-type.mdx @@ -8,7 +8,7 @@ If you already know TypeScript, congratulations- you just learned most of ArkTyp ## Define -```ts twoslash +```ts import { type } from "arktype" const user = type({ @@ -32,7 +32,7 @@ If you make a mistake, don't worry- every definition gets the autocomplete and v Suppose we want to move `platform` and `version` from our original type to a new `device` property. -```ts twoslash +```ts const user = type({ name: "string", // nested definitions don't need to be wrapped @@ -45,7 +45,7 @@ const user = type({ To decouple `device` from `user`, just move it to its own type and reference it. -```ts twoslash +```ts const device = type({ platform: "'android' | 'ios'", "versions?": "(number | string)[]" @@ -61,7 +61,7 @@ const user = type({ At runtime, we can pass `unknown` data to our type and get back either a validated `User` or an array of clear, customizable errors with a root `summary`. -```ts twoslash +```ts const user = type({ name: "string", device: { diff --git a/ark/fuma/content/docs/keywords.mdx b/ark/docs/content/docs/keywords.mdx similarity index 80% rename from ark/fuma/content/docs/keywords.mdx rename to ark/docs/content/docs/keywords.mdx index ef5221ad64..86bb95405a 100644 --- a/ark/fuma/content/docs/keywords.mdx +++ b/ark/docs/content/docs/keywords.mdx @@ -2,15 +2,19 @@ title: Keywords --- +import { Asterisk } from "../../components/Asterisk.tsx" import { SyntaxTab, SyntaxTabs } from "../../components/SyntaxTabs.tsx" ### TypeScript -All\* builtin TypeScript keywords are directly available. +
+ All + builtin TypeScript keywords are directly available. +
-```ts twoslash +```ts const keywords = type({ string: "string", date: "Date" @@ -20,7 +24,7 @@ const keywords = type({ -```ts twoslash +```ts const keywords = type({ string: type.string, date: type.Date @@ -33,13 +37,18 @@ Common keywords are exposed directly on `type`. + + `any` and `void` are misleading and unnecessary for runtime + validation and so are not included as keywords by default. + + ### Subtype Subtype keywords refine or transform their root type. -```ts twoslash +```ts const keywords = type({ dateFormattedString: "string.date", transformStringToDate: "string.date.parse", @@ -54,7 +63,7 @@ You can easily explore available subtypes via autocomplete by with a partial def -```ts twoslash +```ts const keywords = type({ dateFormattedString: type.keywords.string.date.root, isoFormattedString: type.keywords.string.date.iso.root, diff --git a/ark/docs/content/docs/meta.json b/ark/docs/content/docs/meta.json new file mode 100644 index 0000000000..bafe11247e --- /dev/null +++ b/ark/docs/content/docs/meta.json @@ -0,0 +1,16 @@ +{ + "pages": [ + "intro", + "primitives", + "objects", + "keywords", + "expressions", + "type-api", + "configuration", + "integrations", + "scopes", + "generics", + "faq", + "about" + ] +} diff --git a/ark/docs/content/docs/objects/arrays/meta.json b/ark/docs/content/docs/objects/arrays/meta.json new file mode 100644 index 0000000000..36ff60b1bb --- /dev/null +++ b/ark/docs/content/docs/objects/arrays/meta.json @@ -0,0 +1,11 @@ +{ + "title": "arrays and tuples", + "pages": [ + "lengths", + "[prefix](/docs/objects#arrays/prefix)", + "[optional](/docs/objects#arrays/optional)", + "[defaultable](/docs/objects#arrays/defaultable)", + "[variadic](/docs/objects#arrays/variadic)", + "[postfix](/docs/objects#arrays/postfix)" + ] +} diff --git a/ark/fuma/content/docs/objects/index.mdx b/ark/docs/content/docs/objects/index.mdx similarity index 77% rename from ark/fuma/content/docs/objects/index.mdx rename to ark/docs/content/docs/objects/index.mdx index 2fc6425fa7..822d17fb85 100644 --- a/ark/fuma/content/docs/objects/index.mdx +++ b/ark/docs/content/docs/objects/index.mdx @@ -8,10 +8,9 @@ import { SyntaxTab, SyntaxTabs } from "../../../components/SyntaxTabs.tsx" Objects definitions can include any combination of required, optional, defaultable named properties and index signatures. - -##### required +### required [#properties-required] - + ```ts @@ -44,12 +43,11 @@ const myObject = type({ - + - -##### optional +### optional [#properties-optional] - + ```ts @@ -102,36 +100,36 @@ const myObject = type({ - + + + -:::caution[Optional properties cannot be present with the value undefined] + In TypeScript, there is a setting called [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) that can be set to `true` to enforce the distinction between properties that are missing and properties that are present with the value `undefined`. -In TypeScript, there is a setting called `exactOptionalPropertyTypes` that can be set to `true` to enforce the distinction between properties that are missing and properties that are present with the value `undefined`. + ArkType mirrors this behavior by default, so if you want to allow `undefined`, you'll need to add it to your value's definition. If you're interested in a builtin configuration option for this setting, we'd love feedback or contributions on [this issue](https://github.com/arktypeio/arktype/issues/1191). -ArkType mirrors this behavior by default, so if you want to allow `undefined`, you'll need to add it to your value's definition. If you're interested in a builtin configuration option for this setting, we'd love feedback or contributions on [this issue](https://github.com/arktypeio/arktype/issues/1191). +
+ See an example -
- See an example + ```ts + const myObj = type({ + "key?": "number" + }) -```ts -const myObj = type({ - "key?": "number" -}) + // valid data + const validResult = myObj({}) -// valid data -const validResult = myObj({}) + // Error: key must be a number (was undefined) + const errorResult = myObj({ key: undefined }) + ``` -// Error: key must be a number (was undefined) -const errorResult = myObj({ key: undefined }) -``` +
-
-::: +
-
-##### defaultable +### defaultable [#properties-defaultable] - + ```ts @@ -162,23 +160,24 @@ const myObject = type({ - + -:::caution[Optional and default only work within objects and tuples!] -Unlike e.g. `number.array()`, `number.optional()` and `number.default(0)` don't return a new `Type`, but rather a tuple definition like `[Type, "?"]` or `[Type, "=", 0]`. + + Unlike e.g. `number.array()`, `number.optional()` and `number.default(0)` don't return a new `Type`, but rather a tuple definition like `[Type, "?"]` or `[Type, "=", 0]`. -This reflects the fact that in ArkType's type system, optionality and defaultability are only meaningful in reference to a property. Attempting to create an optional or defaultable value outside an object like `type("string?")` will result in a `ParseError`. + This reflects the fact that in ArkType's type system, optionality and defaultability are only meaningful in reference to a property. Attempting to create an optional or defaultable value outside an object like `type("string?")` will result in a `ParseError`. -To create a `Type` accepting `string` or `undefined`, use a union like `type("string | undefined")`. + To create a `Type` accepting `string` or `undefined`, use a union like `type("string | undefined")`. -To have it transform `undefined` to an empty string, use an explicit morph like: + To have it transform `undefined` to an empty string, use an explicit morph like: -```ts -const fallbackString = type("string | undefined").pipe(v => v ?? "") -``` + ```ts + const fallbackString = type("string | undefined").pipe(v => v ?? "") + ``` - -##### index + + +### index [#properties-index] @@ -196,23 +195,19 @@ const myObject = type({ - -##### undeclared +### undeclared [#properties-undeclared] 🚧 Coming soon ™️🚧 - -##### merge +### merge [#properties-merge] 🚧 Coming soon ™️🚧 - -##### keyof +### keyof [#properties-keyof] 🚧 Coming soon ™️🚧 - -##### get +### get [#properties-get] 🚧 Coming soon ™️🚧 @@ -261,8 +256,7 @@ const arrays = type({ - -##### lengths +### lengths [#arrays-lengths] Constrain an array with an inclusive or exclusive min or max length. @@ -332,8 +326,7 @@ const range = type({ Like objects, tuples are structures whose values are nested definitions. Like TypeScript, ArkType supports prefix, optional, variadic, and postfix elements, with the same restrictions about combining them. - -##### prefix +### prefix [#tuples-prefix] @@ -366,8 +359,11 @@ const myTuple = type([ - -##### optional +### defaultable [#tuples-defaultable] + +🚧 Coming soon ™️🚧 + +### optional [#tuples-optional] Tuples can include any number of optional elements following its prefix elements. @@ -428,8 +424,7 @@ const myTuple = type([ - -##### variadic +### variadic [#tuples-variadic] Like in TypeScript, variadic elements allow zero or more consecutive values of a given type and may occur at most once in a tuple. @@ -456,8 +451,7 @@ const myTuple = type([type.string, "...", type.number.array()]) - -##### postfix +### postfix [#tuples-postfix] Postfix elements are required elements following a variadic element. @@ -486,8 +480,7 @@ const myTuple = type(["...", type.number.array(), type.boolean, type.string]) ## dates - -##### literals +### literals [#dates-literals] Date literals represent a Date instance with an exact value. @@ -500,8 +493,7 @@ const literals = type({ }) ``` - -##### ranges +### ranges [#dates-ranges] Constrain a Date with an inclusive or exclusive min or max. @@ -608,7 +600,6 @@ const instances = type({ - -##### keywords +### keywords [#instanceof-keywords] 🚧 Coming soon ™️🚧 diff --git a/ark/fuma/content/docs/objects/meta.json b/ark/docs/content/docs/objects/meta.json similarity index 50% rename from ark/fuma/content/docs/objects/meta.json rename to ark/docs/content/docs/objects/meta.json index ab5e6366aa..e820fe437e 100644 --- a/ark/fuma/content/docs/objects/meta.json +++ b/ark/docs/content/docs/objects/meta.json @@ -1,9 +1,9 @@ { "title": "Objects", + "defaultOpen": true, "pages": [ - "[properties](/docs/objects#properties)", - "[arrays](/docs/objects#arrays)", - "[tuples](/docs/objects#tuples)", + "properties", + "arrays", "[dates](/docs/objects#dates)", "[instanceof](/docs/objects#instanceof)" ] diff --git a/ark/docs/content/docs/objects/properties/meta.json b/ark/docs/content/docs/objects/properties/meta.json new file mode 100644 index 0000000000..aa27b3ae6b --- /dev/null +++ b/ark/docs/content/docs/objects/properties/meta.json @@ -0,0 +1,13 @@ +{ + "title": "properties", + "pages": [ + "[required](/docs/objects#properties/required)", + "[optional](/docs/objects#properties/optional)", + "[defaultable](/docs/objects#properties/defaultable)", + "[index](/docs/objects#properties/index)", + "[undeclared](/docs/objects#properties/undeclared)", + "[merge](/docs/objects#properties/merge)", + "[keyof](/docs/objects#properties/keyof)", + "[get](/docs/objects#properties/get)" + ] +} diff --git a/ark/docs/src/content/docs/primitives.mdx b/ark/docs/content/docs/primitives/index.mdx similarity index 81% rename from ark/docs/src/content/docs/primitives.mdx rename to ark/docs/content/docs/primitives/index.mdx index 20558e9f94..01cc1b2213 100644 --- a/ark/docs/src/content/docs/primitives.mdx +++ b/ark/docs/content/docs/primitives/index.mdx @@ -2,18 +2,15 @@ title: Primitives --- -import { Tabs } from "@astrojs/starlight/components" -import SyntaxTab from "../../components/SyntaxTab.astro" +import { SyntaxTab, SyntaxTabs } from "../../../components/SyntaxTabs.tsx" ## string - -##### keywords +### keywords [#string-keywords] 🚧 Coming soon ™️🚧 - -##### literals +### literals [#string-literals] ```ts const literals = type({ @@ -22,8 +19,7 @@ const literals = type({ }) ``` - -##### patterns +### patterns [#string-patterns] Regex literals specify an unanchored regular expression that an input string must match. @@ -37,11 +33,11 @@ const literals = type({ ``` -##### lengths +### lengths [#string-lengths] Constrain a string with an inclusive or exclusive min or max length. - + ```ts @@ -68,11 +64,11 @@ const bounded = type({ - + Range expressions allow you to specify both a min and max length and use the same syntax for exclusivity. - + ```ts @@ -97,17 +93,15 @@ const range = type({ - + ## number - -##### keywords +### keywords [#number-keywords] 🚧 Coming soon ™️🚧 - -##### literals +### literals [#number-literals] ```ts const literals = type({ @@ -115,12 +109,11 @@ const literals = type({ }) ``` - -##### ranges +### ranges [#number-ranges] Constrain a number with an inclusive or exclusive min or max. - + ```ts @@ -147,11 +140,11 @@ const bounded = type({ - + Range expressions allow you to specify both a min and max and use the same syntax for exclusivity. - + ```ts @@ -177,14 +170,13 @@ const range = type({ - + - -##### divisors +### divisors [#number-divisors] Constrain a `number` to a multiple of the specified integer. - + ```ts @@ -205,13 +197,13 @@ const evens = type({ - + ## bigint To allow any `bigint` value, use the `"bigint"` keyword. - + ```ts @@ -232,10 +224,9 @@ const symbols = type({ - + - -##### literals +### literals [#bigint-literals] To require an exact `bigint` value in your type, you can use add the suffix `n` to a string-embedded [number literal](/primitives#number/literals) to make it a `bigint`. @@ -251,7 +242,7 @@ You may also use a [unit expression](/expressions#unit) to define `bigint` liter To allow any `symbol` value, use the `"symbol"` keyword. - + ```ts @@ -272,7 +263,7 @@ const symbols = type({ - + To reference a specific symbol in your definition, use a [unit expression](/expressions#unit). @@ -282,7 +273,7 @@ No special syntax is required to define symbolic properties like `{ [mySymbol]: To allow `true` or `false`, use the `"boolean"` keyword. - + ```ts @@ -303,14 +294,13 @@ const booleans = type({ - + - -##### literals +### literals [#boolean-literals] To require a specific boolean value, use the corresponding literal. - + ```ts @@ -337,13 +327,13 @@ const booleans = type({ - + ## null The `"null"` keyword can be used to allow the exact value `null`, generally as part of a [union](/expressions#union). - + ```ts @@ -364,13 +354,13 @@ const nullable = type({ - + ## undefined The `"undefined"` keyword can be used to allow the exact value `undefined`, generally as part of a [union](/expressions#union). - + ```ts @@ -391,27 +381,29 @@ const myObj = type({ - + -:::caution[Allowing `undefined` as a value does not make the key optional!] + -In TypeScript, a required property that allows `undefined` must still be present for the type to be satisfied. + In TypeScript, a required property that allows `undefined` must still be present for the type to be satisfied. -The same is true in ArkType. + The same is true in ArkType. -
- See an example +
+ See an example -```ts -const myObj = type({ - key: "number | undefined" -}) + ```ts + const myObj = type({ + key: "number | undefined" + }) -// valid data -const validResult = myObj({ key: undefined }) + // valid data + const validResult = myObj({ key: undefined }) -// Error: name must be a number or undefined (was missing) -const errorResult = myObj({}) -``` + // Error: name must be a number or undefined (was missing) + const errorResult = myObj({}) + ``` + +
-
+
diff --git a/ark/docs/content/docs/primitives/meta.json b/ark/docs/content/docs/primitives/meta.json new file mode 100644 index 0000000000..08c15d9b88 --- /dev/null +++ b/ark/docs/content/docs/primitives/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Primitives", + "defaultOpen": true, + "pages": [ + "string", + "number", + "[bigint](/docs/primitives#bigint)", + "[symbol](/docs/primitives#symbol)", + "[boolean](/docs/primitives#boolean)", + "[null](/docs/primitives#null)", + "[undefined](/docs/primitives#undefined)" + ] +} diff --git a/ark/docs/content/docs/primitives/number/meta.json b/ark/docs/content/docs/primitives/number/meta.json new file mode 100644 index 0000000000..60c2f6bbd6 --- /dev/null +++ b/ark/docs/content/docs/primitives/number/meta.json @@ -0,0 +1,9 @@ +{ + "title": "number", + "pages": [ + "[keywords](/docs/primitives#number/keywords)", + "[literals](/docs/primitives#number/literals)", + "[ranges](/docs/primitives#number/ranges)", + "[divisors](/docs/primitives#number/divisors)" + ] +} diff --git a/ark/docs/content/docs/primitives/string/meta.json b/ark/docs/content/docs/primitives/string/meta.json new file mode 100644 index 0000000000..f6754bd550 --- /dev/null +++ b/ark/docs/content/docs/primitives/string/meta.json @@ -0,0 +1,9 @@ +{ + "title": "string", + "pages": [ + "[keywords](/docs/primitives#string/keywords)", + "[literals](/docs/primitives#string/literals)", + "[patterns](/docs/primitives#string/patterns)", + "[lengths](/docs/primitives#string/lengths)" + ] +} diff --git a/ark/docs/src/content/docs/scopes.mdx b/ark/docs/content/docs/scopes/index.mdx similarity index 98% rename from ark/docs/src/content/docs/scopes.mdx rename to ark/docs/content/docs/scopes/index.mdx index 2b977bf315..4a97bcc520 100644 --- a/ark/docs/src/content/docs/scopes.mdx +++ b/ark/docs/content/docs/scopes/index.mdx @@ -1,7 +1,5 @@ --- title: Scopes -sidebar: - order: 2 --- Scopes are the foundation of ArkType, and one of the most powerful features for users wanting full control over configuration and to make their own keywords available fluidly within string definition syntax. @@ -38,6 +36,7 @@ const packageData: Package = { dependencies: [{ name: "typescript" }], contributors: [{ email: "david@sharktypeio" }] } + packageData.dependencies![0].dependencies = [packageData] export const out = types.package(packageData) diff --git a/ark/fuma/content/docs/scopes/meta.json b/ark/docs/content/docs/scopes/meta.json similarity index 90% rename from ark/fuma/content/docs/scopes/meta.json rename to ark/docs/content/docs/scopes/meta.json index 8691b7cdc2..710f78e1c8 100644 --- a/ark/fuma/content/docs/scopes/meta.json +++ b/ark/docs/content/docs/scopes/meta.json @@ -1,5 +1,6 @@ { "title": "Scopes", + "icon": "Advanced", "pages": [ "[modules](/docs/scopes#modules)", "[visibility](/docs/scopes#visibility)", diff --git a/ark/docs/src/content/docs/types.mdx b/ark/docs/content/docs/type-api.mdx similarity index 88% rename from ark/docs/src/content/docs/types.mdx rename to ark/docs/content/docs/type-api.mdx index 897561404e..cdf119bb19 100644 --- a/ark/docs/src/content/docs/types.mdx +++ b/ark/docs/content/docs/type-api.mdx @@ -1,5 +1,5 @@ --- -title: Types +title: Type API --- 🚧 Coming soon ™️🚧 diff --git a/ark/fuma/lib/ambient.d.ts b/ark/docs/lib/ambient.d.ts similarity index 100% rename from ark/fuma/lib/ambient.d.ts rename to ark/docs/lib/ambient.d.ts diff --git a/ark/fuma/lib/metadata.ts b/ark/docs/lib/metadata.ts similarity index 79% rename from ark/fuma/lib/metadata.ts rename to ark/docs/lib/metadata.ts index 8661f672ac..475c488cc0 100644 --- a/ark/fuma/lib/metadata.ts +++ b/ark/docs/lib/metadata.ts @@ -1,5 +1,5 @@ import { createMetadataImage } from "fumadocs-core/server" -import { source } from "./source.ts" +import { source } from "./source.tsx" export const metadataImage = createMetadataImage({ imageRoute: "/docs-og", diff --git a/ark/fuma/lib/shiki.ts b/ark/docs/lib/shiki.ts similarity index 75% rename from ark/fuma/lib/shiki.ts rename to ark/docs/lib/shiki.ts index a31572d2e4..9cf03769a7 100644 --- a/ark/fuma/lib/shiki.ts +++ b/ark/docs/lib/shiki.ts @@ -1,26 +1,28 @@ import { transformerNotationErrorLevel } from "@shikijs/transformers" import type { RehypeCodeOptions } from "fumadocs-core/mdx-plugins" import { transformerTwoslash } from "fumadocs-twoslash" -import { defaultCompilerOptions } from "twoslash" +import { createRequire } from "node:module" -const { default: arkTypePackageJson } = await import("arkdark/package.json", { - with: { type: "json" } -}) +/** for some reason a standard import with an attribute like: + + import arkDarkTheme from "arkdark/arkdark.json" with { type: "json" } + +results in an error like: -const { default: arkTypeTmJson } = await import( - "arkdark/tsWithArkType.tmLanguage.json", - { - with: { type: "json" } - } -) + Module "file:///home/ssalb/arktype/ark/dark/arkdark.json" needs an import attribute of "type: json" -const { default: arkDarkTheme } = await import("arkdark/arkdark.json", { - with: { type: "json" } -}) +despite it having the attribute. Using require as a workaround- convert to an import +like the one above if possible in the future without breaking the build.*/ -// Theme adjustments +const require = createRequire(import.meta.url) -arkDarkTheme.colors["editor.background"] = "#00000027" +const arkDarkTheme = require("arkdark/arkdark.json") +const arkTypePackageJson = require("arkdark/package.json") +const arkTypeTmJson = require("arkdark/tsWithArkType.tmLanguage.json") + +// Theme adjustments +/** should match the css rule for .bg-fd-secondary\/50 */ +arkDarkTheme.colors["editor.background"] = "#0006" arkDarkTheme.tokenColors.push({ // this is covered by editorBracketHighlight.foreground1 etc. in VSCode, @@ -37,8 +39,7 @@ const twoslash = transformerTwoslash({ langs: ["ts", "js"], twoslashOptions: { compilerOptions: { - ...defaultCompilerOptions, - exactOptionalPropertyTypes: true, + // avoid ... in certain longer types on hover noErrorTruncation: true }, extraFiles: { @@ -130,11 +131,19 @@ declare global { } }) -export const shikiConfig: RehypeCodeOptions = { +export const shikiConfig = { themes: { dark: arkDarkTheme, light: arkDarkTheme }, langs: ["json", "bash", { ...arkTypeTmJson, name: "ts" }], transformers: [twoslash, transformerNotationErrorLevel()] -} +} as const satisfies RehypeCodeOptions + +export type shikiConfig = typeof shikiConfig + +export type BuiltinLang = { + [i in keyof shikiConfig["langs"]]: extractName +}[number] + +type extractName = lang extends { name: infer name } ? name : lang diff --git a/ark/docs/lib/source.tsx b/ark/docs/lib/source.tsx new file mode 100644 index 0000000000..1e14932f5a --- /dev/null +++ b/ark/docs/lib/source.tsx @@ -0,0 +1,35 @@ +import type { autocomplete } from "@ark/util" +import { loader } from "fumadocs-core/source" +import { createMDXSource } from "fumadocs-mdx" +import { icons } from "lucide-react" +import { createElement } from "react" +import { docs, meta } from "../.source" +import { Badge } from "../components/Badge.tsx" + +export type IconName = keyof typeof icons | "Advanced" + +export const source = loader({ + baseUrl: "/docs", + source: createMDXSource(docs, meta), + icon: (name?: autocomplete) => { + if (!name) return + if (name in icons) return createElement(icons[name as never]) + + if (name === "Advanced") { + return ( + + advanced + + ) + } + + throw new Error(`${name} is not a valid icon`) + } +}) diff --git a/ark/docs/lib/writeSnippetsEntrypoint.ts b/ark/docs/lib/writeSnippetsEntrypoint.ts new file mode 100644 index 0000000000..d84c7e3819 --- /dev/null +++ b/ark/docs/lib/writeSnippetsEntrypoint.ts @@ -0,0 +1,54 @@ +import { fromHere, readFile, shell, writeFile } from "@ark/fs" +import { flatMorph, throwInternalError } from "@ark/util" +import { existsSync } from "fs" + +/** + * Previously, we used raw-loader imports like: + * + * import unparalleledDx from "./snippets/unparalleledDx.twoslash.js?raw" + * + * so that we could write longer code blocks for use in .tsx + * without losing syntax highlighting, type checking etc. + * However, I was unable to get this working with Next's --turbo + * flag, which seems very impactful in terms of performance. + * + * As a workaround, when this config is loaded, we regenerate + * the contents.ts file and just import that directly. It is + * then committed to git as normal. + */ +export const writeSnippetsEntrypoint = () => { + const snippetContentsById = flatMorph(snippetIds, (i, id) => { + const tsPath = snippetPath(`${id}.twoslash.ts`) + const jsPath = snippetPath(`${id}.twoslash.js`) + + const path = + existsSync(tsPath) ? tsPath + : existsSync(jsPath) ? jsPath + : throwInternalError( + `Expected a snippet file at ${tsPath} or ${jsPath} (neither existed).` + ) + return [id, readFile(path)] + }) + + const toPath = snippetPath("contentsById.ts") + + writeFile( + toPath, + `export default ${JSON.stringify(snippetContentsById, null, 4)}` + ) + + shell(`pnpm prettier --write ${toPath}`) +} + +const snippetIds = [ + "betterErrors", + "clarityAndConcision", + "deepIntrospectability", + "intrinsicOptimization", + "unparalleledDx" +] as const + +export type SnippetId = (typeof snippetIds)[number] + +const snippetPath = (fileName: string) => + fromHere("..", "components", "snippets", fileName) diff --git a/ark/fuma/next-env.d.ts b/ark/docs/next-env.d.ts similarity index 53% rename from ark/fuma/next-env.d.ts rename to ark/docs/next-env.d.ts index 40c3d68096..1b3be0840f 100644 --- a/ark/fuma/next-env.d.ts +++ b/ark/docs/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/ark/fuma/next.config.ts b/ark/docs/next.config.ts similarity index 61% rename from ark/fuma/next.config.ts rename to ark/docs/next.config.ts index d9fd2dbd60..6fe5293677 100644 --- a/ark/fuma/next.config.ts +++ b/ark/docs/next.config.ts @@ -1,28 +1,27 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ import type { NextConfig } from "next" +import { writeSnippetsEntrypoint } from "./lib/writeSnippetsEntrypoint.ts" // Next can only treat next.config.ts as CJS, but fumadocs-mdx only supports ESM // This allows us to import it using Node 22+ with --experimental-require-module // Should convert to a standard import in the future when this is resolved // https://github.com/fuma-nama/fumadocs/issues/1054 const { createMDX } = - // eslint-disable-next-line @typescript-eslint/no-require-imports require("./node_modules/fumadocs-mdx/dist/next/index.js") as typeof import("fumadocs-mdx/next") +writeSnippetsEntrypoint() + const config = { - output: "export", reactStrictMode: true, + cleanDistDir: true, serverExternalPackages: ["twoslash", "typescript"], - webpack: config => { - // this must be added to the beginning of the array - // so that imports like the following don't have types stripped: - - // import betterErrors from "!./snippets/betterErrors.twoslash.ts?raw" - config.module.rules.push({ - resourceQuery: /raw/, - type: "asset/source" - }) - - return config + // the following properties are required by nextjs-github-pages: + // https://github.com/gregrickaby/nextjs-github-pages + output: "export", + basePath: + process.env.NODE_ENV === "development" ? (undefined as never) : "/arktype", + images: { + unoptimized: true } } as const satisfies NextConfig diff --git a/ark/docs/package.json b/ark/docs/package.json index 74960d4cd3..21ab94399d 100644 --- a/ark/docs/package.json +++ b/ark/docs/package.json @@ -1,40 +1,44 @@ { "name": "@ark/docs", + "version": "0.0.1", "private": true, "type": "module", - "version": "0.0.1", - "license": "MIT", "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "pnpm check && astro build", - "check": "astro check --minimumSeverity warning --minimumFailingSeverity warning", - "preview": "astro preview" + "build": "NODE_OPTIONS=--no-warnings next build", + "dev": "NODE_OPTIONS=--no-warnings next dev --turbo", + "start": "next start", + "clean": "rm -rf .next .source out", + "postinstall": "fumadocs-mdx", + "upDeps": "pnpm up --latest" }, "dependencies": { "@ark/fs": "workspace:*", "@ark/util": "workspace:*", - "@astrojs/check": "0.9.4", - "@astrojs/react": "3.6.2", - "@astrojs/starlight": "0.29.0", - "@astrojs/ts-plugin": "1.10.4", - "@shikijs/transformers": "1.22.2", - "@shikijs/twoslash": "1.22.2", + "@fumadocs/cli": "0.0.4", + "@icons-pack/react-simple-icons": "10.2.0", + "@shikijs/transformers": "1.24.4", + "@types/mdx": "2.0.13", + "@types/react": "19.0.2", + "@types/react-dom": "19.0.2", "arkdark": "workspace:*", "arktype": "workspace:*", - "astro": "4.16.18", - "astro-og-canvas": "0.5.4", - "canvaskit-wasm": "0.39.1", - "framer-motion": "11.11.17", - "react": "18.3.1", - "react-dom": "18.3.1", - "sharp": "0.33.5", - "shiki": "1.22.2", - "twoslash": "0.2.12" - }, - "devDependencies": { - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "autoprefixer": "10.4.20", + "class-variance-authority": "0.7.1", + "framer-motion": "11.15.0", + "fumadocs-core": "14.6.2", + "fumadocs-mdx": "11.1.2", + "fumadocs-twoslash": "2.0.2", + "fumadocs-ui": "14.6.2", + "hast-util-to-jsx-runtime": "2.3.2", + "lucide-react": "0.469.0", + "next": "15.1.2", + "postcss": "8.4.49", + "prettier-plugin-tailwindcss": "0.6.9", + "react": "19.0.0", + "react-dom": "19.0.0", + "shiki": "1.24.4", + "tailwindcss": "3.4.17", + "twoslash": "0.2.12", "typescript": "catalog:" } } diff --git a/ark/fuma/postcss.config.cjs b/ark/docs/postcss.config.cjs similarity index 100% rename from ark/fuma/postcss.config.cjs rename to ark/docs/postcss.config.cjs diff --git a/ark/docs/public/check.svg b/ark/docs/public/image/check.svg similarity index 100% rename from ark/docs/public/check.svg rename to ark/docs/public/image/check.svg diff --git a/ark/docs/public/copy.svg b/ark/docs/public/image/copy.svg similarity index 100% rename from ark/docs/public/copy.svg rename to ark/docs/public/image/copy.svg diff --git a/ark/docs/src/assets/errorSquiggle.svg b/ark/docs/public/image/errorSquiggle.svg similarity index 100% rename from ark/docs/src/assets/errorSquiggle.svg rename to ark/docs/public/image/errorSquiggle.svg diff --git a/ark/docs/public/favicon.svg b/ark/docs/public/image/favicon.svg similarity index 100% rename from ark/docs/public/favicon.svg rename to ark/docs/public/image/favicon.svg diff --git a/ark/docs/src/assets/logo.png b/ark/docs/public/image/logo.png similarity index 100% rename from ark/docs/src/assets/logo.png rename to ark/docs/public/image/logo.png diff --git a/ark/docs/src/assets/logoTransparent.png b/ark/docs/public/image/logoTransparent.png similarity index 100% rename from ark/docs/src/assets/logoTransparent.png rename to ark/docs/public/image/logoTransparent.png diff --git a/ark/docs/src/assets/openGraphBackground.png b/ark/docs/public/image/openGraphBackground.png similarity index 100% rename from ark/docs/src/assets/openGraphBackground.png rename to ark/docs/public/image/openGraphBackground.png diff --git a/ark/docs/src/assets/splash.png b/ark/docs/public/image/splash.png similarity index 100% rename from ark/docs/src/assets/splash.png rename to ark/docs/public/image/splash.png diff --git a/ark/fuma/source.config.ts b/ark/docs/source.config.ts similarity index 100% rename from ark/fuma/source.config.ts rename to ark/docs/source.config.ts diff --git a/ark/docs/src/assets/Raleway.ttf b/ark/docs/src/assets/Raleway.ttf deleted file mode 100644 index 33969e8558..0000000000 Binary files a/ark/docs/src/assets/Raleway.ttf and /dev/null differ diff --git a/ark/docs/src/assets/boat.svg b/ark/docs/src/assets/boat.svg deleted file mode 100644 index d6c5070ea7..0000000000 --- a/ark/docs/src/assets/boat.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/bun.svg b/ark/docs/src/assets/bun.svg deleted file mode 100644 index a26d889406..0000000000 --- a/ark/docs/src/assets/bun.svg +++ /dev/null @@ -1,49 +0,0 @@ - - Bun Logo - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/chromium.svg b/ark/docs/src/assets/chromium.svg deleted file mode 100644 index 6bd293bb9f..0000000000 --- a/ark/docs/src/assets/chromium.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/deno.svg b/ark/docs/src/assets/deno.svg deleted file mode 100644 index 7c9e4b9e19..0000000000 --- a/ark/docs/src/assets/deno.svg +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/github.svg b/ark/docs/src/assets/github.svg deleted file mode 100644 index be5067ae7c..0000000000 --- a/ark/docs/src/assets/github.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/githubDark.svg b/ark/docs/src/assets/githubDark.svg deleted file mode 100644 index 45f939727c..0000000000 --- a/ark/docs/src/assets/githubDark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/intellij.svg b/ark/docs/src/assets/intellij.svg deleted file mode 100644 index 0e6a001690..0000000000 --- a/ark/docs/src/assets/intellij.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/js.svg b/ark/docs/src/assets/js.svg deleted file mode 100644 index 54f714f71e..0000000000 --- a/ark/docs/src/assets/js.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/logo.svg b/ark/docs/src/assets/logo.svg deleted file mode 100644 index e96c892243..0000000000 --- a/ark/docs/src/assets/logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/logoTransparent.svg b/ark/docs/src/assets/logoTransparent.svg deleted file mode 100644 index 447dcec589..0000000000 --- a/ark/docs/src/assets/logoTransparent.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/ark/docs/src/assets/neovim.svg b/ark/docs/src/assets/neovim.svg deleted file mode 100644 index 82606c5ea3..0000000000 --- a/ark/docs/src/assets/neovim.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - neovim-mark@2x - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/node.svg b/ark/docs/src/assets/node.svg deleted file mode 100644 index 5a3396c0ec..0000000000 --- a/ark/docs/src/assets/node.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/npm.svg b/ark/docs/src/assets/npm.svg deleted file mode 100644 index f328a78329..0000000000 --- a/ark/docs/src/assets/npm.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/ts.svg b/ark/docs/src/assets/ts.svg deleted file mode 100644 index 6d2b10f98c..0000000000 --- a/ark/docs/src/assets/ts.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/assets/vscode.svg b/ark/docs/src/assets/vscode.svg deleted file mode 100644 index 42949c4ef4..0000000000 --- a/ark/docs/src/assets/vscode.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ark/docs/src/components/AutoplayDemo.tsx b/ark/docs/src/components/AutoplayDemo.tsx deleted file mode 100644 index b01d904b4b..0000000000 --- a/ark/docs/src/components/AutoplayDemo.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react" - -export type AutoplayDemoProps = React.DetailedHTMLProps< - React.VideoHTMLAttributes, - HTMLVideoElement -> & { src: string } - -export const AutoplayDemo = (props: AutoplayDemoProps) => ( -
- -
-
- ArkType (15 nanoseconds) -
-
-   Zod (1374 nanoseconds) -
-
-) - -export const TypeBenchmarksGraph = () => ( -
-
-
- Union Type Instantiations, TypeScript 5.5.3 ( - - source - - ) -
-
-
-
- ArkType Auto-Discriminated (7,801) -
-
-
- Zod Raw (24,944) -
-
- Zod Discriminated (71,312)   -
-
-) diff --git a/ark/docs/src/components/Code.astro b/ark/docs/src/components/Code.astro deleted file mode 100644 index 492b5d5274..0000000000 --- a/ark/docs/src/components/Code.astro +++ /dev/null @@ -1,22 +0,0 @@ ---- -import { - arkHighlight, - type HighlightArgs, - type BuiltinLang -} from "./highlight.ts" - -// ideally we could just import { Code } from "astro:components" instead of -// using this custom component, but as of now, the `Code` component imported from -// `astro:components` does not apply our shikiConfig. - -// If it does in the future, this component can be deleted. - -interface Props extends HighlightArgs { - code: string - lang?: BuiltinLang -} - -const html = await arkHighlight(Astro.props) ---- - - diff --git a/ark/docs/src/components/FloatYourBoat.tsx b/ark/docs/src/components/FloatYourBoat.tsx deleted file mode 100644 index 165ae3c596..0000000000 --- a/ark/docs/src/components/FloatYourBoat.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { motion } from "framer-motion" -import React, { useEffect } from "react" -import { createRoot } from "react-dom/client" -import BoatSvg from "../assets/boat.svg" - -export const FloatYourBoat = () => { - useEffect(() => { - const boatContainer = document.createElement("div") - const BOB_HEIGHT_PX = 2 - const BOB_WIDTH_PX = 16 - const width = window.innerWidth - const loopDuration = width / BOB_WIDTH_PX - const bobFrames: number[] = [] - for (let i = 0; i < loopDuration; i++) - bobFrames.push(i % 2 ? BOB_HEIGHT_PX : 0) - - createRoot( - document.getElementsByClassName("header")[0].appendChild(boatContainer) - ).render() - return () => { - boatContainer.removeChild(boatContainer) - } - }) - return null -} - -type BoatProps = { - loopDuration: number - bobFrames: number[] -} - -const Boat = ({ loopDuration, bobFrames }: BoatProps) => ( - - - -) diff --git a/ark/docs/src/components/Head.astro b/ark/docs/src/components/Head.astro deleted file mode 100644 index 3f4533bfb9..0000000000 --- a/ark/docs/src/components/Head.astro +++ /dev/null @@ -1,20 +0,0 @@ ---- -import type { Props } from "@astrojs/starlight/props" -import BuiltinHead from "@astrojs/starlight/components/Head.astro" - -// Based on https://hideoo.dev/notes/starlight-og-images/ - -// Get the URL of the generated image for the current page using its -// ID and replace the file extension with `.png`. -const ogImageUrl = new URL( - `/og/${Astro.props.id.replace(/\.\w+$/, ".png")}`, - Astro.site -) ---- - - - - - - - diff --git a/ark/docs/src/components/HeroBackdrop.tsx b/ark/docs/src/components/HeroBackdrop.tsx deleted file mode 100644 index a94e555f91..0000000000 --- a/ark/docs/src/components/HeroBackdrop.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react" -import { FloatYourBoat } from "./FloatYourBoat.tsx" -import { PlatformCloud } from "./PlatformCloud.tsx" - -// workaround for compatibility issue between MDX and Astro -declare module "react" { - // T must be present to match type params of base type - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface HTMLAttributes { - class?: string - } -} - -// workaround for compatibility issue between MDX and Astro, -// allows specifying this directive as a prop -export type HeroBackdropProps = { - "client:only": "react" -} - -export type HeroBackdropComponent = ( - props: HeroBackdropProps -) => React.JSX.Element - -export const HeroBackdrop: HeroBackdropComponent = () => ( -
-
- - -
- -
-) diff --git a/ark/docs/src/components/PlatformCloud.tsx b/ark/docs/src/components/PlatformCloud.tsx deleted file mode 100644 index adad257ddd..0000000000 --- a/ark/docs/src/components/PlatformCloud.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { motion } from "framer-motion" -import React from "react" -import Bun from "../assets/bun.svg" -import Chromium from "../assets/chromium.svg" -import Deno from "../assets/deno.svg" -import Intellij from "../assets/intellij.svg" -import Js from "../assets/js.svg" -import Neovim from "../assets/neovim.svg" -import Node from "../assets/node.svg" -import Ts from "../assets/ts.svg" -import Vscode from "../assets/vscode.svg" - -export type SvgLogoProps = { - name: PlatformName -} - -type PlatformName = keyof typeof platforms - -const platforms = { - js: Js, - chromium: Chromium, - node: Node, - deno: Deno, - ts: Ts, - neovim: Neovim, - vscode: Vscode, - intellij: Intellij, - bun: Bun -} - -const SvgLogo = ({ name }: SvgLogoProps) => ( - -) - -export type PlatformCloudProps = { - main: PlatformName - right: PlatformName - top: PlatformName - left: PlatformName -} - -export const PlatformCloud = ({ - main, - right, - top, - left -}: PlatformCloudProps) => ( -
-
- -
-
- -
-
- -
- - - -
-) diff --git a/ark/docs/src/components/Sidebar.astro b/ark/docs/src/components/Sidebar.astro deleted file mode 100644 index 8661fd3720..0000000000 --- a/ark/docs/src/components/Sidebar.astro +++ /dev/null @@ -1,22 +0,0 @@ ---- -import type { Props } from "@astrojs/starlight/props" -import BuiltinSidebar from "@astrojs/starlight/components/Sidebar.astro" -import { experimental_AstroContainer } from "astro/container" - -const container = await experimental_AstroContainer.create() -const html = await container.renderToString(BuiltinSidebar, Astro) - -const onToggleSrc = `if(!this.open) { -const firstChildLink = this.querySelector('a').href -const lastDelimiterIndex = Math.max(firstChildLink.lastIndexOf('/'), firstChildLink.lastIndexOf('#')) -const groupLink = firstChildLink.slice(0, lastDelimiterIndex) -window.location.href = groupLink -}`.replaceAll("\n", ";") - -const newHtml = html.replaceAll( - /
/g, - (match, p1) => `
` -) ---- - - diff --git a/ark/docs/src/components/SyntaxTab.astro b/ark/docs/src/components/SyntaxTab.astro deleted file mode 100644 index 011bc58488..0000000000 --- a/ark/docs/src/components/SyntaxTab.astro +++ /dev/null @@ -1,24 +0,0 @@ ---- -import { TabItem } from "@astrojs/starlight/components" -import type { SyntaxKind } from "./utils.ts" -import type { show } from "@ark/util" - -type discriminateTabProps = - kind extends unknown ? - show< - { [k in kind]: true } & { [k in Exclude]?: never } & { - children: unknown - } - > - : never - -type Props = discriminateTabProps - -const kind: SyntaxKind = Object.keys(Astro.props)[0] as never - -const codeblockHtml = await Astro.slots.render("default") ---- - - - - diff --git a/ark/docs/src/components/addCopyButtonListeners.js b/ark/docs/src/components/addCopyButtonListeners.js deleted file mode 100644 index 588378fcfe..0000000000 --- a/ark/docs/src/components/addCopyButtonListeners.js +++ /dev/null @@ -1,47 +0,0 @@ -const hoverSelector = ".twoslash-popup-code" -const errorSelector = ".twoslash-error-line" -const completionSelector = ".twoslash-completion-cursor" -const metaSelector = `${hoverSelector}, ${errorSelector}, ${completionSelector}` - -const distillTwoslashCode = container => { - const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT) - - let src = "" - while (walker.nextNode()) { - /** @type { Element } */ - const parentNode = walker.currentNode.parentNode - if (parentNode.closest(errorSelector)) { - // if a twoslash error was rendered in this position, we need an additional newline - src += "\n" - } - if (!parentNode?.closest(metaSelector)) { - // if the node is not a meta node (hover, error or completion) add its textContent - src += walker.currentNode.textContent ?? "" - } - } - - return src.trim() -} - -document.querySelectorAll(".code-container").forEach(codeContainer => { - const src = codeContainer.querySelector(".code-source") - const copyButton = codeContainer.querySelector(".copy-button") - const icon = codeContainer.querySelector(".copy-icon") - - if ("hasListener" in copyButton) return - copyButton.hasListener = true - - copyButton.addEventListener("click", async () => { - const textToCopy = distillTwoslashCode(src) - await navigator.clipboard.writeText(textToCopy) - - icon.setAttribute("src", "/check.svg") - copyButton.setAttribute("disabled", "1") - copyButton.setAttribute("style", "opacity: .6;") - setTimeout(() => { - icon.setAttribute("src", "/copy.svg") - copyButton.removeAttribute("disabled") - copyButton.removeAttribute("style") - }, 2000) - }) -}) diff --git a/ark/docs/src/components/highlight.ts b/ark/docs/src/components/highlight.ts deleted file mode 100644 index 9382437204..0000000000 --- a/ark/docs/src/components/highlight.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { transformerNotationErrorLevel } from "@shikijs/transformers" -import arkdarkColors from "arkdark/arkdark.json" -import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json" -import { createHighlighter } from "shiki" -import { addCopyButton, twoslash } from "./shiki.config.js" - -let highlighter: Awaited> | undefined - -export type BuiltinLang = "ts" | "bash" | "jsonc" - -export type HighlightArgs = { - code: string - lang?: BuiltinLang -} - -export const arkHighlight = async (args: HighlightArgs) => { - highlighter ??= await createHighlighter({ - themes: [arkdarkColors], - langs: [{ ...arktypeTextmate, name: "ts" } as never, "bash", "jsonc"] - }) - return highlighter.codeToHtml(args.code, { - lang: args.lang ?? "ts", - theme: "ArkDark", - transformers: [ - twoslash, - transformerNotationErrorLevel() as never, - addCopyButton - ] - }) -} diff --git a/ark/docs/src/components/shiki.config.js b/ark/docs/src/components/shiki.config.js deleted file mode 100644 index ff5c4aa166..0000000000 --- a/ark/docs/src/components/shiki.config.js +++ /dev/null @@ -1,149 +0,0 @@ -// @ts-check - -import { transformerNotationErrorLevel } from "@shikijs/transformers" -import { transformerTwoslash } from "@shikijs/twoslash" -import arkdarkColors from "arkdark/arkdark.json" -import arkdarkPackageJson from "arkdark/package.json" -import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json" -import { defaultCompilerOptions } from "twoslash" -import addCopyButtonListenersSrc from "./addCopyButtonListeners.js?raw" - -// Theme adjustments - -arkdarkColors.colors["editor.background"] = "#00000027" - -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, - noErrorTruncation: true - }, - extraFiles: { - "global.d.ts": `import type * as a from "arktype" - -declare global { - const type: typeof a.type - namespace type { - export interface cast { - [a.inferred]?: to - } - - export type errors = a.ArkErrors - } - - type type = a.Type - const scope: typeof a.scope -}` - }, - filterNode: node => { - switch (node.type) { - case "hover": - if (node.text.endsWith(", {}>")) - // omit default scope param from type display - node.text = node.text.slice(0, -5) + ">" - if (node.text.startsWith("type")) { - return true - } - // when `noErrorTruncation` is enabled, TS displays the type - // of an anonymous cyclic type as `any` instead of using - // `...`, so replace it to clarify the type is accurately inferred - node.text = node.text.replaceAll(" any", " ...") - if (node.text.startsWith("const")) { - // show type with completions populated for known examples - node.text = node.text.replace( - "version?: never", - `version?: number | string` - ) - node.text = node.text.replace( - "versions?: never", - "versions?: (number | string)[]" - ) - // 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("RuntimeErrors.summary") && node.docs) { - // this shows error summary in JSDoc - // re-add spaces stripped out during processing - node.docs = node.docs.replaceAll("•", " •") - return true - } - if (expression === `platform: "android" | "ios"`) { - // this helps demonstrate narrowing on discrimination - 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("shiki").ShikiTransformer} */ -export const addCopyButton = { - name: "addCopyButton", - postprocess(html) { - return `
-
- ${html} -
- -
` - } -} - -/** @type { import("astro").ShikiConfig } */ -export const shikiConfig = { - theme: arkdarkColors, - // @ts-ignore - langs: [arktypeTextmate], - // @ts-ignore - transformers: [twoslash, transformerNotationErrorLevel(), addCopyButton], - wrap: true -} diff --git a/ark/docs/src/components/utils.ts b/ark/docs/src/components/utils.ts deleted file mode 100644 index 61e0c43609..0000000000 --- a/ark/docs/src/components/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { PartialRecord } from "@ark/util" - -export type SyntaxKind = "string" | "tuple" | "spread" | "fluent" - -export type ExamplesBySyntaxKind = PartialRecord diff --git a/ark/docs/src/content/config.ts b/ark/docs/src/content/config.ts deleted file mode 100644 index c59418c163..0000000000 --- a/ark/docs/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { docsSchema, i18nSchema } from "@astrojs/starlight/schema" -import { defineCollection } from "astro:content" - -export const collections = { - docs: defineCollection({ schema: docsSchema() }), - i18n: defineCollection({ type: "data", schema: i18nSchema() }) -} diff --git a/ark/docs/src/content/docs/definitions.mdx b/ark/docs/src/content/docs/definitions.mdx deleted file mode 100644 index 14bf482696..0000000000 --- a/ark/docs/src/content/docs/definitions.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Definitions -sidebar: - order: 1 ---- - -import { Tabs } from "@astrojs/starlight/components" -import SyntaxTab from "../../components/SyntaxTab.astro" - -### Kitchen Sink - -```ts -export const currentTsSyntax = type({ - keyword: "null", - stringLiteral: "'TS'", - numberLiteral: "5", - bigintLiteral: "5n", - union: "string|number", - intersection: "boolean&true", - array: "Date[]", - grouping: "(0|1)[]", - objectLiteral: { - nested: "string", - "optional?": "number" - }, - arrayOfObjectLiteral: [ - { - name: "string" - }, - "[]" - ], - tuple: ["number", "number"], - keyof: "keyof object", - variadicTuples: ["true", "...", "false[]"], - arrayOfObjectLiteralChained: type({ name: "string" }).array() -}) -``` - -## Constraints - -```ts -// runtime-specific syntax and builtin keywords with great error messages - -export const validationSyntax = type({ - keywords: "string.email | string.uuid | string.creditCard | number.integer", // and many more - builtinParsers: "string.date.parse", // parses a Date from a string - nativeRegexLiteral: /@arktype\.io/, - embeddedRegexLiteral: "string.email & /@arktype\\.io/", - divisibility: "number % 10", // a multiple of 10 - bound: "string.alpha > 10", // an alpha-only string with more than 10 characters - range: "1 <= string.email[] < 100", // a list of 1 to 99 emails - narrows: ["number", ":", n => n % 2 === 1], // an odd integer - morphs: ["string", "=>", parseFloat] // validates a string input then parses it to a number -}) - -// root-level expressions - -const intersected = type({ value: "string" }, "&", { format: "'bigint'" }) - -// chained expressions via .or, .and, .narrow, .pipe and much more -// (these replace previous helper methods like union and intersection) - -const user = type({ - name: "string", - age: "number" -}) - -// type is fully introspectable and traversable -const parseUser = type("string").pipe(s => JSON.parse(s), user) - -const maybeMe = parseUser('{ "name": "David" }') - -if (maybeMe instanceof type.errors) { - // "age must be a number (was missing)" - console.log(maybeMe.summary) -} -``` - -{ - -// - - - - \ No newline at end of file diff --git a/ark/fuma/public/image/logo.png b/ark/fuma/public/image/logo.png deleted file mode 100644 index e757fb7588..0000000000 Binary files a/ark/fuma/public/image/logo.png and /dev/null differ diff --git a/ark/fuma/public/image/logoTransparent.png b/ark/fuma/public/image/logoTransparent.png deleted file mode 100644 index 2c9e742a75..0000000000 Binary files a/ark/fuma/public/image/logoTransparent.png and /dev/null differ diff --git a/ark/fuma/public/image/openGraphBackground.png b/ark/fuma/public/image/openGraphBackground.png deleted file mode 100644 index 1d9a826d60..0000000000 Binary files a/ark/fuma/public/image/openGraphBackground.png and /dev/null differ diff --git a/ark/fuma/public/image/splash.png b/ark/fuma/public/image/splash.png deleted file mode 100644 index 7d9ad065f4..0000000000 Binary files a/ark/fuma/public/image/splash.png and /dev/null differ diff --git a/ark/fuma/tsconfig.json b/ark/fuma/tsconfig.json deleted file mode 100644 index 1adc762b85..0000000000 --- a/ark/fuma/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - // unfortunately, twoslash doesn't seem to respect customConditions, - // so .d.ts will need to be rebuilt to see its static compilation updated - "module": "ESNext", - "moduleResolution": "Bundler", - "allowJs": true, - "jsx": "preserve", - // we don't need declarations for docs, and enabling it causes - // pnpm resolution errors - "declaration": false, - "lib": ["ESNext", "DOM"], - "baseUrl": ".", - "plugins": [ - { - "name": "next" - } - ], - "incremental": true - }, - "mdx": { - "checkMdx": true - }, - "include": [ - "next.config.ts", - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": ["node_modules"] -} diff --git a/ark/repo/nodeOptions.js b/ark/repo/nodeOptions.js new file mode 100644 index 0000000000..8eba2d0d4a --- /dev/null +++ b/ark/repo/nodeOptions.js @@ -0,0 +1,19 @@ +// @ts-check + +const [major, minor] = process.version.replace("v", "").split(".").map(Number) + +const versionedFlags = + major > 22 || (major === 22 && minor >= 7) ? + "--experimental-transform-types --no-warnings" + : (console.log( + "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." + ), + "--import tsx") + +export const nodeDevOptions = `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}` + +/** + * @param {string} [extraOpts] + */ +export const addNodeDevOptions = extraOpts => + (process.env.NODE_OPTIONS = `${nodeDevOptions} ${extraOpts ?? ""}`) diff --git a/ark/repo/package.json b/ark/repo/package.json index c31a7634b6..3f0f9b4f0a 100644 --- a/ark/repo/package.json +++ b/ark/repo/package.json @@ -6,11 +6,12 @@ "dependencies": { "@ark/attest": "workspace:*", "@ark/fs": "workspace:*", + "@sinclair/typebox": "0.34.13", "@trpc/server": "10.45.2", "arktype": "workspace:*", - "type-fest": "4.30.0", + "type-fest": "4.30.2", "typescript": "catalog:", - "zod": "3.23.8" + "zod": "3.24.1" }, "scripts": { "build": "echo No build required!" diff --git a/ark/repo/ts.js b/ark/repo/ts.js index fb90290954..5536b7cec2 100755 --- a/ark/repo/ts.js +++ b/ark/repo/ts.js @@ -2,17 +2,8 @@ // @ts-check import { execSync } from "node:child_process" +import { addNodeDevOptions } from "./nodeOptions.js" -const [major, minor] = process.version.replace("v", "").split(".").map(Number) - -const versionedFlags = - major > 22 || (major === 22 && minor >= 7) ? - "--experimental-transform-types --no-warnings" - : (console.log( - "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." - ), - "--import tsx") - -process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}` +addNodeDevOptions() execSync(`node ${process.argv.slice(2).join(" ")}`, { stdio: "inherit" }) diff --git a/ark/schema/package.json b/ark/schema/package.json index cc765d349a..a8c425ddaa 100644 --- a/ark/schema/package.json +++ b/ark/schema/package.json @@ -1,6 +1,6 @@ { "name": "@ark/schema", - "version": "0.28.0", + "version": "0.29.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/schema/structure/sequence.ts b/ark/schema/structure/sequence.ts index cfb4d74ebb..34d68d3201 100644 --- a/ark/schema/structure/sequence.ts +++ b/ark/schema/structure/sequence.ts @@ -169,7 +169,7 @@ const implementation: nodeImplementationOf = return throwParseError(postfixWithoutVariadicMessage) if (schema.optionals?.length || schema.defaultables?.length) - return throwParseError(postfixFollowingOptionalMessage) + return throwParseError(postfixAfterOptionalOrDefaultableMessage) } if (schema.minVariadicLength && !schema.variadic) { return throwParseError( @@ -545,11 +545,11 @@ const sequenceTupleToInner = (tuple: SequenceTuple): Sequence.Inner => return result }, {}) -export const postfixFollowingOptionalMessage = +export const postfixAfterOptionalOrDefaultableMessage = "A postfix required element cannot follow an optional or defaultable element" -export type postfixFollowingOptionalMessage = - typeof postfixFollowingOptionalMessage +export type postfixAfterOptionalOrDefaultableMessage = + typeof postfixAfterOptionalOrDefaultableMessage export const postfixWithoutVariadicMessage = "A postfix element requires a variadic element" diff --git a/ark/type/__tests__/arrays/variadicTuple.test.ts b/ark/type/__tests__/arrays/variadicTuple.test.ts index 3d1733fc25..bb4b1b7147 100644 --- a/ark/type/__tests__/arrays/variadicTuple.test.ts +++ b/ark/type/__tests__/arrays/variadicTuple.test.ts @@ -1,8 +1,9 @@ import { attest, contextualize } from "@ark/attest" +import { postfixAfterOptionalOrDefaultableMessage } from "@ark/schema" import { scope, type } from "arktype" import { multipleVariadicMesage, - optionalPostVariadicMessage, + optionalOrDefaultableAfterVariadicMessage, writeNonArraySpreadMessage } from "arktype/internal/parser/tupleLiteral.ts" @@ -86,7 +87,29 @@ contextualize(() => { // no type error yet, ideally would have one if tuple // parsing were more precise for nested spread tuples attest(() => type(["...", "string[]", "...", ["string?"]])).throws( - optionalPostVariadicMessage + optionalOrDefaultableAfterVariadicMessage ) }) + + it("errors on postfix following optional", () => { + attest(() => + // @ts-expect-error + type(["number?", "...", "boolean[]", "symbol"]) + ).throwsAndHasTypeError(postfixAfterOptionalOrDefaultableMessage) + }) + + it("errors on postfix following defaultable", () => { + attest(() => + // @ts-expect-error + type(["number = 0", "...", "boolean[]", "symbol"]) + ).throwsAndHasTypeError(postfixAfterOptionalOrDefaultableMessage) + }) + + it("doesn't mistake a string literal containing '=' for defaultable", () => { + const t = type(["'='", "number"]) + + attest<["=", number]>(t.t) + attest(t.infer).type.toString.snap(`["=", number]`) + attest(t.expression).snap('["=", number]') + }) }) diff --git a/ark/type/index.ts b/ark/type/index.ts index 5a170f90ab..cd4f6843bb 100644 --- a/ark/type/index.ts +++ b/ark/type/index.ts @@ -19,5 +19,5 @@ export { type Ark } from "./keywords/keywords.ts" export { Module, type BoundModule, type Submodule } from "./module.ts" -export { module, scope, type Scope } from "./scope.ts" +export { scope, type Scope } from "./scope.ts" export { Type } from "./type.ts" diff --git a/ark/type/keywords/Array.ts b/ark/type/keywords/Array.ts index 5ce370873c..249b81900f 100644 --- a/ark/type/keywords/Array.ts +++ b/ark/type/keywords/Array.ts @@ -2,7 +2,7 @@ import { genericNode, intrinsic, rootSchema } from "@ark/schema" import { Hkt, liftArray, type Digit } from "@ark/util" import type { To } from "../attributes.ts" import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" class liftFromHkt extends Hkt<[element: unknown]> { declare body: liftArray extends infer lifted ? @@ -22,7 +22,7 @@ const liftFrom = genericNode("element")(args => { ) }, liftFromHkt) -export const arkArray: arkArray.module = arkModule({ +export const arkArray: arkArray.module = Scope.module({ root: intrinsic.Array, readonly: "root", index: intrinsic.nonNegativeIntegerString, diff --git a/ark/type/keywords/FormData.ts b/ark/type/keywords/FormData.ts index 15598e92ed..92eb1834c8 100644 --- a/ark/type/keywords/FormData.ts +++ b/ark/type/keywords/FormData.ts @@ -2,7 +2,7 @@ import { rootSchema } from "@ark/schema" import { registry } from "@ark/util" import type { To } from "../attributes.ts" import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" export type FormDataValue = string | File @@ -21,7 +21,7 @@ const parsed = rootSchema({ } }) -export const arkFormData: arkFormData.module = arkModule({ +export const arkFormData: arkFormData.module = Scope.module({ root: ["instanceof", FormData], value, parsed, diff --git a/ark/type/keywords/TypedArray.ts b/ark/type/keywords/TypedArray.ts index 6937877b4b..e56f90eaab 100644 --- a/ark/type/keywords/TypedArray.ts +++ b/ark/type/keywords/TypedArray.ts @@ -1,7 +1,7 @@ import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" -export const TypedArray: TypedArray.module = arkModule({ +export const TypedArray: TypedArray.module = Scope.module({ Int8: ["instanceof", Int8Array], Uint8: ["instanceof", Uint8Array], Uint8Clamped: ["instanceof", Uint8ClampedArray], diff --git a/ark/type/keywords/builtins.ts b/ark/type/keywords/builtins.ts index 754c93bb4b..d3982e2e0b 100644 --- a/ark/type/keywords/builtins.ts +++ b/ark/type/keywords/builtins.ts @@ -2,7 +2,7 @@ import { genericNode, intrinsic } from "@ark/schema" import type * as util from "@ark/util" import { Hkt, type Key } from "@ark/util" import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" class MergeHkt extends Hkt<[base: object, props: object]> { declare body: util.merge @@ -13,7 +13,7 @@ const Merge = genericNode( ["props", intrinsic.object] )(args => args.base.merge(args.props), MergeHkt) -export const arkBuiltins: arkBuiltins = arkModule({ +export const arkBuiltins: arkBuiltins = Scope.module({ Key: intrinsic.key, Merge }) diff --git a/ark/type/keywords/constructors.ts b/ark/type/keywords/constructors.ts index d235f54abe..97a0c69e33 100644 --- a/ark/type/keywords/constructors.ts +++ b/ark/type/keywords/constructors.ts @@ -7,10 +7,10 @@ import { type PlatformObjects } from "@ark/util" import type { Module, Submodule } from "../module.ts" +import { Scope } from "../scope.ts" import { arkArray } from "./Array.ts" import { arkFormData } from "./FormData.ts" import { TypedArray } from "./TypedArray.ts" -import { arkModule } from "./utils.ts" const omittedPrototypes = { Boolean: 1, @@ -18,7 +18,7 @@ const omittedPrototypes = { String: 1 } satisfies KeySet -export const arkPrototypes: arkPrototypes.module = arkModule({ +export const arkPrototypes: arkPrototypes.module = Scope.module({ ...flatMorph( { ...ecmascriptConstructors, ...platformConstructors }, (k, v) => (k in omittedPrototypes ? [] : ([k, ["instanceof", v]] as const)) diff --git a/ark/type/keywords/number.ts b/ark/type/keywords/number.ts index 77232f3b75..b2b1570dae 100644 --- a/ark/type/keywords/number.ts +++ b/ark/type/keywords/number.ts @@ -1,6 +1,6 @@ import { intrinsic, rootSchema } from "@ark/schema" import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" /** * As per the ECMA-262 specification: @@ -34,7 +34,7 @@ export const integer = rootSchema({ divisor: 1 }) -export const number: number.module = arkModule({ +export const number: number.module = Scope.module({ root: intrinsic.number, integer, epoch, diff --git a/ark/type/keywords/string.ts b/ark/type/keywords/string.ts index 6b08853946..83121ffcc9 100644 --- a/ark/type/keywords/string.ts +++ b/ark/type/keywords/string.ts @@ -15,8 +15,8 @@ import { } from "@ark/util" import type { To } from "../attributes.ts" import type { Module, Submodule } from "../module.ts" +import { Scope } from "../scope.ts" import { number } from "./number.ts" -import { arkModule } from "./utils.ts" // Non-trivial expressions should have an explanation or attribution @@ -38,7 +38,7 @@ const stringIntegerRoot = regexStringNode( "a well-formed integer string" ) -export const stringInteger: stringInteger.module = arkModule({ +export const stringInteger: stringInteger.module = Scope.module({ root: stringIntegerRoot, parse: rootSchema({ in: stringIntegerRoot, @@ -65,7 +65,7 @@ export declare namespace stringInteger { } } -const base64 = arkModule({ +const base64 = Scope.module({ root: regexStringNode( /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/, "base64-encoded" @@ -89,7 +89,7 @@ declare namespace base64 { const preformattedCapitalize = regexStringNode(/^[A-Z].*$/, "capitalized") -export const capitalize: capitalize.module = arkModule({ +export const capitalize: capitalize.module = Scope.module({ root: rootSchema({ in: "string", morphs: (s: string) => s.charAt(0).toUpperCase() + s.slice(1), @@ -269,7 +269,7 @@ const epochRoot = stringInteger.root.internal }) .assertHasKind("intersection") -const epoch = arkModule({ +const epoch = Scope.module({ root: epochRoot, parse: rootSchema({ in: epochRoot, @@ -283,7 +283,7 @@ const isoRoot = regexStringNode( "an ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ) date" ).internal.assertHasKind("intersection") -const iso = arkModule({ +const iso = Scope.module({ root: isoRoot, parse: rootSchema({ in: isoRoot, @@ -292,7 +292,7 @@ const iso = arkModule({ }) }) -export const stringDate: stringDate.module = arkModule({ +export const stringDate: stringDate.module = Scope.module({ root: parsableDate, parse: rootSchema({ declaredIn: parsableDate, @@ -365,7 +365,7 @@ const ipv6Matcher = new RegExp( ")(%[0-9a-zA-Z.]{1,})?$" ) -export const ip: ip.module = arkModule({ +export const ip: ip.module = Scope.module({ root: ["v4 | v6", "@", "an IP address"], v4: regexStringNode(ipv4Matcher, "an IPv4 address"), v6: regexStringNode(ipv6Matcher, "an IPv6 address") @@ -428,7 +428,7 @@ const parseJson: Morph = (s: string, ctx: TraversalContext) => { } } -export const json: stringJson.module = arkModule({ +export const json: stringJson.module = Scope.module({ root: jsonRoot, parse: rootSchema({ in: "string", @@ -450,7 +450,7 @@ export declare namespace stringJson { const preformattedLower = regexStringNode(/^[a-z]*$/, "only lowercase letters") -const lower: lower.module = arkModule({ +const lower: lower.module = Scope.module({ root: rootSchema({ in: "string", morphs: (s: string) => s.toLowerCase(), @@ -500,27 +500,27 @@ const normalizeNodes = flatMorph( ] as const ) -export const NFC = arkModule({ +export const NFC = Scope.module({ root: normalizeNodes.NFC, preformatted: preformattedNodes.NFC }) -export const NFD = arkModule({ +export const NFD = Scope.module({ root: normalizeNodes.NFD, preformatted: preformattedNodes.NFD }) -export const NFKC = arkModule({ +export const NFKC = Scope.module({ root: normalizeNodes.NFKC, preformatted: preformattedNodes.NFKC }) -export const NFKD = arkModule({ +export const NFKD = Scope.module({ root: normalizeNodes.NFKD, preformatted: preformattedNodes.NFKD }) -export const normalize = arkModule({ +export const normalize = Scope.module({ root: "NFC", NFC, NFD, @@ -583,7 +583,7 @@ const numericRoot = regexStringNode( "a well-formed numeric string" ) -export const numeric: stringNumeric.module = arkModule({ +export const numeric: stringNumeric.module = Scope.module({ root: numericRoot, parse: rootSchema({ in: numericRoot, @@ -618,7 +618,7 @@ const preformattedTrim = regexStringNode( "trimmed" ) -const trim: trim.module = arkModule({ +const trim: trim.module = Scope.module({ root: rootSchema({ in: "string", morphs: (s: string) => s.trim(), @@ -640,7 +640,7 @@ export declare namespace trim { const preformattedUpper = regexStringNode(/^[A-Z]*$/, "only uppercase letters") -const upper: upper.module = arkModule({ +const upper: upper.module = Scope.module({ root: rootSchema({ in: "string", morphs: (s: string) => s.toUpperCase(), @@ -679,7 +679,7 @@ const urlRoot = rootSchema({ } }) -export const url: url.module = arkModule({ +export const url: url.module = Scope.module({ root: urlRoot, parse: rootSchema({ declaredIn: urlRoot, @@ -707,7 +707,7 @@ export declare namespace url { } // Based on https://github.com/validatorjs/validator.js/blob/master/src/lib/isUUID.js -export const uuid = arkModule({ +export const uuid = Scope.module({ // the meta tuple expression ensures the error message does not delegate // to the individual branches, which are too detailed root: ["versioned | nil | max", "@", "a UUID"], @@ -767,7 +767,7 @@ export declare namespace uuid { } } -export const string = arkModule({ +export const string = Scope.module({ root: intrinsic.string, alpha: regexStringNode(/^[A-Za-z]*$/, "only letters"), alphanumeric: regexStringNode(/^[A-Za-z\d]*$/, "only letters and digits 0-9"), diff --git a/ark/type/keywords/ts.ts b/ark/type/keywords/ts.ts index 5b5ac52e23..04788fb79a 100644 --- a/ark/type/keywords/ts.ts +++ b/ark/type/keywords/ts.ts @@ -9,9 +9,9 @@ import { } from "@ark/util" import type { To } from "../attributes.ts" import type { Module, Submodule } from "../module.ts" -import { arkModule } from "./utils.ts" +import { Scope } from "../scope.ts" -export const arkTsKeywords: arkTsKeywords = arkModule({ +export const arkTsKeywords: arkTsKeywords = Scope.module({ bigint: intrinsic.bigint, boolean: intrinsic.boolean, false: intrinsic.false, @@ -47,7 +47,7 @@ export declare namespace arkTsKeywords { } } -export const unknown = arkModule({ +export const unknown = Scope.module({ root: intrinsic.unknown, any: intrinsic.unknown }) @@ -61,7 +61,7 @@ export declare namespace unknown { } } -export const json = arkModule({ +export const json = Scope.module({ root: intrinsic.json, stringify: node("morph", { in: intrinsic.json, @@ -79,7 +79,7 @@ export declare namespace json { } } -export const object = arkModule({ +export const object = Scope.module({ root: intrinsic.object, json }) @@ -162,7 +162,7 @@ const Extract = genericNode("T", "U")( ExtractHkt ) -export const arkTsGenerics: arkTsGenerics.module = arkModule({ +export const arkTsGenerics: arkTsGenerics.module = Scope.module({ Exclude, Extract, Omit, diff --git a/ark/type/keywords/utils.ts b/ark/type/keywords/utils.ts deleted file mode 100644 index a701488fc6..0000000000 --- a/ark/type/keywords/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { module } from "../scope.ts" - -export const arkModule = module diff --git a/ark/type/package.json b/ark/type/package.json index 93bb398aad..88a07a107e 100644 --- a/ark/type/package.json +++ b/ark/type/package.json @@ -1,7 +1,7 @@ { "name": "arktype", "description": "TypeScript's 1:1 validator, optimized from editor to runtime", - "version": "2.0.0-rc.28", + "version": "2.0.0-rc.29", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/type/parser/definition.ts b/ark/type/parser/definition.ts index 0de511eb01..3b510079c9 100644 --- a/ark/type/parser/definition.ts +++ b/ark/type/parser/definition.ts @@ -32,12 +32,7 @@ import { type inferObjectLiteral, type validateObjectLiteral } from "./objectLiteral.ts" -import type { - DefaultablePropertyTuple, - OptionalPropertyDefinition, - PossibleDefaultableStringDefinition, - validatePossibleStringDefault -} from "./property.ts" +import type { isDefaultable, OptionalPropertyDefinition } from "./property.ts" import { parseString, type BaseCompletions, @@ -131,10 +126,8 @@ export type validateDefinition = : [def] extends [anyOrNever] ? def : def extends OptionalPropertyDefinition ? ErrorMessage - : def extends DefaultablePropertyTuple ? + : isDefaultable extends true ? ErrorMessage - : def extends PossibleDefaultableStringDefinition ? - validatePossibleStringDefault : validateInnerDefinition // validates the definition without checking for optionals/defaults this should diff --git a/ark/type/parser/property.ts b/ark/type/parser/property.ts index 7c7eae7a0d..deb914efe3 100644 --- a/ark/type/parser/property.ts +++ b/ark/type/parser/property.ts @@ -10,7 +10,6 @@ import { type ErrorType, type typeToString } from "@ark/util" -import type { validateString } from "./ast/validate.ts" import { parseInnerDefinition, type inferDefinition, @@ -70,26 +69,17 @@ export type validateProperty = // an already optional or index key def extends OptionalPropertyDefinition ? ErrorMessage - : def extends DefaultablePropertyTuple ? + : isDefaultable extends true ? ErrorMessage - : def extends PossibleDefaultableStringDefinition ? - validatePossibleStringDefault< - def, - $, - args, - invalidDefaultableKeyKindMessage - > : validateInnerDefinition -export type validatePossibleStringDefault< - def extends string, - $, - args, - errorMessage extends string -> = - parseString extends DefaultablePropertyTuple ? - ErrorMessage - : validateString +export type isDefaultable = + def extends DefaultablePropertyTuple ? true + : def extends PossibleDefaultableStringDefinition ? + parseString extends DefaultablePropertyTuple ? + true + : false + : false type validateSpread = inferredProperty extends object ? validateInnerDefinition diff --git a/ark/type/parser/shift/scanner.ts b/ark/type/parser/shift/scanner.ts index a5b0bb7365..83ae18f703 100644 --- a/ark/type/parser/shift/scanner.ts +++ b/ark/type/parser/shift/scanner.ts @@ -44,13 +44,14 @@ export class ArkTypeScanner< static lookaheadIsFinalizing = ( lookahead: string, unscanned: string - ): lookahead is ">" | "," | "=" => + ): lookahead is ">" | "," | "=" | "?" => lookahead === ">" ? unscanned[0] === "=" ? // >== would only occur in an expression like Array==5 // otherwise, >= would only occur as part of a bound like number>=5 unscanned[1] === "=" - // if > is the end of a generic instantiation, the next token will be an operator or the end of the string + // if > is the end of a generic instantiation, the next token will be + // an operator or the end of the string : unscanned.trimStart() === "" || isKeyOf(unscanned.trimStart()[0], ArkTypeScanner.terminatingChars) // "=" is a finalizer on its own (representing a default value), diff --git a/ark/type/parser/tupleLiteral.ts b/ark/type/parser/tupleLiteral.ts index fcec5973a0..3c709d7b0b 100644 --- a/ark/type/parser/tupleLiteral.ts +++ b/ark/type/parser/tupleLiteral.ts @@ -1,6 +1,7 @@ import { $ark, makeRootAndArrayPropertiesMutable, + postfixAfterOptionalOrDefaultableMessage, type BaseParseContext, type BaseRoot, type mutableInnerOfKind, @@ -22,8 +23,8 @@ import type { inferDefinition, validateInnerDefinition } from "./definition.ts" import { parseProperty, type DefaultablePropertyTuple, - type OptionalPropertyDefinition, - type PossibleDefaultableStringDefinition + type isDefaultable, + type OptionalPropertyDefinition } from "./property.ts" export const parseTupleLiteral = ( @@ -99,9 +100,15 @@ const appendRequiredElement = ( base: mutableInnerOfKind<"sequence">, element: BaseRoot ): mutableInnerOfKind<"sequence"> => { - if (base.optionals) - // e.g. [string?, number] - return throwParseError(requiredPostOptionalMessage) + if (base.defaultables || base.optionals) { + return throwParseError( + base.variadic ? + // e.g. [boolean = true, ...string[], number] + postfixAfterOptionalOrDefaultableMessage + // e.g. [string?, number] + : requiredPostOptionalMessage + ) + } if (base.variadic) { // e.g. [...string[], number] base.postfix = append(base.postfix, element) @@ -118,7 +125,7 @@ const appendOptionalElement = ( ): mutableInnerOfKind<"sequence"> => { if (base.variadic) // e.g. [...string[], number?] - return throwParseError(optionalPostVariadicMessage) + return throwParseError(optionalOrDefaultableAfterVariadicMessage) // e.g. [string, number?] base.optionals = append(base.optionals, element) return base @@ -131,7 +138,7 @@ const appendDefaultableElement = ( ): mutableInnerOfKind<"sequence"> => { if (base.variadic) // e.g. [...string[], number = 0] - return throwParseError(defaultablePostVariadicMessage) + return throwParseError(optionalOrDefaultableAfterVariadicMessage) if (base.optionals) // e.g. [string?, number = 0] return throwParseError(defaultablePostOptionalMessage) @@ -181,16 +188,29 @@ const appendSpreadBranch = ( return base } -type SequenceParsePhase = satisfy< +type SequencePhase = satisfy< keyof Sequence.Inner, - "prefix" | "optionals" | "defaultables" | "postfix" + | SequencePhase.prefix + | SequencePhase.optionals + | SequencePhase.defaultables + | SequencePhase.postfix > +declare namespace SequencePhase { + export type prefix = "prefix" + + export type optionals = "optionals" + + export type defaultables = "defaultables" + + export type postfix = "postfix" +} + type SequenceParseState = { unscanned: array inferred: array validated: array - phase: SequenceParsePhase + phase: SequencePhase } type parseSequence = parseNextElement< @@ -198,13 +218,16 @@ type parseSequence = parseNextElement< unscanned: def inferred: [] validated: [] - phase: "prefix" + phase: SequencePhase.prefix }, $, args > -type PreparsedElementKind = "required" | "optionals" | "defaultables" +type PreparsedElementKind = + | "required" + | SequencePhase.optionals + | SequencePhase.defaultables type PreparsedElement = { head: unknown @@ -217,6 +240,12 @@ type PreparsedElement = { declare namespace PreparsedElement { export type from = result + + export type required = "required" + + export type optionals = "optionals" + + export type defaultables = "defaultables" } type preparseNextState = @@ -239,11 +268,10 @@ type preparseNextElement< validated: validateInnerDefinition // if inferredHead is optional and the element is spread, this will be an error // handled in nextValidatedSpreadElements - kind: head extends OptionalPropertyDefinition ? "optionals" - : head extends DefaultablePropertyTuple ? "defaultables" - : // TODO: more precise - head extends PossibleDefaultableStringDefinition ? "defaultables" - : "required" + kind: head extends OptionalPropertyDefinition ? PreparsedElement.optionals + : head extends DefaultablePropertyTuple ? PreparsedElement.defaultables + : isDefaultable extends true ? PreparsedElement.defaultables + : PreparsedElement.required spread: spread }> @@ -254,9 +282,15 @@ type parseNextElement = unscanned: next["tail"] inferred: nextInferred validated: nextValidated - phase: next["kind"] extends "optionals" | "defaultables" ? next["kind"] - : number extends nextInferred["length"] ? "postfix" - : "prefix" + phase: next["kind"] extends ( + SequencePhase.optionals | SequencePhase.defaultables + ) ? + next["kind"] + : // if we're parsing the variadic element, don't update the phase + // so that we can still check it to ensure we don't have + // postfix elements following optionals or defaultables + number extends nextInferred["length"] ? s["phase"] + : SequencePhase.prefix }, $, args @@ -266,8 +300,9 @@ type parseNextElement = type nextInferred = next["spread"] extends true ? [...s["inferred"], ...conform] - : next["kind"] extends "optionals" ? [...s["inferred"], next["inferred"]?] - : [...s["inferred"], next["inferred"]] + : next["kind"] extends SequencePhase.optionals ? + [...s["inferred"], next["inferred"]?] + : [...s["inferred"], next["inferred"]] type nextValidated< s extends SequenceParseState, @@ -287,7 +322,12 @@ type nextValidatedSpreadOperatorIfPresent< next["inferred"] extends infer spreadOperand extends array ? // if the spread operand is a fixed-length tuple, it won't be a variadic element // and therefore doesn't need to be validated as one - [s["phase"], number] extends ["postfix", spreadOperand["length"]] ? + // there are some edge cases around spreads like `[string?, ...[number?]]` which should + // result in a type error but currently don't. TS also doesn't handle those, + // but would be nice to have at some point regardless. + [number, number] extends ( + [s["inferred"]["length"], spreadOperand["length"]] + ) ? ErrorMessage : "..." : ErrorMessage> @@ -298,19 +338,22 @@ type nextValidatedElement< s extends SequenceParseState, next extends PreparsedElement > = - next["kind"] extends "optionals" ? + next["kind"] extends SequencePhase.optionals ? next["spread"] extends true ? ErrorMessage - : s["phase"] extends "postfix" ? ErrorMessage - : next["validated"] - : next["kind"] extends "defaultables" ? + : s["phase"] extends SequencePhase.postfix ? + ErrorMessage + : next["validated"] + : next["kind"] extends SequencePhase.defaultables ? next["spread"] extends true ? ErrorMessage - : s["phase"] extends "optionals" ? + : s["phase"] extends SequencePhase.optionals ? ErrorMessage - : s["phase"] extends "postfix" ? - ErrorMessage + : s["phase"] extends SequencePhase.postfix ? + ErrorMessage : next["validated"] - : [s["phase"], next["spread"]] extends ["optionals" | " defaults", false] ? - ErrorMessage + : [s["phase"], next["spread"]] extends ( + [SequencePhase.optionals | SequencePhase.defaultables, false] + ) ? + ErrorMessage : next["validated"] export const writeNonArraySpreadMessage = ( @@ -332,10 +375,11 @@ export const requiredPostOptionalMessage = type requiredPostOptionalMessage = typeof requiredPostOptionalMessage -export const optionalPostVariadicMessage = +export const optionalOrDefaultableAfterVariadicMessage = "An optional element may not follow a variadic element" -type optionalPostVariadicMessage = typeof optionalPostVariadicMessage +type optionalOrDefaultableAfterVariadicMessage = + typeof optionalOrDefaultableAfterVariadicMessage export const spreadOptionalMessage = "A spread element cannot be optional" @@ -345,11 +389,6 @@ export const spreadDefaultableMessage = "A spread element cannot have a default" type spreadDefaultableMessage = typeof spreadDefaultableMessage -export const defaultablePostVariadicMessage = - "A defaultable element may not follow a variadic element" - -type defaultablePostVariadicMessage = typeof defaultablePostVariadicMessage - export const defaultablePostOptionalMessage = "A defaultable element may not follow an optional element without a default" diff --git a/ark/type/scope.ts b/ark/type/scope.ts index da881050bb..b246957c41 100644 --- a/ark/type/scope.ts +++ b/ark/type/scope.ts @@ -335,8 +335,6 @@ export declare namespace scope { export type infer = inferBootstrapped> } -export const module: ModuleParser = InternalScope.module - export interface Scope<$ = {}> { t: $ [arkKind]: "scope" diff --git a/ark/util/package.json b/ark/util/package.json index cabc5410f4..98b1c923ef 100644 --- a/ark/util/package.json +++ b/ark/util/package.json @@ -1,6 +1,6 @@ { "name": "@ark/util", - "version": "0.28.0", + "version": "0.29.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/util/registry.ts b/ark/util/registry.ts index b4f9c89a30..336fdccae0 100644 --- a/ark/util/registry.ts +++ b/ark/util/registry.ts @@ -7,7 +7,7 @@ import { FileConstructor, objectKindOf } from "./objectKinds.ts" // recent node versions (https://nodejs.org/api/esm.html#json-modules). // For now, we assert this matches the package.json version via a unit test. -export const arkUtilVersion = "0.28.0" +export const arkUtilVersion = "0.29.0" export const initialRegistryContents = { version: arkUtilVersion, diff --git a/eslint.config.js b/eslint.config.js index fa38e5275e..75cf021a90 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -178,18 +178,13 @@ export default tseslint.config( } }, { - files: [ - "**/ark/attest/**", - "**/ark/fs/**", - "**/ark/docs/**", - "**/ark/fuma/**" - ], + files: ["**/ark/attest/**", "**/ark/fs/**", "**/ark/docs/**"], rules: { "import/no-extraneous-dependencies": "warn" } }, { - files: ["**/ark/repo/**", "**/ark/docs/**", "**/ark/fuma/**"], + files: ["**/ark/repo/**", "**/ark/docs/**"], rules: { "@typescript-eslint/explicit-module-boundary-types": "off" } diff --git a/package.json b/package.json index a86267406f..a7a6ed3f55 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,19 @@ "type": "module", "private": true, "scripts": { - "prChecks": "pnpm lint && pnpm build && pnpm testRepo && pnpm bench && pnpm testTsVersions", + "prChecks": "pnpm lint && pnpm buildRepo && pnpm testRepoWithVersionsAndBenches", "attest": "ts ./ark/attest/cli/cli.ts", - "build": "pnpm -r build", + "build": "pnpm -r --filter !'@ark/docs' build", + "buildRepo": "pnpm -r build", + "buildDocs": "pnpm -r --filter '@ark/docs' build", "buildCjs": "ARKTYPE_CJS=1 pnpm -r build", "rmBuild": "pnpm -r exec rm -rf out", "ts": "node ./ark/repo/ts.js", "tsc": "node ./node_modules/typescript/lib/tsc.js", + "testRepoWithVersionsAndBenches": "pnpm testRepo && pnpm bench && pnpm testTsVersions", "test": "pnpm testTyped --skipTypes", "testTyped": "mocha --exclude 'ark/attest/**/*.test.*'", - "testRepo": "pnpm test && pnpm testV8 && cd ./ark/attest && pnpm test", + "testRepo": "pnpm test && pnpm testV8 && pnpm -r --filter '@ark/attest' test", "testV8": "node --allow-natives-syntax ./ark/repo/testV8.js", "testTsVersions": "pnpm testTyped --tsconfig null --tsVersions '*' --compilerOptions '{ \"strictNullChecks\": true }'", "bench": "pnpm benchOperand && pnpm benchOperator && pnpm benchObject && pnpm benchCyclic", @@ -51,24 +54,23 @@ "@ark/fs": "workspace:*", "@ark/repo": "workspace:*", "@ark/util": "workspace:*", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@standard-schema/spec": "1.0.0-beta.4", "@types/mocha": "10.0.10", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "arktype": "workspace:*", - "c8": "10.1.2", - "eslint": "9.16.0", + "c8": "10.1.3", + "eslint": "9.17.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-only-warn": "1.1.0", "eslint-plugin-prefer-arrow-functions": "3.4.1", - "knip": "5.39.2", + "knip": "5.41.1", "mocha": "11.0.1", "prettier": "3.4.2", "prettier-plugin-astro": "0.14.1", "tsx": "4.19.2", "typescript": "catalog:", - "typescript-eslint": "8.17.0", - "vitest": "2.1.8" + "typescript-eslint": "8.18.1" }, "mocha": { "//": "IF YOU UPDATE THE MOCHA CONFIG HERE, PLEASE ALSO UPDATE ark/repo/mocha.jsonc AND .vscode/settings.json", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2f5f14f147..d454745c42 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,6 @@ packages: - "ark/*" catalog: - typescript: 5.7.1-rc + typescript: 5.7.2 "@ark/attest-ts-min": npm:typescript@5.1.6 "@ark/attest-ts-next": npm:typescript@next diff --git a/tsconfig.json b/tsconfig.json index 72a611d736..c3677030bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,5 @@ "types": ["mocha", "node"] // "noErrorTruncation": true }, - "exclude": ["**/out", "**/node_modules", "./ark/docs", "./ark/fuma"] + "exclude": ["**/out", "**/node_modules", "./ark/docs"] }