├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── actions │ └── setup │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── pr.yml │ └── publish.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── ark ├── README.md ├── attest │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── assertions.test.ts │ │ ├── benchExpectedOutput.ts │ │ ├── benchTemplate.ts │ │ ├── completions.test.ts │ │ ├── demo.test.ts │ │ ├── externalSnapshots.test.ts │ │ ├── functions.test.ts │ │ ├── instantiations.test.ts │ │ ├── satisfies.test.ts │ │ ├── snap.test.ts │ │ ├── snapExpectedOutput.ts │ │ ├── snapPopulation.test.ts │ │ ├── snapTemplate.ts │ │ ├── unwrap.test.ts │ │ └── utils.ts │ ├── assert │ │ ├── assertions.ts │ │ ├── attest.ts │ │ └── chainableAssertions.ts │ ├── bench │ │ ├── await1k.ts │ │ ├── baseline.ts │ │ ├── bench.ts │ │ ├── call1k.ts │ │ ├── measure.ts │ │ └── type.ts │ ├── cache │ │ ├── getCachedAssertions.ts │ │ ├── snapshots.ts │ │ ├── ts.ts │ │ ├── utils.ts │ │ └── writeAssertionCache.ts │ ├── cli │ │ ├── cli.ts │ │ ├── precache.ts │ │ ├── shared.ts │ │ ├── stats.ts │ │ └── trace.ts │ ├── config.ts │ ├── fixtures.ts │ ├── index.ts │ ├── package.json │ ├── tsVersioning.ts │ └── utils.ts ├── docs │ ├── README.md │ ├── app │ │ ├── (home) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── discord │ │ │ └── page.tsx │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── global-error.tsx │ │ ├── global.css │ │ ├── layout.config.tsx │ │ ├── layout.tsx │ │ ├── metadata.ts │ │ ├── playground │ │ │ └── page.tsx │ │ └── providers.tsx │ ├── components │ │ ├── ApiTable.tsx │ │ ├── ArkCard.tsx │ │ ├── AutoplayDemo.tsx │ │ ├── Badge.tsx │ │ ├── Banner.tsx │ │ ├── Button.tsx │ │ ├── CodeBlock.tsx │ │ ├── FloatYourBoat.tsx │ │ ├── GhStarButton.tsx │ │ ├── Head.tsx │ │ ├── Hero.tsx │ │ ├── InstallationTabs.tsx │ │ ├── KeywordTable.tsx │ │ ├── LinkCard.tsx │ │ ├── LocalFriendlyUrl.tsx │ │ ├── PlatformCloud.tsx │ │ ├── ReleaseBanner.tsx │ │ ├── RuntimeBenchmarksGraph.tsx │ │ ├── SyntaxTabs.tsx │ │ ├── apiData.ts │ │ ├── dts │ │ │ ├── schema.ts │ │ │ ├── type.ts │ │ │ └── util.ts │ │ ├── icons │ │ │ ├── arktype-logo.tsx │ │ │ ├── boat.tsx │ │ │ ├── bun.tsx │ │ │ ├── chromium.tsx │ │ │ ├── deno.tsx │ │ │ ├── intellij.tsx │ │ │ ├── js.tsx │ │ │ ├── neovim.tsx │ │ │ ├── node.tsx │ │ │ ├── npm.tsx │ │ │ ├── ts.tsx │ │ │ └── vscode.tsx │ │ ├── playground │ │ │ ├── ParseResult.tsx │ │ │ ├── Playground.tsx │ │ │ ├── PlaygroundTabs.tsx │ │ │ ├── RestoreDefault.tsx │ │ │ ├── ShareLink.tsx │ │ │ ├── TraverseResult.tsx │ │ │ ├── completions.ts │ │ │ ├── errorLens.ts │ │ │ ├── execute.ts │ │ │ ├── format.ts │ │ │ ├── highlights.ts │ │ │ ├── hovers.ts │ │ │ ├── tsserver.ts │ │ │ └── utils.ts │ │ └── snippets │ │ │ ├── betterErrors.twoslash.ts │ │ │ ├── clarityAndConcision.twoslash.js │ │ │ ├── contentsById.ts │ │ │ ├── deepIntrospectability.twoslash.js │ │ │ ├── intrinsicOptimization.twoslash.js │ │ │ ├── nestedTypeInScopeError.twoslash.js │ │ │ └── unparalleledDx.twoslash.js │ ├── content │ │ └── docs │ │ │ ├── blog │ │ │ ├── 2.0.mdx │ │ │ ├── 2.1.mdx │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── comparisons.mdx │ │ │ ├── configuration │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── expressions │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── faq.mdx │ │ │ ├── generics │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── integrations │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── intro │ │ │ ├── adding-constraints.mdx │ │ │ ├── meta.json │ │ │ ├── morphs-and-more.mdx │ │ │ ├── setup.mdx │ │ │ └── your-first-type.mdx │ │ │ ├── keywords.mdx │ │ │ ├── match.mdx │ │ │ ├── meta.json │ │ │ ├── objects │ │ │ ├── arrays │ │ │ │ └── meta.json │ │ │ ├── index.mdx │ │ │ ├── meta.json │ │ │ └── properties │ │ │ │ └── meta.json │ │ │ ├── primitives │ │ │ ├── index.mdx │ │ │ ├── meta.json │ │ │ ├── number │ │ │ │ └── meta.json │ │ │ └── string │ │ │ │ └── meta.json │ │ │ ├── scopes │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ │ ├── traversal-api.mdx │ │ │ └── type-api │ │ │ ├── index.mdx │ │ │ └── meta.json │ ├── lib │ │ ├── ambient.d.ts │ │ ├── metadata.ts │ │ ├── shiki.ts │ │ ├── source.tsx │ │ └── writeSnippetsEntrypoint.ts │ ├── next-env.d.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ ├── CNAME │ │ ├── image │ │ │ ├── errorSquiggle.svg │ │ │ ├── favicon.svg │ │ │ ├── logo.png │ │ │ ├── og.png │ │ │ ├── ogDiscord.png │ │ │ ├── ogDocs.png │ │ │ └── ogPlayground.png │ │ └── onigasm.wasm │ ├── source.config.ts │ └── tsconfig.json ├── extension │ ├── LICENSE │ ├── README.md │ ├── arktype.scratch.ts │ ├── errorLens.png │ ├── highlighting.png │ ├── icon.png │ ├── injected.tmLanguage.json │ ├── package.json │ └── tsWithArkType.tmLanguage.json ├── fast-check │ ├── __tests__ │ │ └── arktypeFastCheck.test.ts │ ├── arbitraries │ │ ├── array.ts │ │ ├── date.ts │ │ ├── domain.ts │ │ ├── number.ts │ │ ├── object.ts │ │ ├── proto.ts │ │ └── string.ts │ ├── arktypeFastCheck.ts │ ├── fastCheckContext.ts │ └── package.json ├── fs │ ├── caller.ts │ ├── fs.ts │ ├── getCurrentLine.ts │ ├── index.ts │ ├── package.json │ └── shell.ts ├── json-schema │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── array.test.ts │ │ ├── composition.test.ts │ │ ├── number.test.ts │ │ ├── object.test.ts │ │ └── string.test.ts │ ├── array.ts │ ├── common.ts │ ├── composition.ts │ ├── errors.ts │ ├── index.ts │ ├── json.ts │ ├── number.ts │ ├── object.ts │ ├── package.json │ ├── scope.ts │ └── string.ts ├── repo │ ├── build.ts │ ├── config.ts │ ├── dtsGen.ts │ ├── jsdocGen.ts │ ├── mocha.globalSetup.ts │ ├── mocha.package.jsonc │ ├── nodeOptions.js │ ├── package.json │ ├── patchC8.cjs │ ├── publish.ts │ ├── scratch.ts │ ├── scratch │ │ ├── fn.js │ │ ├── fn.ts │ │ ├── matchComparison.bench.ts │ │ ├── realWorldComparison.ts │ │ ├── typeClass.ts │ │ └── unionComparison.ts │ ├── shared.ts │ ├── testPackage.ts │ ├── testV8.js │ ├── ts.js │ ├── tsconfig.cjs.json │ ├── tsconfig.dts.json │ ├── tsconfig.esm.json │ └── tsconfig.js.json ├── schema │ ├── README.md │ ├── __tests__ │ │ ├── bounds.test.ts │ │ ├── errors.test.ts │ │ ├── intersection.test.ts │ │ ├── jsonSchema.test.ts │ │ ├── morphs.test.ts │ │ ├── onFail.test.ts │ │ ├── parse.test.ts │ │ ├── props.test.ts │ │ ├── proto.test.ts │ │ ├── scope.test.ts │ │ ├── select.test.ts │ │ ├── union.test.ts │ │ └── unit.test.ts │ ├── config.ts │ ├── constraint.ts │ ├── generic.ts │ ├── index.ts │ ├── intrinsic.ts │ ├── kinds.ts │ ├── module.ts │ ├── node.ts │ ├── package.json │ ├── parse.ts │ ├── predicate.ts │ ├── refinements │ │ ├── after.ts │ │ ├── before.ts │ │ ├── divisor.ts │ │ ├── exactLength.ts │ │ ├── kinds.ts │ │ ├── max.ts │ │ ├── maxLength.ts │ │ ├── min.ts │ │ ├── minLength.ts │ │ ├── pattern.ts │ │ └── range.ts │ ├── roots │ │ ├── alias.ts │ │ ├── basis.ts │ │ ├── domain.ts │ │ ├── intersection.ts │ │ ├── morph.ts │ │ ├── proto.ts │ │ ├── root.ts │ │ ├── union.ts │ │ ├── unit.ts │ │ └── utils.ts │ ├── scope.ts │ ├── shared │ │ ├── compile.ts │ │ ├── declare.ts │ │ ├── disjoint.ts │ │ ├── errors.ts │ │ ├── implement.ts │ │ ├── intersections.ts │ │ ├── jsonSchema.ts │ │ ├── registry.ts │ │ ├── standardSchema.ts │ │ ├── toJsonSchema.ts │ │ ├── traversal.ts │ │ └── utils.ts │ └── structure │ │ ├── index.ts │ │ ├── optional.ts │ │ ├── prop.ts │ │ ├── required.ts │ │ ├── sequence.ts │ │ ├── shared.ts │ │ └── structure.ts ├── themes │ ├── LICENSE │ ├── README.md │ ├── arkdark.json │ ├── arkdarkItalic.json │ ├── arklight.json │ ├── arklightItalic.json │ ├── dark.png │ ├── icon.png │ ├── light.png │ └── package.json ├── type │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── arrays │ │ │ ├── array.test.ts │ │ │ ├── base.test.ts │ │ │ ├── defaults.test.ts │ │ │ ├── intersection.test.ts │ │ │ ├── nonVariadicTuple.test.ts │ │ │ └── variadicTuple.test.ts │ │ ├── badDefinitionType.test.ts │ │ ├── basis.test.ts │ │ ├── brand.test.ts │ │ ├── cast.test.ts │ │ ├── clone.test.ts │ │ ├── completions.test.ts │ │ ├── config.test.ts │ │ ├── cyclic.bench.ts │ │ ├── dateLiteral.test.ts │ │ ├── declared.test.ts │ │ ├── defaults.test.ts │ │ ├── define.test.ts │ │ ├── discrimination.test.ts │ │ ├── divisor.test.ts │ │ ├── enclosed.test.ts │ │ ├── expressions.test.ts │ │ ├── filter.test.ts │ │ ├── generateBenchData.ts │ │ ├── generated │ │ │ └── cyclic.ts │ │ ├── generic.test.ts │ │ ├── get.test.ts │ │ ├── group.test.ts │ │ ├── imports.test.ts │ │ ├── instanceOf.test.ts │ │ ├── integration │ │ │ ├── allConfig.ts │ │ │ ├── eoptConfig.ts │ │ │ ├── generateAllConfig.ts │ │ │ ├── onFailConfig.ts │ │ │ ├── simpleConfig.ts │ │ │ ├── testAllConfig.ts │ │ │ ├── testEoptConfig.ts │ │ │ ├── testOnFailConfig.ts │ │ │ ├── testSimpleConfig.ts │ │ │ └── util.ts │ │ ├── intersection.test.ts │ │ ├── keyof.test.ts │ │ ├── keywords │ │ │ ├── date.test.ts │ │ │ ├── exclude.test.ts │ │ │ ├── extract.test.ts │ │ │ ├── formData.test.ts │ │ │ ├── format.test.ts │ │ │ ├── ip.test.ts │ │ │ ├── json.test.ts │ │ │ ├── merge.test.ts │ │ │ ├── number.test.ts │ │ │ ├── numericStrings.test.ts │ │ │ ├── object.test.ts │ │ │ ├── omit.test.ts │ │ │ ├── parse.test.ts │ │ │ ├── partial.test.ts │ │ │ ├── pick.test.ts │ │ │ ├── record.test.ts │ │ │ ├── required.test.ts │ │ │ ├── string.test.ts │ │ │ ├── tsPrimitives.test.ts │ │ │ ├── url.test.ts │ │ │ └── uuid.test.ts │ │ ├── literal.test.ts │ │ ├── match.bench.ts │ │ ├── match.test.ts │ │ ├── narrow.test.ts │ │ ├── nary.bench.ts │ │ ├── nary.test.ts │ │ ├── object.bench.ts │ │ ├── objects │ │ │ ├── indexSignatures.test.ts │ │ │ ├── mapped.test.ts │ │ │ ├── namedKeys.test.ts │ │ │ ├── onUndeclaredKey.test.ts │ │ │ ├── props.test.ts │ │ │ └── spread.test.ts │ │ ├── operand.bench.ts │ │ ├── operator.bench.ts │ │ ├── optional.test.ts │ │ ├── pipe.test.ts │ │ ├── range.test.ts │ │ ├── realWorld.test.ts │ │ ├── regex.test.ts │ │ ├── runtime.bench.ts │ │ ├── scope.test.ts │ │ ├── select.test.ts │ │ ├── serialization.test.ts │ │ ├── standardSchema.test.ts │ │ ├── string.test.ts │ │ ├── submodule.test.ts │ │ ├── this.test.ts │ │ ├── thunk.test.ts │ │ ├── traverse.test.ts │ │ ├── type.test.ts │ │ ├── typeReference.test.ts │ │ ├── unenclosed.test.ts │ │ └── union.test.ts │ ├── attributes.ts │ ├── config.ts │ ├── generic.ts │ ├── index.ts │ ├── keywords │ │ ├── Array.ts │ │ ├── FormData.ts │ │ ├── TypedArray.ts │ │ ├── builtins.ts │ │ ├── constructors.ts │ │ ├── keywords.ts │ │ ├── number.ts │ │ ├── string.ts │ │ └── ts.ts │ ├── match.ts │ ├── methods │ │ ├── array.ts │ │ ├── base.ts │ │ ├── date.ts │ │ ├── instantiate.ts │ │ ├── number.ts │ │ ├── object.ts │ │ └── string.ts │ ├── module.ts │ ├── nary.ts │ ├── package.json │ ├── parser │ │ ├── ast │ │ │ ├── bounds.ts │ │ │ ├── default.ts │ │ │ ├── divisor.ts │ │ │ ├── generic.ts │ │ │ ├── infer.ts │ │ │ ├── keyof.ts │ │ │ ├── utils.ts │ │ │ └── validate.ts │ │ ├── definition.ts │ │ ├── objectLiteral.ts │ │ ├── property.ts │ │ ├── reduce │ │ │ ├── dynamic.ts │ │ │ ├── shared.ts │ │ │ └── static.ts │ │ ├── shift │ │ │ ├── operand │ │ │ │ ├── date.ts │ │ │ │ ├── enclosed.ts │ │ │ │ ├── genericArgs.ts │ │ │ │ ├── operand.ts │ │ │ │ └── unenclosed.ts │ │ │ ├── operator │ │ │ │ ├── bounds.ts │ │ │ │ ├── brand.ts │ │ │ │ ├── default.ts │ │ │ │ ├── divisor.ts │ │ │ │ └── operator.ts │ │ │ └── scanner.ts │ │ ├── string.ts │ │ ├── tupleExpressions.ts │ │ └── tupleLiteral.ts │ ├── scope.ts │ └── type.ts └── util │ ├── __tests__ │ ├── arrays.test.ts │ ├── callable.test.ts │ ├── clone.test.ts │ ├── collapsibleDate.test.ts │ ├── flatMorph.test.ts │ ├── hkt.test.ts │ ├── intersections.test.ts │ ├── numbers.test.ts │ ├── overloads.test.ts │ ├── path.test.ts │ ├── printable.test.ts │ ├── records.test.ts │ ├── registry.test.ts │ ├── string.test.ts │ ├── traits.scratch.ts │ └── traits.test.ts │ ├── arrays.ts │ ├── clone.ts │ ├── describe.ts │ ├── domain.ts │ ├── errors.ts │ ├── flatMorph.ts │ ├── functions.ts │ ├── generics.ts │ ├── get.ts │ ├── hkt.ts │ ├── index.ts │ ├── intersections.ts │ ├── isomorphic.ts │ ├── keys.ts │ ├── lazily.ts │ ├── numbers.ts │ ├── objectKinds.ts │ ├── package.json │ ├── path.ts │ ├── primitive.ts │ ├── records.ts │ ├── registry.ts │ ├── scanner.ts │ ├── serialize.ts │ ├── strings.ts │ ├── traits.ts │ ├── tsconfig.base.json │ └── unionToTuple.ts ├── eslint.config.js ├── package.json ├── pnpm-workspace.yaml └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [arktypeio] 2 | drips: 3 | ethereum: 4 | ownedBy: "0xD5c5Fe5DF95adf8DA1Ae640fCAE8f72795657fa5" 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Report a bug 4 | title: "" 5 | labels: "bug" 6 | assignees: "ssalbdivad" 7 | --- 8 | 9 | # Report a bug 10 | 11 | ### 🔎 Search Terms 12 | 13 | 17 | 18 | ### 🧩 Context 19 | 20 | - ArkType version: 21 | - TypeScript version (5.1+): 22 | - Other context you think may be relevant (JS flavor, OS, etc.): 23 | 24 | 28 | 29 | ### 🧑‍💻 Repro 30 | 31 | 38 | 39 | Playground Link: https://arktype.io/playground 40 | 41 | ```ts 42 | // Paste reproduction code here 43 | ``` 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | # Request a feature 10 | 11 | 14 | 15 | ### 🤷 Motivation 16 | 17 | 21 | 22 | ### 💡 Solution 23 | 24 | 29 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 2.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | If you believe you've identified a security vulnerability within ArkType, please send an email to david@arktype.io describing what it is and how to reproduce it. Expect a response within 24 hours. 12 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup repo 2 | description: Install dependencies and perform setup for https://github.com/arktypeio/arktype 3 | 4 | inputs: 5 | node: 6 | default: lts/* 7 | description: Node version to use 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - name: Setup Node (${{ inputs.node }}) 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: ${{ inputs.node }} 16 | registry-url: "https://registry.npmjs.org" 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | 21 | - name: Install dependencies 22 | shell: bash 23 | run: pnpm install 24 | 25 | - name: Build 26 | shell: bash 27 | run: pnpm build 28 | 29 | - name: Post-build install 30 | shell: bash 31 | run: pnpm install 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | defaults: 8 | run: 9 | shell: bash 10 | 11 | jobs: 12 | core: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 20 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Setup repo 22 | uses: ./.github/actions/setup 23 | 24 | - name: prChecks 25 | run: pnpm prChecks 26 | 27 | compatibility: 28 | needs: core 29 | timeout-minutes: 20 30 | strategy: 31 | matrix: 32 | node: [lts/*] 33 | os: [windows-latest, macos-latest] 34 | include: 35 | - os: ubuntu-latest 36 | node: lts/-1 37 | - os: ubuntu-latest 38 | node: latest 39 | fail-fast: false 40 | 41 | runs-on: ${{ matrix.os }} 42 | steps: 43 | - name: Checkout repo 44 | uses: actions/checkout@v4 45 | with: 46 | fetch-depth: 0 47 | 48 | - name: Setup repo 49 | uses: ./.github/actions/setup 50 | with: 51 | node: ${{ matrix.node }} 52 | 53 | # To test node and OS versions, we don't care about rechecking types 54 | # so just check runtime behavior 55 | - name: Test 56 | run: pnpm testRepo 57 | 58 | prChecks: 59 | needs: compatibility 60 | timeout-minutes: 1 61 | runs-on: ubuntu-latest 62 | steps: 63 | - run: echo All checks succeeded! ⛵ 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | out 3 | *.vsix 4 | node_modules 5 | temp 6 | tmp 7 | *.temp.* 8 | *.log 9 | *.tsbuildinfo 10 | .DS_Store 11 | .next 12 | .source 13 | .cache-loader 14 | .attest 15 | tsconfig.build.json 16 | coverage 17 | # we avoid committing the root pnpm-lock in order to keep the root of the repo as clean as possible. 18 | # we can get away with this to since we're only installing devDependencies and they're all pinned. 19 | /pnpm-lock.yaml 20 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | // Run/debug tests inline via VSCode's Test Explorer 6 | "hbenl.vscode-mocha-test-adapter", 7 | // Syntax highlighting for strings in ArkType definitions 8 | "arktypeio.arkdark", 9 | // Playground-like version dropdown for TypeScript versions 10 | "typeholes.ts-versions-switcher" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Debug Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}/ark/extension"] 11 | }, 12 | { 13 | "name": "Debug Themes", 14 | "type": "extensionHost", 15 | "request": "launch", 16 | "runtimeExecutable": "${execPath}", 17 | "args": ["--extensionDevelopmentPath=${workspaceRoot}/ark/themes"] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 ArkType 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./ark/type/README.md -------------------------------------------------------------------------------- /ark/README.md: -------------------------------------------------------------------------------- 1 | ./type/README.md -------------------------------------------------------------------------------- /ark/attest/__tests__/benchExpectedOutput.ts: -------------------------------------------------------------------------------- 1 | import { bench } from "@ark/attest" 2 | import type { makeComplexType as externalmakeComplexType } from "./utils.ts" 3 | 4 | const fakeCallOptions = { 5 | until: { count: 2 }, 6 | fakeCallMs: "count", 7 | benchFormat: { noExternal: true } 8 | } 9 | 10 | bench( 11 | "bench call single stat median", 12 | () => "boofoozoo".includes("foo"), 13 | fakeCallOptions 14 | ).median([2, "ms"]) 15 | 16 | bench( 17 | "bench call single stat", 18 | () => "boofoozoo".includes("foo"), 19 | fakeCallOptions 20 | ).mean([2, "ms"]) 21 | 22 | bench( 23 | "bench call mark", 24 | () => /.*foo.*/.test("boofoozoo"), 25 | fakeCallOptions 26 | ).mark({ mean: [2, "ms"], median: [2, "ms"] }) 27 | 28 | type makeComplexType = 29 | S extends `${infer head}${infer tail}` ? head | tail | makeComplexType 30 | : S 31 | 32 | bench("bench type", () => ({}) as makeComplexType<"defenestration">).types([ 33 | 177, 34 | "instantiations" 35 | ]) 36 | 37 | bench( 38 | "bench type from external module", 39 | () => ({}) as externalmakeComplexType<"defenestration"> 40 | ).types([193, "instantiations"]) 41 | 42 | bench( 43 | "bench call and type", 44 | () => ({}) as makeComplexType<"antidisestablishmentarianism">, 45 | fakeCallOptions 46 | ) 47 | .mean([2, "ms"]) 48 | .types([345, "instantiations"]) 49 | 50 | bench("empty", () => {}).types([0, "instantiations"]) 51 | -------------------------------------------------------------------------------- /ark/attest/__tests__/benchTemplate.ts: -------------------------------------------------------------------------------- 1 | import { bench } from "@ark/attest" 2 | import type { makeComplexType as externalmakeComplexType } from "./utils.ts" 3 | 4 | const fakeCallOptions = { 5 | until: { count: 2 }, 6 | fakeCallMs: "count", 7 | benchFormat: { noExternal: true } 8 | } 9 | 10 | bench( 11 | "bench call single stat median", 12 | () => "boofoozoo".includes("foo"), 13 | fakeCallOptions 14 | ).median() 15 | 16 | bench( 17 | "bench call single stat", 18 | () => "boofoozoo".includes("foo"), 19 | fakeCallOptions 20 | ).mean() 21 | 22 | bench( 23 | "bench call mark", 24 | () => /.*foo.*/.test("boofoozoo"), 25 | fakeCallOptions 26 | ).mark() 27 | 28 | type makeComplexType = 29 | S extends `${infer head}${infer tail}` ? head | tail | makeComplexType 30 | : S 31 | 32 | bench("bench type", () => ({}) as makeComplexType<"defenestration">).types() 33 | 34 | bench( 35 | "bench type from external module", 36 | () => ({}) as externalmakeComplexType<"defenestration"> 37 | ).types() 38 | 39 | bench( 40 | "bench call and type", 41 | () => ({}) as makeComplexType<"antidisestablishmentarianism">, 42 | fakeCallOptions 43 | ) 44 | .mean() 45 | .types() 46 | 47 | bench("empty", () => {}).types() 48 | -------------------------------------------------------------------------------- /ark/attest/__tests__/instantiations.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import { it } from "mocha" 4 | 5 | contextualize(() => { 6 | it("inline", () => { 7 | attest.instantiations([23731, "instantiations"]) 8 | return type({ 9 | kind: "'admin'", 10 | "powers?": "string[]" 11 | }) 12 | .or({ 13 | kind: "'superadmin'", 14 | "superpowers?": "string[]" 15 | }) 16 | .or({ 17 | kind: "'pleb'" 18 | }) 19 | }) 20 | it("fails on instantiations above threshold", () => { 21 | attest(() => { 22 | attest.instantiations([1, "instantiations"]) 23 | return type({ 24 | foo: "0|1|2|3|4|5|6" 25 | }) 26 | }).throws("exceeded baseline by") 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /ark/attest/__tests__/satisfies.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { nonOverlappingSatisfiesMessage } from "@ark/attest/internal/assert/chainableAssertions.js" 3 | 4 | contextualize(() => { 5 | it("can assert types", () => { 6 | attest({ foo: "bar" }).satisfies({ foo: "string" }) 7 | 8 | attest(() => { 9 | // @ts-expect-error 10 | attest({ foo: "bar" }).satisfies({ foo: "number" }) 11 | }) 12 | .throws("foo must be a number (was a string)") 13 | .type.errors(nonOverlappingSatisfiesMessage) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /ark/attest/__tests__/snapPopulation.test.ts: -------------------------------------------------------------------------------- 1 | import { contextualize } from "@ark/attest" 2 | import { fromHere, readFile } from "@ark/fs" 3 | import { equal } from "node:assert/strict" 4 | import { runThenGetContents } from "./utils.ts" 5 | 6 | contextualize(() => { 7 | it("bench populates file", () => { 8 | const actual = runThenGetContents(fromHere("benchTemplate.ts")) 9 | const expectedOutput = readFile( 10 | fromHere("benchExpectedOutput.ts") 11 | ).replaceAll("\r\n", "\n") 12 | equal(actual, expectedOutput) 13 | }).timeout(60000) 14 | 15 | it("snap populates file", () => { 16 | const actual = runThenGetContents(fromHere("snapTemplate.ts")) 17 | const expectedOutput = readFile( 18 | fromHere("snapExpectedOutput.ts") 19 | ).replaceAll("\r\n", "\n") 20 | equal(actual, expectedOutput) 21 | }).timeout(60000) 22 | }) 23 | -------------------------------------------------------------------------------- /ark/attest/__tests__/snapTemplate.ts: -------------------------------------------------------------------------------- 1 | import { attest, cleanup, setup } from "@ark/attest" 2 | import type { makeComplexType } from "./utils.ts" 3 | 4 | setup({ typeToStringFormat: { useTabs: true } }) 5 | 6 | attest({ re: "do" }).equals({ re: "do" }).type.toString.snap() 7 | 8 | attest({ 9 | ark: "type", 10 | type: "script", 11 | vali: "dator", 12 | opti: "mized", 13 | from: "editor", 14 | to: "runtime" 15 | }) 16 | .snap() 17 | .type.toString.snap() 18 | 19 | attest(5).snap() 20 | 21 | attest({ re: "do" }).snap() 22 | 23 | // @ts-expect-error (using internal updateSnapshots hook) 24 | attest({ re: "dew" }, { cfg: { updateSnapshots: true } }).snap({ re: "do" }) 25 | 26 | // @ts-expect-error (using internal updateSnapshots hook) 27 | attest(5, { cfg: { updateSnapshots: true } }).snap(6) 28 | 29 | attest(5n).snap() 30 | 31 | attest(-5n).snap() 32 | 33 | attest({ a: 4n }).snap() 34 | 35 | attest(undefined).snap() 36 | 37 | attest("undefined").snap() 38 | 39 | attest({ a: undefined }).snap() 40 | 41 | attest("multiline\nmultiline").snap() 42 | 43 | attest("with `quotes`").snap() 44 | 45 | attest({ 46 | a2z: `a"'${"" as string}'"z`, 47 | z2a: `z"'${"" as string}'"a`, 48 | ark: "type", 49 | type: "ark" 50 | } as const).type.toString.snap() 51 | 52 | attest({ [Symbol("mySymbol")]: 1 }).snap() 53 | 54 | const it = (name: string, fn: () => void) => fn() 55 | 56 | it("can snap instantiations", () => { 57 | attest.instantiations() 58 | return {} as makeComplexType<"asbsdfsaodisfhsda"> 59 | }) 60 | 61 | cleanup() 62 | -------------------------------------------------------------------------------- /ark/attest/__tests__/unwrap.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import type { Completions } from "@ark/attest/internal/cache/writeAssertionCache.js" 3 | import type { autocomplete } from "@ark/util" 4 | 5 | contextualize(() => { 6 | it("unwraps unversioned", () => { 7 | const unwrapped = attest({ foo: "bar" }).unwrap() 8 | attest<{ foo: string }>(unwrapped).equals({ 9 | foo: "bar" 10 | }) 11 | }) 12 | 13 | it("unwraps serialized", () => { 14 | const unwrapped = attest({ foo: Symbol("unwrappedSymbol") }).unwrap({ 15 | serialize: true 16 | }) 17 | attest(unwrapped).snap({ foo: "Symbol(unwrappedSymbol)" }) 18 | }) 19 | 20 | it("unwraps completions", () => { 21 | const unwrapped = attest({ foo: "b" } satisfies { 22 | foo: autocomplete<"bar"> 23 | }).completions.unwrap() 24 | 25 | attest(unwrapped).snap({ b: ["bar"] }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /ark/attest/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import { dirName, fromHere, readFile, shell } from "@ark/fs" 2 | import { copyFileSync, rmSync } from "node:fs" 3 | 4 | export const runThenGetContents = (templatePath: string): string => { 5 | rmSync(fromHere(".attest"), { force: true, recursive: true }) 6 | 7 | const tempPath = templatePath + ".temp.ts" 8 | copyFileSync(templatePath, tempPath) 9 | try { 10 | shell(`node --import=tsx ${tempPath}`, { cwd: dirName() }) 11 | } catch (e) { 12 | console.error(e) 13 | } 14 | const resultContents = readFile(tempPath) 15 | rmSync(tempPath) 16 | return resultContents 17 | } 18 | 19 | // type is used in benchTemplate.ts to test compatibility with external modules 20 | export type makeComplexType = 21 | S extends `${infer head}${infer tail}` ? head | tail | makeComplexType 22 | : S 23 | -------------------------------------------------------------------------------- /ark/attest/cli/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { fileName } from "@ark/fs" 3 | import { basename } from "node:path" 4 | import { precache } from "./precache.ts" 5 | import { stats } from "./stats.ts" 6 | import { trace } from "./trace.ts" 7 | 8 | const subcommands = { 9 | precache, 10 | trace, 11 | stats 12 | } 13 | 14 | type Subcommand = keyof typeof subcommands 15 | 16 | const baseFileName = basename(fileName()) 17 | 18 | const thisFileIndex = process.argv.findIndex( 19 | // if running from build output in npm, will be a file called `attest` 20 | // if running from build output in pnpm, will be cli.js in build output 21 | s => s.endsWith(baseFileName) || s.endsWith("attest") 22 | ) 23 | 24 | if (thisFileIndex === -1) 25 | throw new Error(`Expected to find an argument ending with "${baseFileName}"`) 26 | 27 | const subcommand = process.argv[thisFileIndex + 1] 28 | 29 | if (!(subcommand in subcommands)) { 30 | console.error( 31 | `Expected a command like 'attest ', where is one of:\n${Object.keys( 32 | subcommands 33 | )}` 34 | ) 35 | process.exit(1) 36 | } 37 | 38 | const args = process.argv.slice(thisFileIndex + 2) 39 | 40 | subcommands[subcommand as Subcommand](args) 41 | -------------------------------------------------------------------------------- /ark/attest/cli/precache.ts: -------------------------------------------------------------------------------- 1 | import { ensureDir } from "@ark/fs" 2 | import { join } from "node:path" 3 | import { writeAssertionData } from "../fixtures.ts" 4 | 5 | export const precache = (args: string[]): void => { 6 | const cacheFileToWrite = 7 | args[0] ?? join(ensureDir(".attest"), "typescript.json") 8 | 9 | writeAssertionData(cacheFileToWrite) 10 | } 11 | -------------------------------------------------------------------------------- /ark/attest/cli/shared.ts: -------------------------------------------------------------------------------- 1 | export const baseDiagnosticTscCmd = 2 | "npm exec -- tsc --noEmit --extendedDiagnostics --incremental false --tsBuildInfoFile null" 3 | -------------------------------------------------------------------------------- /ark/attest/index.ts: -------------------------------------------------------------------------------- 1 | export { cleanup, setup, teardown, writeAssertionData } from "./fixtures.ts" 2 | // ensure fixtures are exported before config so additional settings can load 3 | export { caller, type CallerOfOptions } from "@ark/fs" 4 | export { attest } from "./assert/attest.ts" 5 | export { bench } from "./bench/bench.ts" 6 | export { 7 | getBenchAssertionsAtPosition, 8 | getTypeAssertionsAtPosition 9 | } from "./cache/getCachedAssertions.ts" 10 | export type { 11 | ArgAssertionData, 12 | LinePositionRange, 13 | TypeAssertionData, 14 | TypeRelationship 15 | } from "./cache/writeAssertionCache.ts" 16 | export { getDefaultAttestConfig, type AttestConfig } from "./config.ts" 17 | export { 18 | findAttestTypeScriptVersions, 19 | getPrimaryTsVersionUnderTest 20 | } from "./tsVersioning.ts" 21 | export { contextualize } from "./utils.ts" 22 | -------------------------------------------------------------------------------- /ark/attest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/attest", 3 | "version": "0.46.0", 4 | "license": "MIT", 5 | "author": { 6 | "name": "David Blass", 7 | "email": "david@arktype.io", 8 | "url": "https://arktype.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/arktypeio/arktype.git", 13 | "directory": "ark/attest" 14 | }, 15 | "type": "module", 16 | "main": "./out/index.js", 17 | "types": "./out/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "ark-ts": "./index.ts", 21 | "default": "./out/index.js" 22 | }, 23 | "./internal/*": { 24 | "ark-ts": "./*", 25 | "default": "./out/*" 26 | } 27 | }, 28 | "files": [ 29 | "out" 30 | ], 31 | "bin": { 32 | "attest": "out/cli/cli.js" 33 | }, 34 | "scripts": { 35 | "build": "ts ../repo/build.ts", 36 | "test": "ts ../repo/testPackage.ts" 37 | }, 38 | "dependencies": { 39 | "@ark/fs": "workspace:*", 40 | "@ark/util": "workspace:*", 41 | "@prettier/sync": "0.5.5", 42 | "@typescript/analyze-trace": "0.10.1", 43 | "@typescript/vfs": "1.6.1", 44 | "arktype": "workspace:*", 45 | "prettier": "3.5.3" 46 | }, 47 | "devDependencies": { 48 | "typescript": "catalog:" 49 | }, 50 | "peerDependencies": { 51 | "typescript": "*" 52 | }, 53 | "publishConfig": { 54 | "access": "public" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ark/docs/README.md: -------------------------------------------------------------------------------- 1 | # my-app 2 | 3 | This is a Next.js application generated with 4 | [Create Fumadocs](https://github.com/fuma-nama/fumadocs). 5 | 6 | Run development server: 7 | 8 | ```bash 9 | npm run dev 10 | # or 11 | pnpm dev 12 | # or 13 | yarn dev 14 | ``` 15 | 16 | Open http://localhost:3000 with your browser to see the result. 17 | 18 | ## Learn More 19 | 20 | To learn more about Next.js and Fumadocs, take a look at the following 21 | resources: 22 | 23 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js 24 | features and API. 25 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 26 | - [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs 27 | -------------------------------------------------------------------------------- /ark/docs/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayout } from "fumadocs-ui/layouts/home" 2 | import type { ReactNode } from "react" 3 | import { FloatYourBoat } from "../../components/FloatYourBoat.tsx" 4 | import { baseOptions } from "../layout.config.tsx" 5 | 6 | export type LayoutProps = { 7 | children: ReactNode 8 | } 9 | 10 | export default ({ children }: LayoutProps): React.ReactElement => ( 11 | 16 | }} 17 | > 18 | {children} 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /ark/docs/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createSearchAPI, 3 | type AdvancedIndex 4 | } from "fumadocs-core/search/server" 5 | import { source } from "../../../lib/source.tsx" 6 | 7 | // it should be cached forever 8 | export const revalidate = false 9 | export const { staticGET: GET } = createSearchAPI("advanced", { 10 | indexes: await Promise.all( 11 | source.getPages().map( 12 | async page => 13 | ({ 14 | id: page.url, 15 | title: page.data.title, 16 | description: page.data.description, 17 | url: page.url, 18 | structuredData: (await page.data.load()).structuredData 19 | }) as AdvancedIndex 20 | ) 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /ark/docs/app/discord/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | import { defineMetadata } from "../metadata.ts" 3 | 4 | export const metadata = defineMetadata({ 5 | title: "ArkType Discord", 6 | ogImage: "ogDiscord.png" 7 | }) 8 | 9 | export default function DiscordPage() { 10 | redirect("https://discord.com/invite/xEzdc3fJQC") 11 | } 12 | -------------------------------------------------------------------------------- /ark/docs/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from "fumadocs-ui/layouts/docs" 2 | import type { ReactNode } from "react" 3 | import { source } from "../../lib/source.tsx" 4 | import { baseOptions } from "../layout.config.tsx" 5 | import { defineMetadata } from "../metadata.ts" 6 | 7 | export const metadata = defineMetadata({ 8 | title: "ArkType Docs", 9 | ogImage: "ogDocs.png" 10 | }) 11 | 12 | export default ({ children }: { children: ReactNode }) => ( 13 | 14 | {children} 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /ark/docs/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SiBluesky, 3 | SiDiscord, 4 | SiTwitch, 5 | SiX 6 | } from "@icons-pack/react-simple-icons" 7 | import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared" 8 | import { ArkTypeLogo } from "../components/icons/arktype-logo.tsx" 9 | 10 | export const baseOptions: BaseLayoutProps = { 11 | nav: { 12 | title: 13 | }, 14 | themeSwitch: { 15 | enabled: false 16 | }, 17 | githubUrl: "https://github.com/arktypeio/arktype", 18 | links: [ 19 | { 20 | text: "Twitch", 21 | type: "icon", 22 | icon: , 23 | url: "https://twitch.tv/arktypeio" 24 | }, 25 | { 26 | text: "Bluesky", 27 | type: "icon", 28 | icon: , 29 | url: "https://bsky.app/profile/arktype.io" 30 | }, 31 | { 32 | text: "X", 33 | type: "icon", 34 | icon: , 35 | url: "https://x.com/arktypeio" 36 | }, 37 | { 38 | text: "Discord", 39 | type: "icon", 40 | icon: , 41 | url: "https://arktype.io/discord" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /ark/docs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "app/global.css" 2 | import "fumadocs-twoslash/twoslash.css" 3 | import { RootProvider } from "fumadocs-ui/provider" 4 | import { Atkinson_Hyperlegible, Raleway } from "next/font/google" 5 | import type { ReactNode } from "react" 6 | import { ReleaseBanner } from "../components/ReleaseBanner.tsx" 7 | import { defineMetadata } from "./metadata.ts" 8 | import { CSPostHogProvider } from "./providers.tsx" 9 | 10 | const raleway = Raleway({ 11 | subsets: ["latin"], 12 | display: "swap", 13 | variable: "--font-raleway" 14 | }) 15 | 16 | const atkinson = Atkinson_Hyperlegible({ 17 | weight: ["400", "700"], 18 | subsets: ["latin"], 19 | display: "swap", 20 | variable: "--font-atkinson" 21 | }) 22 | 23 | export const metadata = defineMetadata({}) 24 | 25 | export default function RootLayout({ children }: { children: ReactNode }) { 26 | return ( 27 | 32 | 33 | 44 | 45 | 46 | {children} 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /ark/docs/app/metadata.ts: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | 3 | export type MetadataOptions = { 4 | title?: string 5 | ogImage?: string 6 | } 7 | 8 | export const defineMetadata = ({ 9 | title = "ArkType", 10 | ogImage = "og.png" 11 | }: MetadataOptions): Metadata => ({ 12 | title: `${title}: TypeScript's 1:1 validator, optimized from editor to runtime`, 13 | description: "TypeScript's 1:1 validator, optimized from editor to runtime", 14 | keywords: [ 15 | "ArkType", 16 | "TypeScript", 17 | "JavaScript", 18 | "runtime validation", 19 | "schema", 20 | "type-safe", 21 | "validator", 22 | "syntax" 23 | ], 24 | openGraph: { 25 | title, 26 | description: "TypeScript's 1:1 validator, optimized from editor to runtime", 27 | url: "https://arktype.io/", 28 | siteName: "ArkType", 29 | images: [ 30 | { 31 | url: `https://arktype.io/image/${ogImage}`, 32 | width: 1200, 33 | height: 600 34 | } 35 | ], 36 | type: "website" 37 | }, 38 | twitter: { 39 | card: "summary_large_image" 40 | }, 41 | icons: { 42 | icon: "/image/favicon.svg" 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /ark/docs/app/playground/page.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayout } from "fumadocs-ui/layouts/home" 2 | import type { ReactNode } from "react" 3 | import { FloatYourBoat } from "../../components/FloatYourBoat.tsx" 4 | import { Playground } from "../../components/playground/Playground.tsx" 5 | import { baseOptions } from "../layout.config.tsx" 6 | import { defineMetadata } from "../metadata.ts" 7 | 8 | export const metadata = defineMetadata({ 9 | title: "ArkType Playground", 10 | ogImage: "ogPlayground.png" 11 | }) 12 | 13 | export type LayoutProps = { 14 | children: ReactNode 15 | } 16 | 17 | export default function PlaygroundPage() { 18 | return ( 19 | 31 | }} 32 | > 33 |
34 |
35 | 36 |
37 |
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /ark/docs/app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import posthog from "posthog-js" 4 | import { PostHogProvider } from "posthog-js/react" 5 | 6 | if ( 7 | globalThis.window !== undefined && 8 | process.env.NEXT_PUBLIC_POSTHOG_KEY && 9 | process.env.NEXT_PUBLIC_POSTHOG_HOST 10 | ) { 11 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { 12 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 13 | person_profiles: "always" 14 | }) 15 | } 16 | 17 | export const CSPostHogProvider = ({ 18 | children 19 | }: { 20 | children: React.ReactNode 21 | }) => {children} 22 | -------------------------------------------------------------------------------- /ark/docs/components/ArkCard.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "fumadocs-ui/components/api" 2 | import { Card, type CardProps, Cards } from "fumadocs-ui/components/card" 3 | 4 | export const ArkCards: React.FC<{ children: React.ReactNode }> = ({ 5 | children 6 | }) => {children} 7 | 8 | export const ArkCard: React.FC = ({ 9 | children, 10 | className, 11 | ...props 12 | }) => ( 13 | h3]:text-2xl [&>h3]:font-semibold [&_.prose-no-margin]:text-lg", 17 | "[&>.prose-no-margin]:flex [&>.prose-no-margin]:flex-col [&>.prose-no-margin]:flex-grow", 18 | "flex flex-col", 19 | "rounded-3xl", 20 | className 21 | )} 22 | style={{ 23 | borderWidth: 2 24 | }} 25 | > 26 | {children} 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /ark/docs/components/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { cva, type VariantProps } from "class-variance-authority" 2 | import { cn } from "fumadocs-ui/components/api" 3 | import * as React from "react" 4 | 5 | export const badgeVariants = cva( 6 | "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", 7 | { 8 | variants: { 9 | variant: { 10 | default: 11 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 12 | secondary: 13 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 14 | destructive: 15 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 16 | outline: "text-foreground" 17 | } 18 | }, 19 | defaultVariants: { 20 | variant: "default" 21 | } 22 | } 23 | ) 24 | 25 | export interface BadgeProps 26 | extends React.HTMLAttributes, 27 | VariantProps {} 28 | 29 | export const Badge = ({ className, variant, ...props }: BadgeProps) => ( 30 |
31 | ) 32 | -------------------------------------------------------------------------------- /ark/docs/components/GhStarButton.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { cx } from "class-variance-authority" 4 | import { Star } from "lucide-react" 5 | import React, { useEffect, useState } from "react" 6 | import { Button } from "./Button.tsx" 7 | 8 | export declare namespace GhStarButton { 9 | export type Props = { 10 | className?: string 11 | } 12 | } 13 | 14 | // based on the trpc component: 15 | // https://github.com/trpc/trpc/blob/7d10d7b028f1d85f6523e995ee7deb17dc886874/www/src/components/GithubStarsButton.tsx#L15 16 | export const GhStarButton = ({ className }: GhStarButton.Props) => { 17 | const [starCount, setStarCount] = useState("5.9k") 18 | 19 | const formatStarCount = (count: number): string => { 20 | if (count < 1000) return count.toString() 21 | 22 | const roundedCount = Math.floor(count / 100) / 10 23 | const roundUpCount = Math.ceil(count / 100) / 10 24 | const finalCount = count % 100 >= 50 ? roundUpCount : roundedCount 25 | 26 | return `${finalCount}k` 27 | } 28 | 29 | const fetchStars = async () => { 30 | const res = await fetch("https://api.github.com/repos/arktypeio/arktype") 31 | const data = (await res.json()) as { stargazers_count: number } 32 | if (typeof data?.stargazers_count === "number") 33 | setStarCount(formatStarCount(data.stargazers_count)) 34 | } 35 | 36 | useEffect(() => { 37 | fetchStars().catch(console.error) 38 | }, []) 39 | 40 | return ( 41 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /ark/docs/components/Head.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/components/Head.tsx -------------------------------------------------------------------------------- /ark/docs/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowRightIcon, PlayIcon } from "lucide-react" 2 | import { MainAutoplayDemo } from "./AutoplayDemo.tsx" 3 | import { Button } from "./Button.tsx" 4 | import { GhStarButton } from "./GhStarButton.tsx" 5 | import { PlatformCloud } from "./PlatformCloud.tsx" 6 | 7 | export const Hero = () => ( 8 |
9 |
10 |
11 |
12 | 18 | 19 |
20 |
21 | 22 |
23 |

ArkType

24 |

25 | TypeScript's 1:1 validator, optimized from editor to runtime 26 |

27 |
28 | 29 | 38 | 42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 | ) 51 | -------------------------------------------------------------------------------- /ark/docs/components/InstallationTabs.tsx: -------------------------------------------------------------------------------- 1 | import { Tab, Tabs } from "fumadocs-ui/components/tabs" 2 | import { CodeBlock } from "./CodeBlock.tsx" 3 | 4 | const installers = ["pnpm", "npm", "yarn", "bun"] as const satisfies string[] 5 | 6 | export type Installer = (typeof installers)[number] 7 | 8 | type InstallationTabProps = { 9 | name: Installer 10 | } 11 | 12 | const InstallerTab = ({ name }: InstallationTabProps) => ( 13 | 14 | {`${name} ${name === "yarn" ? "add" : "install"} arktype`} 15 | 16 | ) 17 | 18 | export const InstallationTabs = () => ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /ark/docs/components/LinkCard.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "fumadocs-ui/components/api" 2 | import { Card } from "fumadocs-ui/components/card" 3 | import { ArrowRight } from "lucide-react" 4 | import Link from "next/link.js" 5 | 6 | export const LinkCard: React.FC<{ 7 | href: string 8 | description: string 9 | title: string 10 | className?: string 11 | date?: string 12 | }> = ({ href, description, title, className, date }) => ( 13 | 14 | 18 | 19 |

{description}

20 | {date && ( 21 | 22 | {date} 23 | 24 | )} 25 |
26 | 27 | ) 28 | -------------------------------------------------------------------------------- /ark/docs/components/LocalFriendlyUrl.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { useEffect, useState } from "react" 3 | 4 | export interface LocalFriendlyUrlProps { 5 | children: string 6 | url: string 7 | key?: string | number | undefined 8 | } 9 | 10 | export const LocalFriendlyUrl = (props: LocalFriendlyUrlProps) => { 11 | const [locallyAccessibleUrl, setLocallyAccessibleUrl] = useState(props.url) 12 | 13 | if (process.env.NODE_ENV === "development") { 14 | useEffect(() => { 15 | const devFriendlyUrl = new URL(props.url) 16 | devFriendlyUrl.protocol = "http:" 17 | devFriendlyUrl.host = window.location.host 18 | setLocallyAccessibleUrl(devFriendlyUrl.toString()) 19 | }, [props.url]) 20 | } 21 | 22 | return ( 23 | 28 | {props.children} 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /ark/docs/components/ReleaseBanner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { usePathname } from "next/navigation.js" 4 | import { Banner } from "./Banner.tsx" 5 | 6 | const text = "📈 Announcing ArkType 2.1 📈" 7 | 8 | export const ReleaseBanner = () => ( 9 | 15 | {text} 16 | 17 | ) 18 | -------------------------------------------------------------------------------- /ark/docs/components/SyntaxTabs.tsx: -------------------------------------------------------------------------------- 1 | import type { omit, unionToPropwiseXor } from "@ark/util" 2 | import { Tab, Tabs, type TabsProps } from "fumadocs-ui/components/tabs" 3 | import { Children, isValidElement, type FC } from "react" 4 | 5 | export const syntaxKinds = [ 6 | "string", 7 | "fluent", 8 | "tuple", 9 | "args", 10 | "function" 11 | // don't infer as readonly since Fumadocs (incorrectly) doesn't support that 12 | ] as const satisfies string[] 13 | 14 | export type SyntaxKind = (typeof syntaxKinds)[number] 15 | 16 | export const SyntaxTabs: React.FC> = ({ 17 | children, 18 | ...rest 19 | }) => { 20 | const usedKinds = Children.toArray(children as never).flatMap(child => { 21 | if (!isValidElement(child)) return [] 22 | if (!child.props) return [] 23 | 24 | const props = child.props as SyntaxTabProps 25 | 26 | const matchingKind = syntaxKinds.find(k => props[k]) 27 | if (!matchingKind) return [] 28 | 29 | return matchingKind 30 | }) 31 | 32 | return ( 33 | 34 | {children} 35 | 36 | ) 37 | } 38 | 39 | type DiscriminatedSyntaxKindProps = unionToPropwiseXor< 40 | { 41 | [kind in SyntaxKind]: { [k in kind]: true } 42 | }[SyntaxKind] 43 | > 44 | 45 | type SyntaxTabProps = DiscriminatedSyntaxKindProps & { 46 | children: React.ReactNode 47 | } 48 | 49 | export const SyntaxTab: FC = props => ( 50 | props[k])!}>{props.children} 51 | ) 52 | -------------------------------------------------------------------------------- /ark/docs/components/icons/arktype-logo.tsx: -------------------------------------------------------------------------------- 1 | export const ArkTypeLogo = ({ height = 40, width = 40 }) => ( 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export const ArkTypeLogoTransparent = () => ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /ark/docs/components/icons/js.tsx: -------------------------------------------------------------------------------- 1 | export const JsIcon: React.FC<{ height: number }> = ({ height }) => ( 2 | 3 | 4 | 5 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /ark/docs/components/icons/npm.tsx: -------------------------------------------------------------------------------- 1 | export const NPMIcon = () => ( 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /ark/docs/components/icons/ts.tsx: -------------------------------------------------------------------------------- 1 | export const TsIcon: React.FC<{ height: number; className?: string }> = ({ 2 | height, 3 | className 4 | }) => ( 5 | 11 | 12 | 13 | 19 | 20 | 21 | ) 22 | -------------------------------------------------------------------------------- /ark/docs/components/playground/RestoreDefault.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "fumadocs-ui/components/api" 2 | import { RotateCcwIcon } from "lucide-react" 3 | 4 | export interface RestoreDefaultProps { 5 | onClick: () => void 6 | variant?: "full" | "icon" 7 | className?: string 8 | } 9 | 10 | export const RestoreDefault = ({ 11 | onClick, 12 | variant = "full", 13 | className 14 | }: RestoreDefaultProps) => { 15 | if (variant === "icon") { 16 | return ( 17 | 30 | ) 31 | } 32 | 33 | return ( 34 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /ark/docs/components/playground/ShareLink.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "fumadocs-ui/components/api" 2 | import { CheckIcon, LinkIcon } from "lucide-react" 3 | import { useState } from "react" 4 | import { copyToClipboard } from "./utils.ts" 5 | 6 | export interface ShareLinkProps { 7 | onShare: () => string 8 | className?: string 9 | } 10 | 11 | export const ShareLink = ({ onShare, className }: ShareLinkProps) => { 12 | const [copied, setCopied] = useState(false) 13 | 14 | const handleClick = async () => { 15 | const url = onShare() 16 | const success = await copyToClipboard(url) 17 | 18 | if (success) { 19 | setCopied(true) 20 | setTimeout(() => setCopied(false), 2000) 21 | } 22 | } 23 | 24 | return ( 25 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /ark/docs/components/playground/TraverseResult.tsx: -------------------------------------------------------------------------------- 1 | import { hasArkKind } from "@ark/schema" 2 | import { printable, unset } from "@ark/util" 3 | import type { type } from "arktype" 4 | import { type ResultKind, backgroundsByResultKind } from "./utils.ts" 5 | 6 | export type TraverseResult = type.errors | unset | {} | null | undefined 7 | 8 | export declare namespace TraverseResult { 9 | export type Props = { 10 | traversed: TraverseResult 11 | } 12 | } 13 | 14 | export const TraverseResult = ({ traversed }: TraverseResult.Props) => { 15 | const resultKind: ResultKind = 16 | hasArkKind(traversed, "errors") ? "failure" 17 | : traversed === unset ? "none" 18 | : "success" 19 | 20 | return ( 21 |
27 |

28 | {resultKind === "failure" ? "ArkErrors" : "Out"} 29 |

30 | { 31 |
32 | 					
33 | 						{resultKind === "none" ?
34 | 							"(variable was never set)"
35 | 						: resultKind === "failure" ?
36 | 							(traversed as type.errors).summary
37 | 						:	printable(traversed, { quoteKeys: false, indent: 4 })}
38 | 					
39 | 				
40 | } 41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /ark/docs/components/playground/execute.ts: -------------------------------------------------------------------------------- 1 | import { unset } from "@ark/util" 2 | import * as arktypeExports from "arktype" 3 | import type { ParseResult } from "./ParseResult.tsx" 4 | import type { TraverseResult } from "./TraverseResult.tsx" 5 | import { 6 | playgroundOutVariableName, 7 | playgroundTypeVariableName 8 | } from "./utils.ts" 9 | 10 | export interface ExecutionResult { 11 | parsed: ParseResult 12 | traversed: TraverseResult 13 | } 14 | 15 | if (!("type" in globalThis)) Object.assign(globalThis, arktypeExports) 16 | 17 | export const executeCode = (code: string): ExecutionResult => { 18 | const isolatedUserCode = code 19 | .replaceAll(/^\s*import .*\n/g, "") 20 | .replaceAll(/^\s*export\s+const/gm, "const") 21 | 22 | try { 23 | const wrappedCode = `${isolatedUserCode} 24 | return { ${playgroundTypeVariableName}, ${playgroundOutVariableName} }` 25 | 26 | const result = new Function(wrappedCode)() 27 | const { 28 | [playgroundTypeVariableName]: parsed, 29 | [playgroundOutVariableName]: traversed 30 | } = result 31 | 32 | return { 33 | parsed, 34 | traversed 35 | } 36 | } catch (e) { 37 | return { 38 | parsed: 39 | e instanceof Error ? e : new ReferenceError(playgroundTypeVariableName), 40 | traversed: unset 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ark/docs/components/playground/format.ts: -------------------------------------------------------------------------------- 1 | import type * as Monaco from "monaco-editor" 2 | import prettierPluginEstree from "prettier/plugins/estree" 3 | import prettierPluginTypeScript from "prettier/plugins/typescript" 4 | import prettier from "prettier/standalone" 5 | 6 | export const formatEditor = async ( 7 | editor: Monaco.editor.IStandaloneCodeEditor 8 | ): Promise => { 9 | const model = editor.getModel() 10 | if (!model) return 11 | 12 | try { 13 | const currentCode = editor.getValue() 14 | const cursorPosition = editor.getPosition() 15 | const cursorOffset = model.getOffsetAt(cursorPosition!)! 16 | 17 | const result = await prettier.formatWithCursor(currentCode, { 18 | parser: "typescript", 19 | cursorOffset, 20 | plugins: [prettierPluginEstree, prettierPluginTypeScript], 21 | semi: false, 22 | useTabs: true, 23 | trailingComma: "none", 24 | experimentalTernaries: true 25 | }) 26 | model.setValue(result.formatted) 27 | editor.setPosition(model.getPositionAt(result.cursorOffset)) 28 | } catch { 29 | // could have invalid syntax etc., fail silently 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ark/docs/components/playground/highlights.ts: -------------------------------------------------------------------------------- 1 | import arktypeTextmate from "arkdark/tsWithArkType.tmLanguage.json" with { type: "json" } 2 | import arkdarkColors from "arkthemes/arkdark.json" with { type: "json" } 3 | import type * as Monaco from "monaco-editor" 4 | import { wireTmGrammars } from "monaco-editor-textmate" 5 | import { Registry } from "monaco-textmate" 6 | 7 | interface VSCodeTheme { 8 | colors: { 9 | [name: string]: string 10 | } 11 | tokenColors: TokenColor[] 12 | } 13 | 14 | interface TokenColor { 15 | scope: string | string[] 16 | settings: { 17 | foreground?: string 18 | background?: string 19 | fontStyle?: string 20 | } 21 | } 22 | 23 | const vsCodeThemeToMonaco = ( 24 | theme: VSCodeTheme 25 | ): Monaco.editor.IStandaloneThemeData => ({ 26 | base: "vs-dark", 27 | inherit: false, 28 | colors: theme.colors, 29 | rules: arkdarkColors.tokenColors.flatMap(c => 30 | Array.isArray(c.scope) ? 31 | c.scope.map(token => ({ token, ...c.settings })) 32 | : [{ token: c.scope, ...c.settings }] 33 | ) 34 | }) 35 | 36 | export const theme = vsCodeThemeToMonaco(arkdarkColors) 37 | 38 | export const setupTextmateGrammar = async (monaco: typeof Monaco) => 39 | await wireTmGrammars( 40 | monaco, 41 | new Registry({ 42 | getGrammarDefinition: async () => ({ 43 | format: "json", 44 | content: arktypeTextmate 45 | }) 46 | }), 47 | new Map().set("typescript", "source.ts") 48 | ) 49 | -------------------------------------------------------------------------------- /ark/docs/components/playground/tsserver.ts: -------------------------------------------------------------------------------- 1 | import type * as Monaco from "monaco-editor" 2 | import { schemaDts } from "../dts/schema.ts" 3 | import { typeDts } from "../dts/type.ts" 4 | import { utilDts } from "../dts/util.ts" 5 | 6 | const configureTypeScript = (monaco: typeof Monaco): void => { 7 | const tsDefaultModeConfig = ( 8 | monaco.languages.typescript.typescriptDefaults as any 9 | )._modeConfiguration 10 | tsDefaultModeConfig.hovers = false 11 | tsDefaultModeConfig.completionItems = false 12 | 13 | monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ 14 | strict: true, 15 | exactOptionalPropertyTypes: true, 16 | target: monaco.languages.typescript.ScriptTarget.ESNext, 17 | moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, 18 | allowNonTsExtensions: true 19 | }) 20 | 21 | monaco.languages.typescript.typescriptDefaults.addExtraLib(utilDts) 22 | monaco.languages.typescript.typescriptDefaults.addExtraLib(schemaDts) 23 | monaco.languages.typescript.typescriptDefaults.addExtraLib(typeDts) 24 | } 25 | 26 | export const getInitializedTypeScriptService = async ( 27 | monaco: typeof Monaco, 28 | editorFileUri: string, 29 | contents: string 30 | ): Promise => { 31 | const targetUri = monaco.Uri.parse(editorFileUri) 32 | configureTypeScript(monaco) 33 | 34 | if (!monaco.editor.getModel(targetUri)) 35 | monaco.editor.createModel(contents, "typescript", targetUri) 36 | 37 | const worker = await monaco.languages.typescript.getTypeScriptWorker() 38 | return await worker(targetUri) 39 | } 40 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/betterErrors.twoslash.ts: -------------------------------------------------------------------------------- 1 | import { type, type ArkErrors } from "arktype" 2 | 3 | const User = type({ 4 | name: "string", 5 | platform: "'android' | 'ios'", 6 | "versions?": "(number | string)[]" 7 | }) 8 | 9 | interface RuntimeErrors extends ArkErrors { 10 | /**platform must be "android" or "ios" (was "enigma") 11 | versions[2] must be a number or a string (was bigint)*/ 12 | summary: string 13 | } 14 | 15 | const narrowMessage = (e: ArkErrors): e is RuntimeErrors => true 16 | 17 | // ---cut--- 18 | const out = User({ 19 | name: "Alan Turing", 20 | platform: "enigma", 21 | versions: [0, "1", 0n] 22 | }) 23 | 24 | if (out instanceof type.errors) { 25 | // ---cut-start--- 26 | if (!narrowMessage(out)) throw new Error() 27 | // ---cut-end--- 28 | // hover summary to see validation errors 29 | console.error(out.summary) 30 | } 31 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/clarityAndConcision.twoslash.js: -------------------------------------------------------------------------------- 1 | // @errors: 2322 2 | import { type } from "arktype" 3 | // this file is written in JS so that it can include a syntax error 4 | // without creating a type error while still displaying the error in twoslash 5 | // ---cut--- 6 | // hover me 7 | const User = type({ 8 | name: "string", 9 | platform: "'android' | 'ios'", 10 | "versions?": "number | string)[]" 11 | }) 12 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/deepIntrospectability.twoslash.js: -------------------------------------------------------------------------------- 1 | import { type } from "arktype" 2 | 3 | const User = type({ 4 | name: "string", 5 | device: { 6 | platform: "'android' | 'ios'", 7 | "version?": "number | string" 8 | } 9 | }) 10 | 11 | // ---cut--- 12 | User.extends("object") // true 13 | User.extends("string") // false 14 | // true (string is narrower than unknown) 15 | User.extends({ 16 | name: "unknown" 17 | }) 18 | // false (string is wider than "Alan") 19 | User.extends({ 20 | name: "'Alan'" 21 | }) 22 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/intrinsicOptimization.twoslash.js: -------------------------------------------------------------------------------- 1 | import { type } from "arktype" 2 | // prettier-ignore 3 | // ---cut--- 4 | // all unions are optimally discriminated 5 | // even if multiple/nested paths are needed 6 | const Account = type({ 7 | kind: "'admin'", 8 | "powers?": "string[]" 9 | }).or({ 10 | kind: "'superadmin'", 11 | "superpowers?": "string[]" 12 | }).or({ 13 | kind: "'pleb'" 14 | }) 15 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/nestedTypeInScopeError.twoslash.js: -------------------------------------------------------------------------------- 1 | // @errors: 2322 2 | import { scope } from "arktype" 3 | // ---cut--- 4 | const myScope = scope({ 5 | id: "string#id", 6 | user: type({ 7 | name: "string", 8 | id: "id" 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /ark/docs/components/snippets/unparalleledDx.twoslash.js: -------------------------------------------------------------------------------- 1 | // @noErrors 2 | import { type } from "arktype" 3 | // prettier-ignore 4 | // ---cut--- 5 | const User = type({ 6 | name: "string", 7 | platform: "'android' | 'ios'", 8 | "version?": "number | s" 9 | // ^| 10 | }) 11 | -------------------------------------------------------------------------------- /ark/docs/content/docs/blog/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Posts 3 | --- 4 | 5 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /ark/docs/content/docs/blog/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Blog", 3 | "defaultOpen": true, 4 | "pages": [ 5 | "[2.1 Announcement](/docs/blog/2.1)", 6 | "[2.0 Announcement](/docs/blog/2.0)" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ark/docs/content/docs/comparisons.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comparisons 3 | --- 4 | -------------------------------------------------------------------------------- /ark/docs/content/docs/configuration/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration", 3 | "pages": [ 4 | "[levels](/docs/configuration#levels)", 5 | "[errors](/docs/configuration#errors)", 6 | "[keywords](/docs/configuration#keywords)", 7 | "[clone](/docs/configuration#clone)", 8 | "[onUndeclaredKey](/docs/configuration#onundeclaredkey)", 9 | "[jitless](/docs/configuration#jitless)", 10 | "[onFail](/docs/configuration#onfail)", 11 | "[metadata](/docs/configuration#metadata)", 12 | "[toJsonSchema](/docs/configuration#tojsonschema)", 13 | "[prototypes](/docs/configuration#prototypes)" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /ark/docs/content/docs/expressions/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Expressions", 3 | "pages": [ 4 | "[intersection](/docs/expressions#intersection)", 5 | "[unions](/docs/expressions#unions)", 6 | "[brand](/docs/expressions#brand)", 7 | "[narrow](/docs/expressions#narrow)", 8 | "[morph](/docs/expressions#morph)", 9 | "[unit](/docs/expressions#unit)", 10 | "[enumerated](/docs/expressions#enumerated)", 11 | "[valueOf](/docs/expressions#valueOf)", 12 | "[meta](/docs/expressions#meta)", 13 | "[cast](/docs/expressions#cast)", 14 | "[parenthetical](/docs/expressions#parenthetical)", 15 | "[this](/docs/expressions#this)" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ark/docs/content/docs/generics/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Generics", 3 | "icon": "Advanced", 4 | "pages": [ 5 | "[keywords](/docs/generics#keywords)", 6 | "[syntax](/docs/generics#syntax)", 7 | "[hkt](/docs/generics#hkt)", 8 | "[external](/docs/generics#external)" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ark/docs/content/docs/integrations/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Integrations", 3 | "pages": [ 4 | "[Standard Schema](/docs/integrations#standard-schema)", 5 | "[tRPC](/docs/integrations#trpc)", 6 | "[drizzle](/docs/integrations#drizzle)", 7 | "[react-hook-form](/docs/integrations#react-hook-form)", 8 | "[hono](/docs/integrations#hono)" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ark/docs/content/docs/intro/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Intro", 3 | "defaultOpen": true, 4 | "pages": ["setup", "your-first-type", "adding-constraints", "morphs-and-more"] 5 | } 6 | -------------------------------------------------------------------------------- /ark/docs/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "intro", 4 | "primitives", 5 | "objects", 6 | "keywords", 7 | "expressions", 8 | "match", 9 | "configuration", 10 | "type-api", 11 | "traversal-api", 12 | "integrations", 13 | "scopes", 14 | "generics", 15 | "blog", 16 | "faq" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ark/docs/content/docs/objects/arrays/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "arrays and tuples", 3 | "pages": [ 4 | "[lengths](/docs/objects#arrays-lengths)", 5 | "[prefix](/docs/objects#tuples-prefix)", 6 | "[defaultable](/docs/objects#tuples-defaultable)", 7 | "[optional](/docs/objects#tuples-optional)", 8 | "[variadic](/docs/objects#tuples-variadic)", 9 | "[postfix](/docs/objects#tuples-postfix)" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /ark/docs/content/docs/objects/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Objects", 3 | "defaultOpen": true, 4 | "pages": [ 5 | "properties", 6 | "arrays", 7 | "[dates](/docs/objects#dates)", 8 | "[instanceof](/docs/objects#instanceof)" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ark/docs/content/docs/objects/properties/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "properties", 3 | "pages": [ 4 | "[required](/docs/objects#properties-required)", 5 | "[optional](/docs/objects#properties-optional)", 6 | "[defaultable](/docs/objects#properties-defaultable)", 7 | "[index](/docs/objects#properties-index)", 8 | "[undeclared](/docs/objects#properties-undeclared)", 9 | "[spread](/docs/objects#properties-spread)", 10 | "[keyof](/docs/objects#properties-keyof)", 11 | "[get](/docs/objects#properties-get)" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /ark/docs/content/docs/primitives/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Primitives", 3 | "defaultOpen": true, 4 | "pages": [ 5 | "string", 6 | "number", 7 | "[bigint](/docs/primitives#bigint)", 8 | "[symbol](/docs/primitives#symbol)", 9 | "[boolean](/docs/primitives#boolean)", 10 | "[null](/docs/primitives#null)", 11 | "[undefined](/docs/primitives#undefined)" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /ark/docs/content/docs/primitives/number/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "number", 3 | "pages": [ 4 | "[keywords](/docs/primitives#number-keywords)", 5 | "[literals](/docs/primitives#number-literals)", 6 | "[ranges](/docs/primitives#number-ranges)", 7 | "[divisors](/docs/primitives#number-divisors)" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /ark/docs/content/docs/primitives/string/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "string", 3 | "pages": [ 4 | "[keywords](/docs/primitives#string-keywords)", 5 | "[literals](/docs/primitives#string-literals)", 6 | "[patterns](/docs/primitives#string-patterns)", 7 | "[lengths](/docs/primitives#string-lengths)" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /ark/docs/content/docs/scopes/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Scopes", 3 | "icon": "Advanced", 4 | "pages": [ 5 | "[modules](/docs/scopes#modules)", 6 | "[visibility](/docs/scopes#visibility)", 7 | "[submodules](/docs/scopes#submodules)", 8 | "[thunks](/docs/scopes#thunks)" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ark/docs/content/docs/traversal-api.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Traversal API 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /ark/docs/content/docs/type-api/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Type API 3 | --- 4 | 5 | 6 | 7 | ### toJsonSchema 8 | 9 | Each `Type` instance exposes a `toJsonSchema()` method that can be used to generate a corresponding JSON Schema. 10 | 11 | ```ts 12 | const User = type({ 13 | name: "string", 14 | email: "string.email", 15 | "age?": "number >= 18" 16 | }) 17 | 18 | const schema = User.toJsonSchema() 19 | 20 | const result = { 21 | $schema: "https://json-schema.org/draft/2020-12/schema", 22 | type: "object", 23 | properties: { 24 | name: { type: "string" }, 25 | email: { 26 | type: "string", 27 | format: "email", 28 | pattern: "^[\w%+.-]+@[\d.A-Za-z-]+\.[A-Za-z]{2,}$" 29 | }, 30 | age: { type: "number", minimum: 18 } 31 | }, 32 | required: ["name", "email"] 33 | } 34 | ``` 35 | 36 | Options can be passed to change the behavior including how incompatibilities are handled. See [the associated config docs](/docs/configuration#toJsonSchema) for more details. 37 | -------------------------------------------------------------------------------- /ark/docs/content/docs/type-api/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Type API", 3 | "pages": ["[toJsonSchema](/docs/type-api#tojsonschema)"] 4 | } 5 | -------------------------------------------------------------------------------- /ark/docs/lib/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.css" {} 2 | 3 | declare module "*?raw" { 4 | const _: string 5 | export default _ 6 | } 7 | -------------------------------------------------------------------------------- /ark/docs/lib/metadata.ts: -------------------------------------------------------------------------------- 1 | import { createMetadataImage } from "fumadocs-core/server" 2 | import { source } from "./source.tsx" 3 | 4 | export const metadataImage = createMetadataImage({ 5 | imageRoute: "/docs-og", 6 | source 7 | }) 8 | -------------------------------------------------------------------------------- /ark/docs/lib/source.tsx: -------------------------------------------------------------------------------- 1 | import type { autocomplete } from "@ark/util" 2 | import { loader } from "fumadocs-core/source" 3 | import { icons } from "lucide-react" 4 | import { createElement } from "react" 5 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 6 | import { docs } from "../.source/index.ts" 7 | import { Badge } from "../components/Badge.tsx" 8 | 9 | export type IconName = keyof typeof icons | "Advanced" 10 | 11 | export const source = loader({ 12 | baseUrl: "/docs", 13 | source: docs.toFumadocsSource(), 14 | icon: (name?: autocomplete) => { 15 | if (!name) return 16 | if (name in icons) return createElement(icons[name as never]) 17 | 18 | if (name === "Advanced") { 19 | return ( 20 | 28 | advanced 29 | 30 | ) 31 | } 32 | 33 | throw new Error(`${name} is not a valid icon`) 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /ark/docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /ark/docs/next.config.ts: -------------------------------------------------------------------------------- 1 | import { createMDX } from "fumadocs-mdx/next" 2 | import type { NextConfig } from "next" 3 | import { updateSnippetsEntrypoint } from "./lib/writeSnippetsEntrypoint.ts" 4 | 5 | updateSnippetsEntrypoint() 6 | 7 | const config = { 8 | reactStrictMode: true, 9 | cleanDistDir: true, 10 | serverExternalPackages: ["twoslash", "typescript", "ts-morph"], 11 | // the following properties are required by nextjs-github-pages: 12 | // https://github.com/gregrickaby/nextjs-github-pages 13 | output: "export", 14 | images: { 15 | unoptimized: true 16 | } 17 | } as const satisfies NextConfig 18 | 19 | const mdxConfig = createMDX()(config) 20 | 21 | export default mdxConfig 22 | -------------------------------------------------------------------------------- /ark/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/docs", 3 | "version": "0.0.1", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "dev": "next dev --turbo", 9 | "buildAndStart": "pnpm build && pnpm start", 10 | "start": "pnpm serve out", 11 | "clean": "rm -rf .next .source out", 12 | "upDeps": "pnpm up --latest", 13 | "checkLinks": "linkinator https://arktype.io -r" 14 | }, 15 | "dependencies": { 16 | "@ark/fs": "workspace:*", 17 | "@ark/repo": "workspace:*", 18 | "@ark/schema": "workspace:*", 19 | "@ark/util": "workspace:*", 20 | "@fumadocs/mdx-remote": "1.3.0", 21 | "@icons-pack/react-simple-icons": "12.4.0", 22 | "@monaco-editor/react": "4.7.0", 23 | "@oramacloud/client": "2.1.4", 24 | "@shikijs/transformers": "3.2.1", 25 | "arkdark": "workspace:*", 26 | "arkthemes": "workspace:*", 27 | "arktype": "workspace:*", 28 | "class-variance-authority": "0.7.1", 29 | "framer-motion": "12.6.3", 30 | "fumadocs-core": "15.2.3", 31 | "fumadocs-mdx": "11.5.7", 32 | "fumadocs-twoslash": "3.1.0", 33 | "fumadocs-ui": "15.2.3", 34 | "hast-util-to-jsx-runtime": "2.3.6", 35 | "linkinator": "6.1.2", 36 | "lucide-react": "0.487.0", 37 | "monaco-editor": "0.52.2", 38 | "monaco-editor-textmate": "4.0.0", 39 | "monaco-textmate": "3.0.1", 40 | "next": "15.2.4", 41 | "onigasm": "2.2.5", 42 | "postcss": "8.5.3", 43 | "posthog-js": "1.234.8", 44 | "prettier": "3.5.3", 45 | "react": "19.1.0", 46 | "react-dom": "19.1.0", 47 | "serve": "14.2.4", 48 | "shiki": "3.2.1", 49 | "ts-morph": "25.0.1", 50 | "twoslash": "0.3.1" 51 | }, 52 | "devDependencies": { 53 | "@fumadocs/cli": "0.1.0", 54 | "@tailwindcss/postcss": "4.1.2", 55 | "@types/mdx": "2.0.13", 56 | "@types/react": "19.1.0", 57 | "@types/react-dom": "19.1.1", 58 | "prettier-plugin-tailwindcss": "0.6.11", 59 | "tailwindcss": "4.1.2", 60 | "typescript": "catalog:" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ark/docs/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "@tailwindcss/postcss": {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ark/docs/public/CNAME: -------------------------------------------------------------------------------- 1 | arktype.io -------------------------------------------------------------------------------- /ark/docs/public/image/errorSquiggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ark/docs/public/image/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /ark/docs/public/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/image/logo.png -------------------------------------------------------------------------------- /ark/docs/public/image/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/image/og.png -------------------------------------------------------------------------------- /ark/docs/public/image/ogDiscord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/image/ogDiscord.png -------------------------------------------------------------------------------- /ark/docs/public/image/ogDocs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/image/ogDocs.png -------------------------------------------------------------------------------- /ark/docs/public/image/ogPlayground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/image/ogPlayground.png -------------------------------------------------------------------------------- /ark/docs/public/onigasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/docs/public/onigasm.wasm -------------------------------------------------------------------------------- /ark/docs/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, defineDocs } from "fumadocs-mdx/config" 2 | import { shikiConfig } from "./lib/shiki.ts" 3 | 4 | export const docs = defineDocs({ 5 | dir: "content/docs", 6 | docs: { 7 | async: true 8 | } 9 | }) 10 | 11 | export default defineConfig({ 12 | mdxOptions: { 13 | rehypeCodeOptions: shikiConfig 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /ark/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | // unfortunately, twoslash doesn't seem to respect customConditions, 5 | // so .d.ts will need to be rebuilt to see its static compilation updated 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowJs": true, 9 | "jsx": "preserve", 10 | // we don't need declarations for docs, and enabling it causes 11 | // pnpm resolution errors 12 | "declaration": false, 13 | "lib": ["ESNext", "DOM"], 14 | "baseUrl": ".", 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "incremental": true 21 | }, 22 | "mdx": { 23 | "checkMdx": true 24 | }, 25 | "include": [ 26 | "next.config.ts", 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts" 31 | ], 32 | "exclude": ["node_modules"], 33 | "customConditions": ["ark-ts"] 34 | } 35 | -------------------------------------------------------------------------------- /ark/extension/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /ark/extension/errorLens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/extension/errorLens.png -------------------------------------------------------------------------------- /ark/extension/highlighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/extension/highlighting.png -------------------------------------------------------------------------------- /ark/extension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/extension/icon.png -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/array.ts: -------------------------------------------------------------------------------- 1 | import type { nodeOfKind, SequenceTuple } from "@ark/schema" 2 | import * as fc from "fast-check" 3 | import type { Ctx } from "../fastCheckContext.ts" 4 | 5 | export const getPossiblyWeightedArray = ( 6 | arrArbitrary: fc.Arbitrary, 7 | node: nodeOfKind<"sequence">, 8 | ctx: Ctx 9 | ): fc.Arbitrary => 10 | ctx.tieStack.length ? 11 | fc.oneof( 12 | { 13 | maxDepth: 2, 14 | depthIdentifier: `id:${node.id}` 15 | }, 16 | { arbitrary: fc.constant([]), weight: 1 }, 17 | { 18 | arbitrary: arrArbitrary, 19 | weight: 2 20 | } 21 | ) 22 | : arrArbitrary 23 | 24 | export const spreadVariadicElements = ( 25 | tupleArbitraries: fc.Arbitrary[], 26 | tupleElements: SequenceTuple 27 | ): fc.Arbitrary => 28 | fc.tuple(...tupleArbitraries).chain(arr => { 29 | const arrayWithoutOptionals = [] 30 | const arrayWithOptionals = [] 31 | 32 | for (const i in arr) { 33 | if (tupleElements[i].kind === "variadic") { 34 | const generatedValuesArray = (arr[i] as unknown[]).map(val => 35 | fc.constant(val) 36 | ) 37 | arrayWithoutOptionals.push(...generatedValuesArray) 38 | arrayWithOptionals.push(...generatedValuesArray) 39 | } else if (tupleElements[i].kind === "optionals") 40 | arrayWithOptionals.push(fc.constant(arr[i])) 41 | else { 42 | arrayWithoutOptionals.push(fc.constant(arr[i])) 43 | arrayWithOptionals.push(fc.constant(arr[i])) 44 | } 45 | } 46 | if (arrayWithOptionals.length !== arrayWithoutOptionals.length) { 47 | return fc.oneof( 48 | fc.tuple(...arrayWithoutOptionals), 49 | fc.tuple(...arrayWithOptionals) 50 | ) 51 | } 52 | return fc.tuple(...arrayWithOptionals) 53 | }) 54 | -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/date.ts: -------------------------------------------------------------------------------- 1 | import * as fc from "fast-check" 2 | import type { ProtoInputNode } from "./proto.ts" 3 | 4 | export const buildDateArbitrary = ( 5 | node: ProtoInputNode 6 | ): fc.Arbitrary => { 7 | if (node.hasKind("intersection")) { 8 | const fastCheckDateConstraints: fc.DateConstraints = {} 9 | if (node.inner.after) fastCheckDateConstraints.min = node.inner.after.rule 10 | if (node.inner.before) fastCheckDateConstraints.max = node.inner.before.rule 11 | 12 | return fc.date(fastCheckDateConstraints) 13 | } 14 | return fc.date() 15 | } 16 | -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/domain.ts: -------------------------------------------------------------------------------- 1 | import type { nodeOfKind } from "@ark/schema" 2 | import * as fc from "fast-check" 3 | import { buildStructureArbitrary } from "../arktypeFastCheck.ts" 4 | import type { Ctx } from "../fastCheckContext.ts" 5 | import { buildNumberArbitrary } from "./number.ts" 6 | import { buildStringArbitrary } from "./string.ts" 7 | 8 | export const buildDomainArbitrary: BuildDomainArbitrary = { 9 | number: node => buildNumberArbitrary(node), 10 | string: node => buildStringArbitrary(node), 11 | object: (node, ctx) => 12 | node.hasKind("domain") ? fc.object() : buildStructureArbitrary(node, ctx), 13 | symbol: () => fc.constant(Symbol()), 14 | bigint: () => fc.bigInt() 15 | } 16 | 17 | export type DomainArbitrary = ( 18 | node: DomainInputNode, 19 | ctx: Ctx 20 | ) => fc.Arbitrary 21 | 22 | type BuildDomainArbitrary = { 23 | number: DomainArbitrary 24 | string: DomainArbitrary 25 | symbol: DomainArbitrary 26 | bigint: DomainArbitrary 27 | object: DomainArbitrary 28 | } 29 | 30 | export type DomainInputNode = nodeOfKind<"intersection"> | nodeOfKind<"domain"> 31 | -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/object.ts: -------------------------------------------------------------------------------- 1 | import type { nodeOfKind } from "@ark/schema" 2 | import { letrec, type Arbitrary, type LetrecValue } from "fast-check" 3 | import { buildObjectArbitrary } from "../arktypeFastCheck.ts" 4 | import type { Ctx } from "../fastCheckContext.ts" 5 | 6 | export const buildCyclicArbitrary = ( 7 | node: nodeOfKind<"structure">, 8 | ctx: Ctx 9 | ): Arbitrary> => { 10 | const objectArbitrary: LetrecValue = letrec(tie => { 11 | ctx.tieStack.push(tie) 12 | const arbitraries = { 13 | root: buildObjectArbitrary(node, ctx), 14 | ...ctx.arbitrariesByIntersectionId 15 | } 16 | ctx.tieStack.pop() 17 | return arbitraries 18 | }) 19 | return (objectArbitrary as never)["root"] 20 | } 21 | -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/proto.ts: -------------------------------------------------------------------------------- 1 | import type { nodeOfKind } from "@ark/schema" 2 | import * as fc from "fast-check" 3 | import { buildStructureArbitrary } from "../arktypeFastCheck.ts" 4 | import type { Ctx } from "../fastCheckContext.ts" 5 | import { buildDateArbitrary } from "./date.ts" 6 | import type { DomainInputNode } from "./domain.ts" 7 | 8 | export const buildProtoArbitrary: BuildProtoArbitrary = { 9 | Array: (node, ctx) => 10 | node.hasKind("proto") ? 11 | fc.array(fc.anything()) 12 | : buildStructureArbitrary(node as never, ctx), 13 | Set: () => fc.uniqueArray(fc.anything()).map(arr => new Set(arr)), 14 | Date: node => buildDateArbitrary(node) 15 | } 16 | 17 | type BuildProtoArbitrary = { 18 | Array: ProtoArbitrary 19 | Set: ProtoArbitrary> 20 | Date: ProtoArbitrary 21 | [key: string]: ProtoArbitrary 22 | } 23 | 24 | type ProtoArbitrary = ( 25 | node: ProtoInputNode | DomainInputNode, 26 | ctx: Ctx 27 | ) => fc.Arbitrary 28 | 29 | export type ProtoInputNode = nodeOfKind<"intersection"> | nodeOfKind<"domain"> 30 | -------------------------------------------------------------------------------- /ark/fast-check/arbitraries/string.ts: -------------------------------------------------------------------------------- 1 | import type { nodeOfKind, RefinementKind } from "@ark/schema" 2 | import { throwInternalError, type array } from "@ark/util" 3 | import * as fc from "fast-check" 4 | import type { DomainInputNode } from "./domain.ts" 5 | 6 | export const buildStringArbitrary = ( 7 | node: DomainInputNode 8 | ): fc.Arbitrary => { 9 | if (node.hasKind("domain")) return fc.string() 10 | const stringConstraints = getFastCheckStringConstraints(node.refinements) 11 | if ("pattern" in stringConstraints) { 12 | if (stringConstraints.minLength || stringConstraints.maxLength) 13 | throwInternalError("Bounded regex is not supported.") 14 | return fc.stringMatching(new RegExp(stringConstraints.pattern)) 15 | } 16 | 17 | return fc.string(stringConstraints) 18 | } 19 | 20 | const getFastCheckStringConstraints = ( 21 | refinements: array> 22 | ) => { 23 | const stringConstraints: fc.StringConstraints & { 24 | pattern?: string 25 | } = {} 26 | for (const refinement of refinements) { 27 | if (refinement.hasKind("pattern")) { 28 | if (stringConstraints.pattern !== undefined) { 29 | throwInternalError( 30 | "Multiple regexes on a single node is not supported." 31 | ) 32 | } 33 | stringConstraints["pattern"] = refinement.rule 34 | } else if (refinement.hasKind("exactLength")) { 35 | stringConstraints["minLength"] = refinement.rule 36 | stringConstraints["maxLength"] = refinement.rule 37 | } else 38 | stringConstraints[refinement.kind as never] = refinement.rule as never 39 | } 40 | return stringConstraints 41 | } 42 | -------------------------------------------------------------------------------- /ark/fast-check/fastCheckContext.ts: -------------------------------------------------------------------------------- 1 | import type { Arbitrary, LetrecLooselyTypedTie } from "fast-check" 2 | 3 | export type Ctx = { 4 | seenIntersectionIds: Record 5 | arbitrariesByIntersectionId: Record> 6 | isCyclic: boolean 7 | tieStack: LetrecLooselyTypedTie[] 8 | } 9 | 10 | export const initializeContext = (): Ctx => ({ 11 | seenIntersectionIds: {}, 12 | arbitrariesByIntersectionId: {}, 13 | isCyclic: false, 14 | tieStack: [] 15 | }) 16 | -------------------------------------------------------------------------------- /ark/fast-check/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/fast-check", 3 | "version": "0.0.11", 4 | "license": "MIT", 5 | "author": { 6 | "name": "David Blass", 7 | "email": "david@arktype.io", 8 | "url": "https://arktype.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/arktypeio/arktype.git", 13 | "directory": "ark/fast-check" 14 | }, 15 | "type": "module", 16 | "main": "./out/arktypeFastCheck.js", 17 | "types": "./out/arktypeFastCheck.d.ts", 18 | "exports": { 19 | ".": { 20 | "ark-ts": "./arktypeFastCheck.ts", 21 | "default": "./out/arktypeFastCheck.js" 22 | }, 23 | "./internal/*.ts": { 24 | "ark-ts": "./*.ts", 25 | "default": "./out/*.js" 26 | } 27 | }, 28 | "files": [ 29 | "out" 30 | ], 31 | "scripts": { 32 | "build": "ts ../repo/build.ts", 33 | "test": "tsx ../repo/testPackage.ts" 34 | }, 35 | "dependencies": { 36 | "arktype": "workspace:*", 37 | "@ark/schema": "workspace:*", 38 | "@ark/util": "workspace:*" 39 | }, 40 | "publishConfig": { 41 | "access": "public" 42 | }, 43 | "peerDependencies": { 44 | "fast-check": "3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ark/fs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./caller.ts" 2 | export * from "./fs.ts" 3 | export * from "./shell.ts" 4 | -------------------------------------------------------------------------------- /ark/fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/fs", 3 | "version": "0.46.0", 4 | "license": "MIT", 5 | "author": { 6 | "name": "David Blass", 7 | "email": "david@arktype.io", 8 | "url": "https://arktype.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/arktypeio/arktype.git", 13 | "directory": "ark/fs" 14 | }, 15 | "type": "module", 16 | "main": "./out/index.js", 17 | "types": "./out/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "ark-ts": "./index.ts", 21 | "default": "./out/index.js" 22 | } 23 | }, 24 | "scripts": { 25 | "build": "ts ../repo/build.ts" 26 | }, 27 | "files": [ 28 | "out" 29 | ], 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ark/fs/shell.ts: -------------------------------------------------------------------------------- 1 | import { execSync, type ExecSyncOptions } from "node:child_process" 2 | import * as process from "node:process" 3 | 4 | export type ShellOptions = Omit & { 5 | env?: Record 6 | } 7 | 8 | /** Run the cmd synchronously. Output goes to terminal. */ 9 | export const shell = ( 10 | cmd: string, 11 | { env, ...otherOptions }: ShellOptions = {} 12 | ): void => { 13 | execSync(cmd, { 14 | env: { ...process.env, ...env }, 15 | ...otherOptions, 16 | stdio: "inherit" 17 | }) 18 | } 19 | 20 | /** Run the cmd synchronously, returning output as a string */ 21 | export const getShellOutput = ( 22 | cmd: string, 23 | { env, ...otherOptions }: ShellOptions = {} 24 | ): string => 25 | execSync(cmd, { 26 | env: { ...process.env, ...env }, 27 | ...otherOptions, 28 | stdio: "pipe" 29 | })!.toString() 30 | -------------------------------------------------------------------------------- /ark/json-schema/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @ark/json-schema 2 | 3 | ## 0.0.1 4 | 5 | ### Initial Release 6 | 7 | Released the initial implementation of the package. 8 | -------------------------------------------------------------------------------- /ark/json-schema/__tests__/composition.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { jsonSchemaToType } from "@ark/json-schema" 3 | 4 | contextualize(() => { 5 | it("allOf", () => { 6 | const tAllOf = jsonSchemaToType({ 7 | allOf: [ 8 | { type: "string", minLength: 1 }, 9 | { type: "string", maxLength: 10 } 10 | ] 11 | }) 12 | attest(tAllOf.expression).snap("string <= 10 & >= 1") 13 | }) 14 | 15 | it("anyOf", () => { 16 | const tAnyOf = jsonSchemaToType({ 17 | anyOf: [ 18 | { type: "string", minLength: 1 }, 19 | { type: "string", maxLength: 10 } 20 | ] 21 | }) 22 | attest(tAnyOf.expression).snap("string <= 10 | string >= 1") 23 | }) 24 | 25 | it("not", () => { 26 | const tNot = jsonSchemaToType({ not: { type: "string", maxLength: 3 } }) 27 | attest(tNot.json).snap({ 28 | predicate: ["$ark.jsonSchemaNotValidator"] 29 | }) 30 | 31 | attest(tNot.allows(123)).equals(true) 32 | attest(tNot.allows("1234")).equals(true) 33 | attest(() => tNot.assert("123")).throws( 34 | 'TraversalError: must be not: a string and at most length 3 (was "123")' 35 | ) 36 | }) 37 | 38 | it("oneOf", () => { 39 | const tOneOf = jsonSchemaToType({ 40 | oneOf: [{ type: "string", minLength: 10 }, { const: "foo" }] 41 | }) 42 | attest(tOneOf.json).snap({ 43 | predicate: ["$ark.jsonSchemaOneOfValidator"] 44 | }) 45 | 46 | attest(tOneOf.allows("foo")).equals(true) 47 | attest(tOneOf.allows("1234567890")).equals(true) 48 | attest(() => tOneOf.assert("bar")).throws( 49 | 'TraversalError: must be valid according to jsonSchemaOneOfValidator (was "bar")' 50 | ) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /ark/json-schema/__tests__/string.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { jsonSchemaToType } from "@ark/json-schema" 3 | 4 | contextualize(() => { 5 | it("type string", () => { 6 | const t = jsonSchemaToType({ type: "string" }) 7 | attest(t.expression).snap("string") 8 | }) 9 | 10 | it("maxLength (positive)", () => { 11 | const tMaxLength = jsonSchemaToType({ 12 | type: "string", 13 | maxLength: 5 14 | }) 15 | attest(tMaxLength.expression).snap("string <= 5") 16 | }) 17 | 18 | it("maxLength (negative)", () => { 19 | const maxLength = -5 20 | attest(() => 21 | jsonSchemaToType({ 22 | type: "string", 23 | maxLength 24 | }) 25 | ).throws( 26 | `TraversalError: maxLength must be non-negative (was ${maxLength})` 27 | ) 28 | }) 29 | 30 | it("minLength (positive)", () => { 31 | const tMinLength = jsonSchemaToType({ 32 | type: "string", 33 | minLength: 5 34 | }) 35 | attest(tMinLength.expression).snap("string >= 5") 36 | }) 37 | 38 | it("minLength (negative)", () => { 39 | const minLength = -1 40 | attest(() => 41 | jsonSchemaToType({ 42 | type: "string", 43 | minLength 44 | }) 45 | ).throws( 46 | `TraversalError: minLength must be non-negative (was ${minLength})` 47 | ) 48 | }) 49 | 50 | it("pattern", () => { 51 | const tPatternString = jsonSchemaToType({ 52 | type: "string", 53 | pattern: "es" 54 | }) 55 | attest(tPatternString.expression).snap("/es/") 56 | // JSON Schema explicitly specifies that regexes MUST NOT be implicitly anchored 57 | // https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01#rfc.section.4.3 58 | attest(tPatternString.allows("expression")).equals(true) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /ark/json-schema/common.ts: -------------------------------------------------------------------------------- 1 | import { throwParseError } from "@ark/util" 2 | import { type JsonSchema, type Type, type } from "arktype" 3 | import { writeJsonSchemaCommonConstAndEnumMessage } from "./errors.ts" 4 | 5 | export const parseCommonJsonSchema = ( 6 | jsonSchema: JsonSchema 7 | ): Type | undefined => { 8 | if ("const" in jsonSchema) { 9 | if ("enum" in jsonSchema) 10 | throwParseError(writeJsonSchemaCommonConstAndEnumMessage()) 11 | 12 | return type.unit(jsonSchema.const) 13 | } 14 | 15 | if ("enum" in jsonSchema) return type.enumerated(jsonSchema.enum) 16 | } 17 | -------------------------------------------------------------------------------- /ark/json-schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./errors.ts" 2 | export { jsonSchemaToType } from "./json.ts" 3 | export * from "./scope.ts" 4 | -------------------------------------------------------------------------------- /ark/json-schema/number.ts: -------------------------------------------------------------------------------- 1 | import { rootSchema, type Intersection } from "@ark/schema" 2 | import { throwParseError } from "@ark/util" 3 | import type { JsonSchema, Out, Type } from "arktype" 4 | import { 5 | writeJsonSchemaNumberMaximumAndExclusiveMaximumMessage, 6 | writeJsonSchemaNumberMinimumAndExclusiveMinimumMessage 7 | } from "./errors.ts" 8 | import { JsonSchemaScope } from "./scope.ts" 9 | 10 | export const parseNumberJsonSchema: Type< 11 | (In: JsonSchema.Numeric) => Out>, 12 | any 13 | > = JsonSchemaScope.NumberSchema.pipe((jsonSchema): Type => { 14 | const arktypeNumberSchema: Intersection.Schema = { 15 | domain: "number" 16 | } 17 | 18 | if ("maximum" in jsonSchema) { 19 | if ("exclusiveMaximum" in jsonSchema) 20 | throwParseError(writeJsonSchemaNumberMaximumAndExclusiveMaximumMessage()) 21 | 22 | arktypeNumberSchema.max = jsonSchema.maximum 23 | } else if ("exclusiveMaximum" in jsonSchema) { 24 | arktypeNumberSchema.max = { 25 | rule: jsonSchema.exclusiveMaximum, 26 | exclusive: true 27 | } 28 | } 29 | 30 | if ("minimum" in jsonSchema) { 31 | if ("exclusiveMinimum" in jsonSchema) 32 | throwParseError(writeJsonSchemaNumberMinimumAndExclusiveMinimumMessage()) 33 | 34 | arktypeNumberSchema.min = jsonSchema.minimum 35 | } else if ("exclusiveMinimum" in jsonSchema) { 36 | arktypeNumberSchema.min = { 37 | rule: jsonSchema.exclusiveMinimum, 38 | exclusive: true 39 | } 40 | } 41 | 42 | if ("multipleOf" in jsonSchema) 43 | arktypeNumberSchema.divisor = jsonSchema.multipleOf 44 | else if (jsonSchema.type === "integer") arktypeNumberSchema.divisor = 1 45 | 46 | return rootSchema(arktypeNumberSchema) as never 47 | }) 48 | -------------------------------------------------------------------------------- /ark/json-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/json-schema", 3 | "version": "0.0.3", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "TizzySaurus", 8 | "email": "tizzysaurus@gmail.com", 9 | "url": "https://github.com/tizzysaurus" 10 | }, 11 | { 12 | "name": "David Blass", 13 | "email": "david@arktype.io", 14 | "url": "https://arktype.io" 15 | } 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/arktypeio/arktype.git", 20 | "directory": "ark/json-schema" 21 | }, 22 | "type": "module", 23 | "main": "./out/index.js", 24 | "types": "./out/index.d.ts", 25 | "exports": { 26 | ".": { 27 | "ark-ts": "./index.ts", 28 | "default": "./out/index.js" 29 | }, 30 | "./internal/*.ts": { 31 | "ark-ts": "./*.ts", 32 | "default": "./out/*.js" 33 | } 34 | }, 35 | "files": [ 36 | "out" 37 | ], 38 | "scripts": { 39 | "build": "ts ../repo/build.ts", 40 | "test": "ts ../repo/testPackage.ts", 41 | "tnt": "ts ../repo/testPackage.ts --skipTypes" 42 | }, 43 | "dependencies": { 44 | "arktype": "workspace:*", 45 | "@ark/schema": "workspace:*", 46 | "@ark/util": "workspace:*" 47 | }, 48 | "publishConfig": { 49 | "access": "public" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ark/json-schema/string.ts: -------------------------------------------------------------------------------- 1 | import { rootSchema, type Intersection } from "@ark/schema" 2 | import type { Out, Type } from "arktype" 3 | import { JsonSchemaScope, type StringSchema } from "./scope.ts" 4 | 5 | export const parseStringJsonSchema: Type< 6 | (In: StringSchema) => Out>, 7 | any 8 | > = JsonSchemaScope.StringSchema.pipe((jsonSchema): Type => { 9 | const arktypeStringSchema: Intersection.Schema = { 10 | domain: "string" 11 | } 12 | 13 | if ("maxLength" in jsonSchema) 14 | arktypeStringSchema.maxLength = jsonSchema.maxLength 15 | if ("minLength" in jsonSchema) 16 | arktypeStringSchema.minLength = jsonSchema.minLength 17 | if ("pattern" in jsonSchema) 18 | arktypeStringSchema.pattern = [jsonSchema.pattern] 19 | 20 | return rootSchema(arktypeStringSchema) as never 21 | }) 22 | -------------------------------------------------------------------------------- /ark/repo/build.ts: -------------------------------------------------------------------------------- 1 | import { copyFileSync } from "node:fs" 2 | import { join } from "node:path" 3 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 4 | import { 5 | fromCwd, 6 | fromHere, 7 | readPackageJson, 8 | rmRf, 9 | shell, 10 | writeJson 11 | } from "../fs/index.ts" 12 | import { dtsGen } from "./dtsGen.ts" 13 | import { jsDocGen } from "./jsdocGen.ts" 14 | import { packagesByScope } from "./shared.ts" 15 | 16 | const buildKind = 17 | process.argv.includes("--cjs") || process.env.ARKTYPE_CJS ? "cjs" : "esm" 18 | const outDir = fromCwd("out") 19 | const packageName = readPackageJson(process.cwd()).name 20 | 21 | const buildCurrentProject = () => 22 | shell( 23 | `node ${fromHere("node_modules", "typescript", "lib", "tsc.js")} --project tsconfig.build.json` 24 | ) 25 | 26 | try { 27 | rmRf(outDir) 28 | rmRf("tsconfig.build.json") 29 | copyFileSync(`../repo/tsconfig.${buildKind}.json`, "tsconfig.build.json") 30 | buildCurrentProject() 31 | rmRf("tsconfig.build.json") 32 | copyFileSync(`../repo/tsconfig.dts.json`, "tsconfig.build.json") 33 | buildCurrentProject() 34 | if (buildKind === "cjs") 35 | writeJson(join(outDir, "package.json"), { type: "commonjs" }) 36 | if (packageName === "arktype") { 37 | jsDocGen() 38 | dtsGen() 39 | } else if (packageName in packagesByScope.type.json.dependencies!) dtsGen() 40 | } finally { 41 | rmRf("tsconfig.build.json") 42 | } 43 | -------------------------------------------------------------------------------- /ark/repo/config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 2 | import { configure } from "arktype/config" 3 | 4 | configure({}) 5 | -------------------------------------------------------------------------------- /ark/repo/dtsGen.ts: -------------------------------------------------------------------------------- 1 | import { basename, join } from "node:path" 2 | import { 3 | bootstrapFs, 4 | packagesByScope, 5 | repoDirs, 6 | type PackageScope 7 | } from "./shared.ts" 8 | 9 | const { readFile, shell, rmSync, writeFile } = bootstrapFs 10 | 11 | export const dtsGen = () => { 12 | const pkgScope = basename(process.cwd()) as PackageScope 13 | const pkg = packagesByScope[pkgScope] 14 | 15 | console.log(`✍️ Generating DTS bundle for ${pkg.name}...`) 16 | 17 | shell("pnpm tsup index.ts --dts-only --dts-resolve --format esm --out-dir .") 18 | 19 | const expectedDtsBundlePath = join(pkg.path, "index.d.ts") 20 | 21 | const rawDts = readFile(expectedDtsBundlePath) 22 | rmSync(expectedDtsBundlePath) 23 | 24 | const bundleOutputPath = join( 25 | repoDirs.docs, 26 | "components", 27 | "dts", 28 | `${pkgScope}.ts` 29 | ) 30 | 31 | const rawDtsModule = `declare module "${pkg.name}" { 32 | ${rawDts} 33 | }` 34 | 35 | const rawFileContents = `/** THIS FILE IS AUTOGENERATED FROM ark/repo/dtsGen.ts **/ 36 | // prettier-ignore 37 | export const ${pkg.scope}Dts = ${JSON.stringify(rawDtsModule)}\n` 38 | 39 | // fix some tsup confusion 40 | const fileContents = rawFileContents.replaceAll( 41 | /(Omit|Exclude|Extract|Record)\$1/g, 42 | "$1" 43 | ) 44 | 45 | writeFile(bundleOutputPath, fileContents) 46 | 47 | console.log(`📚 Successfully wrote DTS bundle for ${pkg.name}!`) 48 | } 49 | -------------------------------------------------------------------------------- /ark/repo/mocha.globalSetup.ts: -------------------------------------------------------------------------------- 1 | import { cleanup, setup } from "@ark/attest" 2 | 3 | process.env.TZ = "America/New_York" 4 | 5 | export const mochaGlobalSetup = (): typeof cleanup => 6 | setup({ 7 | typeToStringFormat: { 8 | useTabs: true 9 | } 10 | }) 11 | 12 | export const mochaGlobalTeardown = cleanup 13 | -------------------------------------------------------------------------------- /ark/repo/mocha.package.jsonc: -------------------------------------------------------------------------------- 1 | // This is the per-package config for running mocha tests without including 2 | // context from the entire workspace. 3 | 4 | // Other than require which has its relative path changed, it should be identical 5 | // to the repo root's mocha config from the root package.json 6 | 7 | // IF YOU UPDATE THE MOCHA CONFIG HERE, PLEASE ALSO UPDATE package.json/mocha AND .vscode/settings.json 8 | { 9 | "spec": ["__tests__/*.test.*"], 10 | "ignore": "node_modules/**/*", 11 | "node-option": ["conditions=ark-ts", "import=tsx"], 12 | "require": "../repo/mocha.globalSetup.ts" 13 | } 14 | -------------------------------------------------------------------------------- /ark/repo/nodeOptions.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const [major, minor] = process.version.replace("v", "").split(".").map(Number) 4 | 5 | const versionedFlags = 6 | major > 22 || (major === 22 && minor >= 7) ? 7 | "--experimental-transform-types --no-warnings" 8 | : (console.log( 9 | "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." 10 | ), 11 | "--import tsx") 12 | 13 | export const nodeDevOptions = `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}` 14 | 15 | export const addNodeDevOptions = extraOpts => 16 | (process.env.NODE_OPTIONS = `${nodeDevOptions} ${extraOpts ?? ""}`) 17 | -------------------------------------------------------------------------------- /ark/repo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/repo", 3 | "private": true, 4 | "type": "module", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@ark/attest": "link:../attest", 8 | "@ark/fs": "workspace:*", 9 | "arktype": "workspace:*", 10 | "ts-morph": "25.0.1", 11 | "ts-pattern": "5.7.0", 12 | "type-fest": "4.39.1", 13 | "typescript": "catalog:", 14 | "zod": "3.24.2" 15 | }, 16 | "scripts": { 17 | "build": "echo No build required!" 18 | }, 19 | "bin": { 20 | "ts": "./ts.js" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ark/repo/patchC8.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | // Patch the c8 ignore comment parser to ignore internal errors 3 | const c8Paths = require.resolve("c8") 4 | const covSourcePath = require.resolve("v8-to-istanbul/lib/source", { 5 | paths: [c8Paths] 6 | }) 7 | const CovSource = require(covSourcePath) 8 | 9 | // Wrap the function found here: 10 | // https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/source.js#L53 11 | const original = CovSource.prototype._parseIgnore 12 | if (!original) { 13 | throw new Error( 14 | `Failed to patch c8: unable to find the _parseIgnore function.` 15 | ) 16 | } 17 | 18 | let inThrowInternalCall = false 19 | const stack = [] 20 | const throwInternalRegex = /throwInternal.*\(/ 21 | 22 | CovSource.prototype._parseIgnore = lineStr => { 23 | if (throwInternalRegex.test(lineStr)) { 24 | inThrowInternalCall = true 25 | } 26 | if (inThrowInternalCall) { 27 | if (isBalanced(lineStr)) { 28 | inThrowInternalCall = false 29 | } 30 | return { count: 1 } 31 | } else { 32 | return original(lineStr) 33 | } 34 | } 35 | 36 | const isBalanced = lineStr => { 37 | const filteredMatchers = lineStr 38 | .split(" ") 39 | .filter( 40 | section => 41 | throwInternalRegex.test(section) | 42 | section.includes("(") | 43 | section.includes(")") | 44 | false 45 | ) 46 | 47 | for (let match of filteredMatchers) { 48 | for (let position = 0; position < match.length; position++) { 49 | const section = match[position] 50 | for (let char of section) { 51 | if (char === "(") { 52 | stack.push(char) 53 | } else if (char === ")") { 54 | stack.pop() 55 | } 56 | } 57 | } 58 | } 59 | return !stack.length 60 | } 61 | -------------------------------------------------------------------------------- /ark/repo/publish.ts: -------------------------------------------------------------------------------- 1 | import { getShellOutput, rewriteJson, shell } from "@ark/fs" 2 | import { packages, type ArkPackage } from "./shared.ts" 3 | 4 | const tagsToPublish: string[] = [] 5 | 6 | const existingTags = getShellOutput("git tag").split("\n") 7 | 8 | const publishPackage = (pkg: ArkPackage, alias?: string) => { 9 | const tagName = `${alias ?? pkg.name}@${pkg.version}` 10 | 11 | if (!existingTags.includes(tagName)) { 12 | if (alias) rewritePackageJsonName(pkg.packageJsonPath, alias) 13 | 14 | shell(`git tag ${tagName}`) 15 | tagsToPublish.push(tagName) 16 | shell("pnpm publish --no-git-checks", { cwd: pkg.path }) 17 | 18 | if (alias) rewritePackageJsonName(pkg.packageJsonPath, pkg.name) 19 | } 20 | } 21 | 22 | const rewritePackageJsonName = (path: string, alias: string) => 23 | rewriteJson(path, data => ({ ...data, name: alias })) 24 | 25 | for (const pkg of packages) { 26 | // primary name (either arktype or @ark/*) 27 | publishPackage(pkg) 28 | 29 | // scoped alias for primary entry point 30 | if (pkg.scope === "type") publishPackage(pkg, "@ark/type") 31 | else { 32 | // alias for original @arktype/ scope 33 | publishPackage(pkg, `@arktype/${pkg.scope}`) 34 | } 35 | } 36 | 37 | shell("git push --tags") 38 | 39 | for (const tagName of tagsToPublish) 40 | shell(`gh release create ${tagName} --latest`) 41 | -------------------------------------------------------------------------------- /ark/repo/scratch.ts: -------------------------------------------------------------------------------- 1 | import { type } from "arktype" 2 | 3 | const Even = type.number.divisibleBy(2) 4 | const By3 = type.number.divisibleBy(3) 5 | const By6 = Even.and(By3) 6 | 7 | console.log(By6.description) 8 | By6.extends(By3) //? 9 | By3.extends(By6) //? 10 | -------------------------------------------------------------------------------- /ark/repo/scratch/fn.js: -------------------------------------------------------------------------------- 1 | import { fn } from "./fn.js" 2 | 3 | export const greet = fn({ name: "string[]", isWizard: "boolean" })( 4 | data => `${data.name.join(" ")}${data.isWizard ? "🧙‍♂️" : ":("}` 5 | ) 6 | 7 | const result = greet({ name: ["Matt", "Pocock"], isWizard: true }) 8 | -------------------------------------------------------------------------------- /ark/repo/scratch/typeClass.ts: -------------------------------------------------------------------------------- 1 | import { DynamicBase } from "@ark/util" 2 | import { type } from "arktype" 3 | 4 | const Class = (def: type.validate) => { 5 | const Validator = type(def as never) 6 | 7 | return class TypeConstructor> extends DynamicBase< 8 | t & object 9 | > { 10 | static infer: type.infer 11 | 12 | constructor(input: unknown) { 13 | const out = Validator(input) 14 | if (out instanceof type.errors) { 15 | return out.throw() 16 | } 17 | super(out as never) 18 | } 19 | 20 | static and( 21 | this: cls, 22 | def: type.validate 23 | ) { 24 | return class extends (this as typeof TypeConstructor< 25 | InstanceType & type.infer 26 | >) { 27 | static infer: cls["infer"] & type.infer 28 | } 29 | } 30 | } 31 | } 32 | 33 | class Foo extends Class({ a: "string|number[]" }) { 34 | getA() { 35 | return this.a 36 | } 37 | } 38 | 39 | const data = new Foo({}) //=> 40 | 41 | const a = data.a //=> 42 | 43 | class Bar extends Foo.and({ b: "boolean" }) { 44 | getB() { 45 | return this.b 46 | } 47 | } 48 | 49 | type Z = typeof Bar.infer //=> 50 | 51 | const data2 = new Bar({}) //=> 52 | 53 | const implemented = data2.getB() //=> 54 | const inherited = data2.getA() //=> 55 | 56 | const a2 = data2.a //=> 57 | const b = data2.b //=> 58 | -------------------------------------------------------------------------------- /ark/repo/scratch/unionComparison.ts: -------------------------------------------------------------------------------- 1 | import { scope, type } from "arktype" 2 | import { z } from "zod" 3 | 4 | const zodUser = z.discriminatedUnion("kind", [ 5 | z.object({ kind: z.literal("admin"), powers: z.string().array().optional() }), 6 | z.object({ 7 | kind: z.literal("superadmin"), 8 | superpowers: z.string().array().optional() 9 | }), 10 | z.object({ kind: z.literal("pleb") }) 11 | ]) 12 | 13 | export const ArkUser = type({ 14 | kind: "'admin'", 15 | "powers?": "string[]" 16 | }) 17 | .or({ 18 | kind: "'superadmin'", 19 | "superpowers?": "string[]" 20 | }) 21 | .or({ 22 | kind: "'pleb'" 23 | }) 24 | -------------------------------------------------------------------------------- /ark/repo/shared.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fileName, 3 | findPackageAncestors, 4 | readJson, 5 | readPackageJson 6 | } from "@ark/fs" 7 | import { flatMorph, throwInternalError } from "@ark/util" 8 | import { join } from "node:path" 9 | import type { PackageJson } from "type-fest" 10 | 11 | // allow other utils invoked from build to bootstrap utils 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 14 | export * as bootstrapFs from "../fs/index.ts" 15 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 16 | export * as bootstrapUtil from "../util/index.ts" 17 | 18 | const root = findPackageAncestors().find( 19 | dir => readPackageJson(dir).name === "ark" 20 | ) 21 | 22 | if (!root) throwInternalError(`Can't find repo root from ${fileName()}!`) 23 | 24 | const arkDir = join(root, "ark") 25 | const docs = join(arkDir, "docs") 26 | 27 | export const repoDirs = { 28 | root, 29 | arkDir, 30 | docs, 31 | repo: join(arkDir, "repo") 32 | } 33 | 34 | export const packageScopes = [ 35 | "attest", 36 | "fs", 37 | "json-schema", 38 | "schema", 39 | "type", 40 | "util" 41 | ] as const 42 | 43 | export type PackageScope = (typeof packageScopes)[number] 44 | 45 | export type ArkPackage = { 46 | scope: PackageScope 47 | path: string 48 | packageJsonPath: string 49 | name: string 50 | version: string 51 | json: PackageJson 52 | } 53 | 54 | export const packagesByScope = flatMorph( 55 | packageScopes, 56 | (i, scope): [PackageScope, ArkPackage] => { 57 | const path = join(arkDir, scope) 58 | const packageJsonPath = join(path, "package.json") 59 | const json = readJson(packageJsonPath) as PackageJson 60 | return [ 61 | scope, 62 | { 63 | scope, 64 | path, 65 | packageJsonPath, 66 | json, 67 | name: json.name!, 68 | version: json.version! 69 | } 70 | ] 71 | } 72 | ) 73 | 74 | export const packages = Object.values(packagesByScope) 75 | -------------------------------------------------------------------------------- /ark/repo/testPackage.ts: -------------------------------------------------------------------------------- 1 | import { fromHere, shell } from "@ark/fs" 2 | 3 | shell( 4 | `pnpm mocha --config ${fromHere("mocha.package.jsonc")} ${process.argv 5 | .slice(2) 6 | .join(" ")}` 7 | ) 8 | -------------------------------------------------------------------------------- /ark/repo/testV8.js: -------------------------------------------------------------------------------- 1 | import { fromHere } from "@ark/fs" 2 | import { type } from "arktype" 3 | 4 | console.log( 5 | "⏱️ Checking for V8 fast properties (https://v8.dev/blog/fast-properties) on Type...\n" 6 | ) 7 | 8 | const T = type({ 9 | name: "string", 10 | age: "number" 11 | }) 12 | 13 | let hasFastProperties 14 | 15 | try { 16 | hasFastProperties = eval("%HasFastProperties(T)") 17 | } catch { 18 | throw new Error(`This test must be run in a V8-based runtime with the --allow-natives-syntax flag, e.g.: 19 | node --allow-natives-syntax ${fromHere()}`) 20 | } 21 | 22 | if (!hasFastProperties) { 23 | throw new Error("⚠️ Type instance has been deoptimized.") 24 | } 25 | 26 | console.log("🏎️ Type instance has fast properties!") 27 | -------------------------------------------------------------------------------- /ark/repo/ts.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // @ts-check 4 | import { execSync } from "node:child_process" 5 | import { addNodeDevOptions } from "./nodeOptions.js" 6 | 7 | addNodeDevOptions() 8 | 9 | execSync(`node ${process.argv.slice(2).join(" ")}`, { stdio: "inherit" }) 10 | -------------------------------------------------------------------------------- /ark/repo/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "verbatimModuleSyntax": false, 7 | "rootDir": ".", 8 | "outDir": "out", 9 | "noEmit": false, 10 | "declaration": false, 11 | "customConditions": [] 12 | }, 13 | "exclude": ["out", "__tests__"] 14 | } 15 | -------------------------------------------------------------------------------- /ark/repo/tsconfig.dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "out", 6 | "noEmit": false, 7 | "emitDeclarationOnly": true, 8 | "customConditions": [] 9 | }, 10 | "exclude": ["out", "__tests__"] 11 | } 12 | -------------------------------------------------------------------------------- /ark/repo/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "out", 6 | "noEmit": false, 7 | "declaration": false, 8 | "noCheck": true, 9 | "customConditions": [] 10 | }, 11 | "exclude": ["out", "__tests__"] 12 | } 13 | -------------------------------------------------------------------------------- /ark/repo/tsconfig.js.json: -------------------------------------------------------------------------------- 1 | ./tsconfig.esm.json -------------------------------------------------------------------------------- /ark/schema/README.md: -------------------------------------------------------------------------------- 1 | # @ark/schema 2 | 3 | Underlying schema language parsed from arktype syntax. 4 | 5 | The parts of ArkType's type system that exist in TS (i.e. not runtime-only constraints like bounds, divisors, custom predicates, morphs etc.) are structured like this: 6 | 7 | - Union: a set of intersections 8 | - Intersection: a set of a basis and constraints 9 | - Basis: this is the base type to which refinements like props are applied. It is one of three things, getting narrower as you move down the list: 10 | - Domain: `"string" | "number" | "bigint" | "object" | "symbol"` parallels built-in TS keywords for non-enumerable value sets 11 | - Proto: Must be an `instanceof` some class (implies domain `"object"`) 12 | - Unit: Must `===` some value (can be intersected with any other constraint and reduced to itself or a disjoint) 13 | - Constraint: an individual condition that must be satisfied: 14 | - Required: must have a specified literal string or symbol key and a value that conforms to a specified union or intersection 15 | - Optional: Required conditions met or the specified key is not present 16 | - Index: all keys that satisfy an index type must have values satisfying the corresponding value type 17 | 18 | In this system, `L` extends/subtypes/is assignable to `R` if and only if the intersection `L & R` is equal to `L`. 19 | -------------------------------------------------------------------------------- /ark/schema/__tests__/intersection.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { rootSchema } from "@ark/schema" 3 | 4 | contextualize(() => { 5 | it("normalizes refinement order", () => { 6 | const L = rootSchema({ 7 | domain: "number", 8 | divisor: 3, 9 | min: 5 10 | }) 11 | const R = rootSchema({ 12 | domain: "number", 13 | min: 5, 14 | divisor: 3 15 | }) 16 | attest(L.json).equals(R.json) 17 | }) 18 | 19 | it("multiple constraints", () => { 20 | const n = rootSchema({ 21 | domain: "number", 22 | divisor: 3, 23 | min: 5 24 | }) 25 | attest(n.allows(6)).snap(true) 26 | attest(n.allows(4)).snap(false) 27 | attest(n.allows(7)).snap(false) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /ark/schema/__tests__/onFail.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { intrinsic } from "@ark/schema" 3 | 4 | contextualize(() => { 5 | it("traverse always returns ArkErrors", () => { 6 | const s = intrinsic.string.configureReferences( 7 | { 8 | onFail: () => "foo" 9 | }, 10 | "self" 11 | ) 12 | attest(s(5)).equals("foo") 13 | attest(s.traverse(5)?.toString()).snap("must be a string (was a number)") 14 | }) 15 | 16 | it("assert always throws ArkErrors", () => { 17 | const n = intrinsic.number.configureReferences( 18 | { 19 | onFail: () => "foo" 20 | }, 21 | "self" 22 | ) 23 | attest(() => n.assert("five")) 24 | }) 25 | 26 | it("passed onFail overrides meta", () => { 27 | const b = intrinsic.bigint.configureReferences( 28 | { 29 | onFail: () => "foo" 30 | }, 31 | "self" 32 | ) 33 | 34 | attest(b(5, undefined, () => "bar")).equals("bar") 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /ark/schema/__tests__/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { rootSchema } from "@ark/schema" 3 | 4 | contextualize(() => { 5 | it("single constraint", () => { 6 | const T = rootSchema({ domain: "string", pattern: ".*" }) 7 | attest(T.json).snap({ domain: "string", pattern: [".*"] }) 8 | }) 9 | 10 | it("multiple constraints", () => { 11 | const L = rootSchema({ 12 | domain: "number", 13 | divisor: 3, 14 | min: 5 15 | }) 16 | const R = rootSchema({ 17 | domain: "number", 18 | divisor: 5 19 | }) 20 | const T = L.and(R) 21 | 22 | attest(T.json).snap({ 23 | domain: "number", 24 | divisor: 15, 25 | min: 5 26 | }) 27 | }) 28 | 29 | it("throws on reduced minLength disjoint", () => { 30 | attest(() => 31 | rootSchema({ 32 | proto: Array, 33 | maxLength: 0, 34 | sequence: { 35 | prefix: ["number"], 36 | variadic: "number" 37 | } 38 | }) 39 | ).throws.snap( 40 | "ParseError: Intersection of == 0 and >= 1 results in an unsatisfiable type" 41 | ) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /ark/schema/__tests__/proto.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { node, rootSchema } from "@ark/schema" 3 | 4 | contextualize(() => { 5 | it("normalizes node schema", () => { 6 | const d = node("proto", Date) 7 | attest(d.json).snap({ proto: "Date" }) 8 | 9 | const reparsed = rootSchema(d) 10 | attest(reparsed.json).equals(d.json) 11 | attest(reparsed.id).equals(d.id) 12 | }) 13 | 14 | it("can parse proto from string", () => { 15 | const T = rootSchema({ 16 | domain: "object", 17 | required: [{ key: "foo", value: "Array" }] 18 | }) 19 | 20 | attest(T).snap({ 21 | required: [{ key: "foo", value: "Array" }], 22 | domain: "object" 23 | }) 24 | 25 | attest(T.get("foo")).snap({ proto: "Array" }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /ark/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./config.ts" 2 | export * from "./constraint.ts" 3 | export * from "./generic.ts" 4 | export * from "./intrinsic.ts" 5 | export * from "./kinds.ts" 6 | export * from "./module.ts" 7 | export * from "./node.ts" 8 | export * from "./parse.ts" 9 | export * from "./predicate.ts" 10 | export * from "./refinements/after.ts" 11 | export * from "./refinements/before.ts" 12 | export * from "./refinements/divisor.ts" 13 | export * from "./refinements/exactLength.ts" 14 | export * from "./refinements/max.ts" 15 | export * from "./refinements/maxLength.ts" 16 | export * from "./refinements/min.ts" 17 | export * from "./refinements/minLength.ts" 18 | export * from "./refinements/pattern.ts" 19 | export * from "./refinements/range.ts" 20 | export * from "./roots/domain.ts" 21 | export * from "./roots/intersection.ts" 22 | export * from "./roots/morph.ts" 23 | export * from "./roots/proto.ts" 24 | export * from "./roots/root.ts" 25 | export * from "./roots/union.ts" 26 | export * from "./roots/unit.ts" 27 | export * from "./scope.ts" 28 | export * from "./shared/compile.ts" 29 | export * from "./shared/declare.ts" 30 | export * from "./shared/disjoint.ts" 31 | export * from "./shared/errors.ts" 32 | export * from "./shared/implement.ts" 33 | export * from "./shared/intersections.ts" 34 | export * from "./shared/jsonSchema.ts" 35 | export * from "./shared/registry.ts" 36 | export * from "./shared/standardSchema.ts" 37 | export * from "./shared/toJsonSchema.ts" 38 | export * from "./shared/traversal.ts" 39 | export * from "./shared/utils.ts" 40 | export * from "./structure/index.ts" 41 | export * from "./structure/optional.ts" 42 | export * from "./structure/prop.ts" 43 | export * from "./structure/required.ts" 44 | export * from "./structure/sequence.ts" 45 | export * from "./structure/structure.ts" 46 | 47 | export { ParseError } from "@ark/util" 48 | -------------------------------------------------------------------------------- /ark/schema/intrinsic.ts: -------------------------------------------------------------------------------- 1 | import { node, schemaScope } from "./scope.ts" 2 | import { $ark } from "./shared/registry.ts" 3 | import { arrayIndexSource } from "./structure/shared.ts" 4 | 5 | const intrinsicBases = schemaScope( 6 | { 7 | bigint: "bigint", 8 | // since we know this won't be reduced, it can be safely cast to a union 9 | boolean: [{ unit: false }, { unit: true }], 10 | false: { unit: false }, 11 | never: [], 12 | null: { unit: null }, 13 | number: "number", 14 | object: "object", 15 | string: "string", 16 | symbol: "symbol", 17 | true: { unit: true }, 18 | unknown: {}, 19 | undefined: { unit: undefined }, 20 | Array, 21 | Date 22 | }, 23 | { prereducedAliases: true } 24 | ).export() 25 | 26 | $ark.intrinsic = { ...intrinsicBases } as never 27 | 28 | const intrinsicRoots = schemaScope( 29 | { 30 | integer: { 31 | domain: "number", 32 | divisor: 1 33 | }, 34 | lengthBoundable: ["string", Array], 35 | key: ["string", "symbol"], 36 | nonNegativeIntegerString: { domain: "string", pattern: arrayIndexSource } 37 | }, 38 | { prereducedAliases: true } 39 | ).export() 40 | 41 | // needed to parse index signatures for JSON 42 | Object.assign($ark.intrinsic, intrinsicRoots) 43 | 44 | const intrinsicJson = schemaScope( 45 | { 46 | jsonPrimitive: [ 47 | "string", 48 | "number", 49 | { unit: true }, 50 | { unit: false }, 51 | { unit: null } 52 | ], 53 | jsonObject: { 54 | domain: "object", 55 | index: { 56 | signature: "string", 57 | value: "$jsonData" 58 | } 59 | }, 60 | jsonData: ["$jsonPrimitive", "$jsonObject"] 61 | }, 62 | { prereducedAliases: true } 63 | ).export() 64 | 65 | export const intrinsic = { 66 | ...intrinsicBases, 67 | ...intrinsicRoots, 68 | ...intrinsicJson, 69 | emptyStructure: node("structure", {}, { prereduced: true }) 70 | } 71 | 72 | $ark.intrinsic = { ...intrinsic } as never 73 | -------------------------------------------------------------------------------- /ark/schema/module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicBase, flatMorph, type anyOrNever } from "@ark/util" 2 | import type { BaseRoot } from "./roots/root.ts" 3 | import type { 4 | BaseScope, 5 | InternalResolution, 6 | InternalResolutions 7 | } from "./scope.ts" 8 | import { arkKind, hasArkKind } from "./shared/utils.ts" 9 | 10 | export type PreparsedNodeResolution = { 11 | [arkKind]: "generic" | "module" 12 | } 13 | 14 | export class RootModule extends DynamicBase { 15 | // ensure `[arkKind]` is non-enumerable so it doesn't get spread on import/export 16 | get [arkKind](): "module" { 17 | return "module" 18 | } 19 | } 20 | 21 | export interface InternalModule< 22 | exports extends InternalResolutions = InternalResolutions 23 | > extends RootModule { 24 | root?: BaseRoot 25 | } 26 | 27 | export const bindModule = ( 28 | module: InternalModule, 29 | $: BaseScope 30 | ): InternalModule => 31 | new RootModule( 32 | flatMorph(module, (alias, value) => [ 33 | alias, 34 | hasArkKind(value, "module") ? 35 | bindModule(value, $) 36 | : $.bindReference(value) 37 | ]) 38 | ) as never 39 | 40 | type exportSchemaScope<$> = { 41 | [k in keyof $]: instantiateRoot<$[k]> 42 | } 43 | 44 | export type instantiateRoot = 45 | t extends InternalResolution ? 46 | [t] extends [anyOrNever] ? 47 | BaseRoot 48 | : t 49 | : BaseRoot 50 | 51 | export const SchemaModule: new <$ = {}>( 52 | types: exportSchemaScope<$> 53 | ) => SchemaModule<$> = RootModule 54 | 55 | export interface SchemaModule<$ = {}> 56 | extends RootModule> {} 57 | -------------------------------------------------------------------------------- /ark/schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/schema", 3 | "version": "0.46.0", 4 | "license": "MIT", 5 | "author": { 6 | "name": "David Blass", 7 | "email": "david@arktype.io", 8 | "url": "https://arktype.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/arktypeio/arktype.git", 13 | "directory": "ark/schema" 14 | }, 15 | "type": "module", 16 | "main": "./out/index.js", 17 | "types": "./out/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "ark-ts": "./index.ts", 21 | "default": "./out/index.js" 22 | }, 23 | "./config": { 24 | "ark-ts": "./config.ts", 25 | "default": "./out/config.js" 26 | } 27 | }, 28 | "files": [ 29 | "out" 30 | ], 31 | "scripts": { 32 | "build": "ts ../repo/build.ts", 33 | "bench": "ts ./__tests__/comparison.bench.ts", 34 | "test": "ts ../repo/testPackage.ts", 35 | "tnt": "ts ../repo/testPackage.ts --skipTypes" 36 | }, 37 | "dependencies": { 38 | "@ark/util": "workspace:*" 39 | }, 40 | "publishConfig": { 41 | "access": "public" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ark/schema/refinements/kinds.ts: -------------------------------------------------------------------------------- 1 | import type { BaseConstraint } from "../constraint.ts" 2 | import type { BoundKind, nodeImplementationOf } from "../shared/implement.ts" 3 | import { After } from "./after.ts" 4 | import { Before } from "./before.ts" 5 | import { ExactLength } from "./exactLength.ts" 6 | import { Max } from "./max.ts" 7 | import { MaxLength } from "./maxLength.ts" 8 | import { Min } from "./min.ts" 9 | import { MinLength } from "./minLength.ts" 10 | 11 | export interface BoundDeclarations { 12 | min: Min.Declaration 13 | max: Max.Declaration 14 | minLength: MinLength.Declaration 15 | maxLength: MaxLength.Declaration 16 | exactLength: ExactLength.Declaration 17 | after: After.Declaration 18 | before: Before.Declaration 19 | } 20 | 21 | export interface BoundNodesByKind { 22 | min: Min.Node 23 | max: Max.Node 24 | minLength: MinLength.Node 25 | maxLength: MaxLength.Node 26 | exactLength: ExactLength.Node 27 | after: After.Node 28 | before: Before.Node 29 | } 30 | 31 | export type boundImplementationsByKind = { 32 | [k in BoundKind]: nodeImplementationOf 33 | } 34 | 35 | export const boundImplementationsByKind: boundImplementationsByKind = { 36 | min: Min.implementation, 37 | max: Max.implementation, 38 | minLength: MinLength.implementation, 39 | maxLength: MaxLength.implementation, 40 | exactLength: ExactLength.implementation, 41 | after: After.implementation, 42 | before: Before.implementation 43 | } 44 | 45 | export const boundClassesByKind: Record> = 46 | { 47 | min: Min.Node, 48 | max: Max.Node, 49 | minLength: MinLength.Node, 50 | maxLength: MaxLength.Node, 51 | exactLength: ExactLength.Node, 52 | after: After.Node, 53 | before: Before.Node 54 | } 55 | -------------------------------------------------------------------------------- /ark/schema/roots/basis.ts: -------------------------------------------------------------------------------- 1 | import type { NodeCompiler } from "../shared/compile.ts" 2 | import { compileObjectLiteral } from "../shared/implement.ts" 3 | import type { TraverseApply } from "../shared/traversal.ts" 4 | import { BaseRoot, type InternalRootDeclaration } from "./root.ts" 5 | 6 | export abstract class InternalBasis< 7 | d extends InternalRootDeclaration = InternalRootDeclaration 8 | > extends BaseRoot { 9 | abstract compiledCondition: string 10 | abstract compiledNegation: string 11 | declare structure: undefined 12 | 13 | traverseApply: TraverseApply = (data, ctx) => { 14 | if (!this.traverseAllows(data, ctx)) 15 | ctx.errorFromNodeContext(this.errorContext as never) 16 | } 17 | 18 | get errorContext(): d["errorContext"] { 19 | return { 20 | code: this.kind, 21 | description: this.description, 22 | meta: this.meta, 23 | ...this.inner 24 | } 25 | } 26 | 27 | get compiledErrorContext(): string { 28 | return compileObjectLiteral(this.errorContext!) 29 | } 30 | 31 | compile(js: NodeCompiler): void { 32 | if (js.traversalKind === "Allows") js.return(this.compiledCondition) 33 | else { 34 | js.if(this.compiledNegation, () => 35 | js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`) 36 | ) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ark/schema/roots/utils.ts: -------------------------------------------------------------------------------- 1 | import { flatMorph } from "@ark/util" 2 | import { 3 | schemaKindsRightOf, 4 | type RootIntersection, 5 | type RootKind 6 | } from "../shared/implement.ts" 7 | import type { schemaKindRightOf } from "./root.ts" 8 | 9 | export const defineRightwardIntersections = ( 10 | kind: kind, 11 | implementation: RootIntersection> 12 | ): { [k in schemaKindRightOf]: RootIntersection } => 13 | flatMorph(schemaKindsRightOf(kind), (i, kind) => [ 14 | kind, 15 | implementation 16 | ]) as never 17 | -------------------------------------------------------------------------------- /ark/schema/shared/registry.ts: -------------------------------------------------------------------------------- 1 | import { register, registry, type NonNegativeIntegerLiteral } from "@ark/util" 2 | import type { ArkSchemaRegistry } from "../config.ts" 3 | 4 | let _registryName = "$ark" 5 | let suffix = 2 6 | 7 | while (_registryName in globalThis) _registryName = `$ark${suffix++}` 8 | 9 | export const registryName = _registryName 10 | ;(globalThis as any)[registryName] = registry 11 | 12 | export const $ark: ArkSchemaRegistry = registry as never 13 | 14 | export const reference = (name: string): RegisteredReference => 15 | `${registryName}.${name}` as never 16 | 17 | export const registeredReference = ( 18 | value: object | symbol 19 | ): RegisteredReference => reference(register(value)) 20 | 21 | export type RegisteredReference = 22 | `$ark${"" | NonNegativeIntegerLiteral}.${to}` 23 | -------------------------------------------------------------------------------- /ark/schema/structure/shared.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registeredReference, 3 | type RegisteredReference 4 | } from "../shared/registry.ts" 5 | 6 | export const arrayIndexSource = `^(?:0|[1-9]\\d*)$` 7 | 8 | export const arrayIndexMatcher = new RegExp(arrayIndexSource) 9 | 10 | export const arrayIndexMatcherReference: RegisteredReference = 11 | registeredReference(arrayIndexMatcher) 12 | -------------------------------------------------------------------------------- /ark/themes/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /ark/themes/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

ArkThemes

4 |
5 | 6 |
7 | 8 | A collection of themes with special highlighting for TypeScript generics ⛵ 9 | 10 | Created with love by the [ArkType](https://arktype.io) team 🥰 11 | 12 |
13 | 14 | ### Dark 15 | 16 | ![theme](https://raw.githubusercontent.com/arktypeio/arktype/refs/heads/main/ark/themes/dark.png) 17 | 18 | ### Light 19 | 20 | ![theme](https://raw.githubusercontent.com/arktypeio/arktype/refs/heads/main/ark/themes/light.png) 21 | -------------------------------------------------------------------------------- /ark/themes/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/themes/dark.png -------------------------------------------------------------------------------- /ark/themes/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/themes/icon.png -------------------------------------------------------------------------------- /ark/themes/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arktypeio/arktype/049bc4dcede23a620cd0fc43176ce7b5aaaf2f48/ark/themes/light.png -------------------------------------------------------------------------------- /ark/themes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arkthemes", 3 | "displayName": "ArkThemes", 4 | "description": "A collection of themes with special highlighting for TypeScript generics ⛵", 5 | "version": "6.1.0", 6 | "publisher": "arktypeio", 7 | "type": "module", 8 | "license": "MIT", 9 | "scripts": { 10 | "publishExtension": "pnpm packageExtension && pnpm publishVsce && pnpm publishOvsx", 11 | "packageExtension": "vsce package --out arkthemes.vsix", 12 | "publishVsce": "vsce publish -i arkthemes.vsix", 13 | "publishOvsx": "ovsx publish -i arkthemes.vsix" 14 | }, 15 | "devDependencies": { 16 | "@vscode/vsce": "3.3.2", 17 | "ovsx": "0.10.1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/arktypeio/arktype" 22 | }, 23 | "engines": { 24 | "vscode": "^1.0.0" 25 | }, 26 | "categories": [ 27 | "Themes" 28 | ], 29 | "icon": "icon.png", 30 | "exports": { 31 | "./*.json": "./*.json" 32 | }, 33 | "contributes": { 34 | "themes": [ 35 | { 36 | "label": "ArkDark", 37 | "uiTheme": "vs-dark", 38 | "path": "./arkdark.json" 39 | }, 40 | { 41 | "label": "ArkDark (italic)", 42 | "uiTheme": "vs-dark", 43 | "path": "./arkdarkItalic.json" 44 | }, 45 | { 46 | "label": "ArkLight", 47 | "uiTheme": "vs", 48 | "path": "./arklight.json" 49 | }, 50 | { 51 | "label": "ArkLight (italic)", 52 | "uiTheme": "vs", 53 | "path": "./arklightItalic.json" 54 | } 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ark/type/__tests__/badDefinitionType.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import { writeBadDefinitionTypeMessage } from "arktype/internal/parser/definition.ts" 4 | 5 | contextualize(() => { 6 | it("undefined", () => { 7 | // @ts-expect-error 8 | attest(() => type({ bad: undefined })).throwsAndHasTypeError( 9 | writeBadDefinitionTypeMessage("undefined") 10 | ) 11 | }) 12 | 13 | it("null", () => { 14 | // @ts-expect-error 15 | attest(() => type({ bad: null })).throwsAndHasTypeError( 16 | writeBadDefinitionTypeMessage("null") 17 | ) 18 | }) 19 | 20 | it("boolean", () => { 21 | // @ts-expect-error 22 | attest(() => type({ bad: true })).throwsAndHasTypeError( 23 | writeBadDefinitionTypeMessage("boolean") 24 | ) 25 | }) 26 | 27 | it("number", () => { 28 | // @ts-expect-error 29 | attest(() => type({ bad: 5 })).throwsAndHasTypeError( 30 | writeBadDefinitionTypeMessage("number") 31 | ) 32 | }) 33 | 34 | it("bigint", () => { 35 | // @ts-expect-error 36 | attest(() => type({ bad: 99999n })).throwsAndHasTypeError( 37 | writeBadDefinitionTypeMessage("bigint") 38 | ) 39 | }) 40 | 41 | it("symbol", () => { 42 | // @ts-expect-error 43 | attest(() => type({ bad: Symbol() })).throwsAndHasTypeError( 44 | writeBadDefinitionTypeMessage("symbol") 45 | ) 46 | }) 47 | 48 | it("any", () => { 49 | // doesn't error, so this test is just to ensure it doesn't infinitely recurse 50 | const T = type({ bad: {} as any }) 51 | attest<{ bad: any }>(T.infer) 52 | }) 53 | 54 | it("never", () => { 55 | // can't error 56 | const T = type({ bad: {} as never }) 57 | attest<{ bad: never }>(T.infer) 58 | }) 59 | 60 | it("unknown", () => { 61 | // @ts-expect-error just results in base completions, so we just check there's an error 62 | attest(() => type({ bad: {} as unknown })).type.errors("") 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /ark/type/__tests__/basis.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { writeUnsatisfiableExpressionError } from "@ark/schema" 3 | import { type } from "arktype" 4 | 5 | contextualize(() => { 6 | describe("intersections", () => { 7 | it("class & literal", () => { 8 | const a = [0] 9 | const Literal = type("===", a) 10 | const Cls = type("instanceof", Array) 11 | const lr = Literal.and(Cls) 12 | attest(lr.infer) 13 | attest(lr.json).equals(Literal.json) 14 | const rl = Cls.and(Literal) 15 | attest(rl.infer) 16 | attest(rl.json).equals(Literal.json) 17 | }) 18 | 19 | it("unsatisfiable class & literal", () => { 20 | const a = [0] 21 | const Literal = type("===", a) 22 | const Cls = type("instanceof", Date) 23 | attest(() => Literal.and(Cls)).throws( 24 | writeUnsatisfiableExpressionError("") 25 | ) 26 | attest(() => Cls.and(Literal)).throws( 27 | writeUnsatisfiableExpressionError("") 28 | ) 29 | }) 30 | 31 | it("domain & literal", () => { 32 | const Literal = type("'foo'") 33 | const Domain = type("string") 34 | attest(Literal.and(Domain).json).equals(Literal.json) 35 | attest(Domain.and(Literal).json).equals(Literal.json) 36 | }) 37 | 38 | it("unsatisfiable domain & literal", () => { 39 | const Literal = type("'foo'") 40 | const Domain = type("number") 41 | attest(() => Literal.and(Domain)).throws( 42 | writeUnsatisfiableExpressionError("") 43 | ) 44 | attest(() => Domain.and(Literal)).throws( 45 | writeUnsatisfiableExpressionError("") 46 | ) 47 | }) 48 | 49 | it("domain & class", () => { 50 | const Domain = type("object") 51 | const Cls = type("instanceof", Date) 52 | attest(Domain.and(Cls).json).equals(Cls.json) 53 | attest(Cls.and(Domain).json).equals(Cls.json) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /ark/type/__tests__/completions.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("completes standalone keyword", () => { 6 | // @ts-expect-error 7 | attest(() => type("s")).completions({ s: ["string", "symbol"] }) 8 | }) 9 | 10 | it("completes within objects", () => { 11 | // @ts-expect-error 12 | attest(() => type({ b: "b" })).completions({ 13 | b: ["bigint", "boolean"] 14 | }) 15 | }) 16 | 17 | it("completes within expressions", () => { 18 | // @ts-expect-error 19 | attest(() => type("string|n")).completions({ 20 | "string|n": ["string|never", "string|null", "string|number"] 21 | }) 22 | }) 23 | 24 | it("completes within expressions in objects", () => { 25 | // @ts-expect-error 26 | attest(() => type({ key: "number | b" })).completions({ 27 | "number | b": ["number | bigint", "number | boolean"] 28 | }) 29 | }) 30 | 31 | it("completes user-defined aliases", () => { 32 | const $ = scope({ 33 | over9000: "number>9000", 34 | myArk: { 35 | kind: "'type'", 36 | buoyancy: "null", 37 | dxLevel: "over9000" 38 | }, 39 | noahsArk: { 40 | kind: "'boat'", 41 | buoyancy: "over9000", 42 | dxLevel: "null" 43 | } 44 | }) 45 | // this could also have been defined with completions directly in scope! 46 | attest(() => 47 | $.type({ 48 | // @ts-expect-error 49 | type: "my", 50 | // @ts-expect-error 51 | boat: "noah" 52 | }) 53 | ).completions({ my: ["myArk"], noah: ["noahsArk"] }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /ark/type/__tests__/cyclic.bench.ts: -------------------------------------------------------------------------------- 1 | import { bench } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | import { cyclic10, cyclic100, cyclic500 } from "./generated/cyclic.ts" 4 | 5 | bench.baseline(() => type("never")) 6 | 7 | bench( 8 | "cyclic 10 intersection", 9 | () => scope(cyclic10).type("user&user2").infer 10 | ).types([42547, "instantiations"]) 11 | 12 | bench("cyclic(10)", () => scope(cyclic10).export()).types([ 13 | 8719, 14 | "instantiations" 15 | ]) 16 | 17 | bench("cyclic(100)", () => scope(cyclic100).export()).types([ 18 | 61567, 19 | "instantiations" 20 | ]) 21 | 22 | bench("cyclic(500)", () => scope(cyclic500).export()).types([ 23 | 291065, 24 | "instantiations" 25 | ]) 26 | -------------------------------------------------------------------------------- /ark/type/__tests__/dateLiteral.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import { writeInvalidDateMessage } from "arktype/internal/parser/shift/operand/date.ts" 4 | 5 | contextualize(() => { 6 | it("base", () => { 7 | const T = type("d'2000/05/05'") 8 | attest(T.infer) 9 | attest(T.allows(new Date("2000/05/05"))).equals(true) 10 | attest(T.allows(new Date("2000/06/05"))).equals(false) 11 | attest(T.allows(new Date("2000-05-05T09:00:00.000Z"))).equals(false) 12 | }) 13 | 14 | it("with punctuation", () => { 15 | const ISO = type("d'2000-05-05T04:00:00.000Z'") 16 | attest(ISO.infer) 17 | attest(ISO.allows(new Date("2000/05/05"))).equals(true) 18 | attest(ISO.allows(new Date("2000/07/05"))).equals(false) 19 | }) 20 | 21 | it("allows spaces", () => { 22 | const T = type("d' 2021 / 05 / 01 '") 23 | attest(T.allows(new Date("2021/05/01"))).equals(true) 24 | }) 25 | 26 | it("epoch", () => { 27 | const now = new Date() 28 | const T = type(`d'${now.valueOf()}'`) 29 | attest(T.allows(now)).equals(true) 30 | attest(T.allows(new Date(now.valueOf() + 1))).equals(false) 31 | }) 32 | 33 | it("invalid date", () => { 34 | attest(() => type("d'tuesday'")).throws(writeInvalidDateMessage("tuesday")) 35 | }) 36 | 37 | it("morphable", () => { 38 | const T = type(["Date", "=>", d => d.toISOString()]) 39 | attest(T.from(new Date(2000, 1))).snap("2000-02-01T05:00:00.000Z") 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /ark/type/__tests__/define.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { writeUnresolvableMessage } from "@ark/schema" 3 | import { define, scope, type } from "arktype" 4 | 5 | contextualize(() => { 6 | it("ark", () => { 7 | const def = define({ 8 | a: "string|number", 9 | b: ["boolean"] 10 | }) 11 | attest<{ a: "string|number"; b: readonly ["boolean"] }>(def) 12 | }) 13 | 14 | it("type attached", () => { 15 | const T = type.define({ 16 | foo: "string" 17 | }) 18 | 19 | attest<{ readonly foo: "string" }>(T).equals({ foo: "string" }) 20 | 21 | // @ts-expect-error 22 | attest(() => type.define({ foo: "str" })).completions({ str: ["string"] }) 23 | }) 24 | 25 | it("ark error", () => { 26 | // currently is a no-op, so only has type error 27 | // @ts-expect-error 28 | attest(() => define({ a: "boolean|foo" })).type.errors( 29 | writeUnresolvableMessage("foo") 30 | ) 31 | }) 32 | 33 | it("custom scope", () => { 34 | const $ = scope({ 35 | a: "string[]" 36 | }) 37 | 38 | const ok = $.define(["a[]|boolean"]) 39 | attest(ok) 40 | 41 | // @ts-expect-error 42 | attest(() => $.define({ not: "ok" })).type.errors( 43 | writeUnresolvableMessage("ok") 44 | ) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /ark/type/__tests__/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import type { Out } from "arktype/internal/attributes.ts" 4 | 5 | contextualize(() => { 6 | const parseNumber = (s: string) => Number(s) 7 | 8 | it("applies to input", () => { 9 | const stringIsLong = (s: string) => s.length > 5 10 | const ParseLongNumber = type("string") 11 | .pipe(parseNumber) 12 | .filter(stringIsLong) 13 | 14 | attest<(In: string) => Out>(ParseLongNumber.t) 15 | 16 | attest(ParseLongNumber.json).snap({ 17 | in: { domain: "string", predicate: ["$ark.stringIsLong"] }, 18 | morphs: ["$ark.parseNumber"] 19 | }) 20 | 21 | attest(ParseLongNumber("123456")).snap(123456) 22 | attest(ParseLongNumber("123").toString()).snap( 23 | 'must be valid according to stringIsLong (was "123")' 24 | ) 25 | attest(ParseLongNumber(123456).toString()).snap( 26 | "must be a string (was a number)" 27 | ) 28 | }) 29 | 30 | it("predicate inferred on input", () => { 31 | const stringIsIntegerLike = (s: string): s is `${bigint}` => 32 | /^-?\d+$/.test(s) 33 | const ParseIntegerLike = type("string") 34 | .pipe(parseNumber) 35 | .filter(stringIsIntegerLike) 36 | 37 | attest<(In: `${bigint}`) => Out>(ParseIntegerLike.t) 38 | 39 | attest(ParseIntegerLike.json).snap({ 40 | in: { domain: "string", predicate: ["$ark.stringIsIntegerLike"] }, 41 | morphs: ["$ark.parseNumber"] 42 | }) 43 | 44 | attest(ParseIntegerLike("123456")).snap(123456) 45 | attest(ParseIntegerLike("3.14159").toString()).snap( 46 | 'must be valid according to stringIsIntegerLike (was "3.14159")' 47 | ) 48 | attest(ParseIntegerLike(123456).toString()).snap( 49 | "must be a string (was a number)" 50 | ) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/eoptConfig.ts: -------------------------------------------------------------------------------- 1 | import { configure } from "arktype/config" 2 | 3 | export const config = configure({ 4 | exactOptionalPropertyTypes: false 5 | }) 6 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/generateAllConfig.ts: -------------------------------------------------------------------------------- 1 | import { fromHere, writeFile } from "@ark/fs" 2 | import { hasArkKind } from "@ark/schema" 3 | import { flatMorph } from "@ark/util" 4 | import { ark } from "arktype" 5 | import type { ArkConfig } from "arktype/config" 6 | 7 | const config: ArkConfig = { 8 | keywords: flatMorph(ark.internal.resolutions, (qualifiedName, resolution) => { 9 | if (!resolution || typeof resolution === "string") return [] 10 | if (hasArkKind(resolution, "generic")) return [] 11 | if (qualifiedName.endsWith(".root")) return [] 12 | return [qualifiedName, { description: "configured" }] 13 | }) 14 | } 15 | 16 | writeFile( 17 | fromHere("allConfig.ts"), 18 | `import { configure } from "arktype/config" 19 | 20 | configure(${JSON.stringify(config, null, 4)}) 21 | ` 22 | ) 23 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/onFailConfig.ts: -------------------------------------------------------------------------------- 1 | import { configure } from "arktype/config" 2 | 3 | export const config = configure({ 4 | onFail: errors => errors.throw() 5 | }) 6 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/simpleConfig.ts: -------------------------------------------------------------------------------- 1 | import { configure } from "arktype/config" 2 | 3 | export const config = configure({ 4 | numberAllowsNaN: true, 5 | keywords: { 6 | null: { 7 | description: "configured null" 8 | }, 9 | string: { 10 | description: "a configured string" 11 | }, 12 | "string.numeric": { 13 | description: "a configured numeric string" 14 | }, 15 | "string.trim.preformatted": { 16 | description: "a configured trimmed string" 17 | } 18 | }, 19 | onUndeclaredKey: "delete" 20 | }) 21 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/testAllConfig.ts: -------------------------------------------------------------------------------- 1 | import "./allConfig.ts" 2 | 3 | import { hasArkKind } from "@ark/schema" 4 | import { flatMorph } from "@ark/util" 5 | import { ark } from "arktype" 6 | import { AssertionError } from "node:assert" 7 | import { cases } from "./util.ts" 8 | 9 | cases({ 10 | allResolutionsHaveMatchingQualifiedName: () => { 11 | const mismatches = flatMorph( 12 | ark.internal.resolutions, 13 | (qualifiedName, resolution, i: number) => { 14 | if (!resolution || typeof resolution === "string") return [] 15 | if (hasArkKind(resolution, "generic")) return [] 16 | if (qualifiedName.endsWith(".root")) return [] 17 | if (resolution.description !== "configured") return [i, qualifiedName] 18 | return [] 19 | } 20 | ) 21 | 22 | if (mismatches.length) { 23 | throw new AssertionError({ 24 | message: `The following resolutions had mismatching qualifiedNames:\n${mismatches.join("\n")}` 25 | }) 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/testEoptConfig.ts: -------------------------------------------------------------------------------- 1 | import "./eoptConfig.ts" 2 | 3 | import { type } from "arktype" 4 | import { equal } from "node:assert/strict" 5 | import { cases } from "./util.ts" 6 | 7 | cases({ 8 | fromDef: () => { 9 | const O = type({ 10 | "name?": "string" 11 | }) 12 | 13 | equal(O.expression, "{ name?: string | undefined }") 14 | }, 15 | fromRef: () => { 16 | const O = type({ 17 | "name?": type.string 18 | }) 19 | 20 | equal(O.expression, "{ name?: string | undefined }") 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/testOnFailConfig.ts: -------------------------------------------------------------------------------- 1 | import "./onFailConfig.ts" 2 | 3 | import { type } from "arktype" 4 | import { throws } from "node:assert/strict" 5 | import { cases } from "./util.ts" 6 | 7 | cases({ 8 | throwsOnFail: () => { 9 | throws(() => { 10 | type.string(5) 11 | }, "must be a string (was number)") 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/testSimpleConfig.ts: -------------------------------------------------------------------------------- 1 | import { config } from "./simpleConfig.ts" 2 | 3 | import { type } from "arktype" 4 | import { deepStrictEqual, strictEqual } from "node:assert/strict" 5 | import { cases } from "./util.ts" 6 | 7 | cases({ 8 | NaN: () => { 9 | strictEqual(type("number").allows(Number.NaN), true) 10 | }, 11 | onUndeclaredKey: () => { 12 | const O = type({ a: "number" }) 13 | const out = O.assert({ a: 1, b: 2 }) 14 | deepStrictEqual(out, { a: 1 }) 15 | }, 16 | shallowLeafKeyword: () => { 17 | const expected = config.keywords.null.description 18 | strictEqual(type.null.description, expected) 19 | strictEqual( 20 | type.null(undefined)?.toString(), 21 | `must be ${expected} (was undefined)` 22 | ) 23 | }, 24 | shallowModuleKeyword: () => { 25 | const expected = config.keywords.string.description 26 | strictEqual(type.string.description, expected) 27 | strictEqual(type.string(5).toString(), `must be ${expected} (was a number)`) 28 | }, 29 | deepModuleKeyword: () => { 30 | const numeric = type.keywords.string.numeric.root 31 | const expected = config.keywords["string.numeric"].description 32 | strictEqual(numeric.description, expected) 33 | strictEqual(numeric("abc").toString(), `must be ${expected} (was "abc")`) 34 | }, 35 | deepLeafKeyword: () => { 36 | const pretrimmed = type.keywords.string.trim.preformatted 37 | const expected = config.keywords["string.trim.preformatted"].description 38 | strictEqual(pretrimmed.description, expected) 39 | strictEqual(pretrimmed(" ").toString(), `must be ${expected} (was " ")`) 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /ark/type/__tests__/integration/util.ts: -------------------------------------------------------------------------------- 1 | import { keysOf } from "@ark/util" 2 | import { stdout } from "node:process" 3 | 4 | export const cases = (cases: Record unknown>) => { 5 | let failed = 0 6 | 7 | for (const name of keysOf(cases)) { 8 | stdout.write(name) 9 | try { 10 | cases[name]() 11 | stdout.write("✅\n") 12 | } catch (e) { 13 | stdout.write("❌\n") 14 | console.group() 15 | console.error(String(e)) 16 | console.groupEnd() 17 | failed = 1 18 | } 19 | } 20 | 21 | if (failed) process.exit(failed) 22 | } 23 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/date.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("string.date", () => { 6 | const DateString = type("string.date") 7 | attest(DateString("2023-01-01")).equals("2023-01-01") 8 | attest(DateString("foo").toString()).snap( 9 | 'must be a parsable date (was "foo")' 10 | ) 11 | attest(DateString(new Date()).toString()).snap( 12 | "must be a string (was an object)" 13 | ) 14 | }) 15 | 16 | it("string.date.parse", () => { 17 | const parseDate = type("string.date.parse") 18 | attest(parseDate("5/21/1993").toString()).snap( 19 | "Fri May 21 1993 00:00:00 GMT-0400 (Eastern Daylight Time)" 20 | ) 21 | attest(parseDate("foo").toString()).snap( 22 | 'must be a parsable date (was "foo")' 23 | ) 24 | attest(parseDate(5).toString()).snap("must be a string (was a number)") 25 | }) 26 | 27 | it("string.date.iso", () => { 28 | const IsoDate = type("string.date.iso") 29 | const d = new Date().toISOString() 30 | attest(IsoDate(d)).equals(d) 31 | attest(IsoDate("05-21-1993").toString()).snap( 32 | 'must be an ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ) date (was "05-21-1993")' 33 | ) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/exclude.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("parsed", () => { 6 | const types = scope({ 7 | from: "0 | 1", 8 | actual: "Exclude", 9 | expected: "0" 10 | }).export() 11 | 12 | attest(types.actual.t) 13 | attest(types.actual.expression).equals(types.expected.expression) 14 | }) 15 | 16 | it("chained", () => { 17 | const Excluded = type("true | 0 | 'foo'").exclude("string") 18 | 19 | const Expected = type("true | 0") 20 | 21 | attest(Excluded.t) 22 | 23 | attest(Excluded.expression).equals(Expected.expression) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/extract.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("parsed", () => { 6 | const types = scope({ 7 | from: "0 | 1", 8 | actual: "Extract", 9 | expected: "1" 10 | }).export() 11 | 12 | attest(types.actual.t) 13 | attest(types.actual.expression).equals(types.expected.expression) 14 | }) 15 | 16 | it("chained", () => { 17 | const Extracted = type("true | 0 | 'foo'").extract("boolean | number") 18 | 19 | const Expected = type("true | 0") 20 | 21 | attest(Extracted.t) 22 | 23 | attest(Extracted.expression).equals(Expected.expression) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/formData.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { registry } from "@ark/util" 3 | import { type } from "arktype" 4 | 5 | contextualize(() => { 6 | it("formData", () => { 7 | const User = type({ 8 | email: "string.email", 9 | file: "File", 10 | tags: "Array.liftFrom" 11 | }) 12 | 13 | const parseUserForm = type("FormData.parse").pipe(User) 14 | 15 | attest(parseUserForm).type.toString.snap(`Type< 16 | ( 17 | In: FormData 18 | ) => To<{ 19 | email: string 20 | file: File 21 | tags: (In: string | string[]) => To 22 | }>, 23 | {} 24 | >`) 25 | 26 | const data = new FormData() 27 | 28 | // Node18 doesn't have a File constructor 29 | if (process.version.startsWith("v18")) return 30 | 31 | const file = new registry.FileConstructor([], "") 32 | 33 | data.append("email", "david@arktype.io") 34 | data.append("file", file) 35 | data.append("tags", "typescript") 36 | data.append("tags", "arktype") 37 | 38 | const out = parseUserForm(data) 39 | attest(out).equals({ 40 | email: "david@arktype.io", 41 | file, 42 | tags: ["typescript", "arktype"] 43 | }) 44 | 45 | data.set("email", "david") 46 | data.set("file", null) 47 | data.append("tags", file) 48 | 49 | attest(parseUserForm(data).toString()) 50 | .snap(`email must be an email address (was "david") 51 | file must be a File instance (was string) 52 | tags[2] must be a string (was an object)`) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/ip.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { keywords, type } from "arktype" 3 | 4 | const validIPv4 = "192.168.1.1" 5 | const validIPv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 6 | 7 | contextualize(() => { 8 | it("root", () => { 9 | const Ip = type("string.ip") 10 | 11 | attest(Ip(validIPv4)).equals(validIPv4) 12 | 13 | attest(Ip(validIPv6)).equals(validIPv6) 14 | 15 | attest(Ip("192.168.1.256").toString()).snap( 16 | 'must be an IP address (was "192.168.1.256")' 17 | ) 18 | attest(Ip("2001:0db8:85a3:0000:0000:8a2e:0370:733g").toString()).snap( 19 | 'must be an IP address (was "2001:0db8:85a3:0000:0000:8a2e:0370:733g")' 20 | ) 21 | }) 22 | 23 | it("version subtype", () => { 24 | const Uuidv4 = type("string.ip.v4") 25 | 26 | attest(Uuidv4(validIPv4)).equals(validIPv4) 27 | attest(Uuidv4("1234").toString()).snap( 28 | 'must be an IPv4 address (was "1234")' 29 | ) 30 | 31 | attest(keywords.string.ip.v6(validIPv6)).equals(validIPv6) 32 | 33 | attest(Uuidv4(validIPv6).toString()).snap( 34 | 'must be an IPv4 address (was "2001:0db8:85a3:0000:0000:8a2e:0370:7334")' 35 | ) 36 | 37 | attest(keywords.string.ip.v6(validIPv4).toString()).snap( 38 | 'must be an IPv6 address (was "192.168.1.1")' 39 | ) 40 | }) 41 | 42 | it("invalid ipv6 with empty segments", () => { 43 | const out = type.keywords.string.ip.v6("::%8:.-:.:") 44 | attest(out.toString()).snap('must be an IPv6 address (was "::%8:.-:.:")') 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/json.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import { writeJsonSyntaxErrorProblem } from "arktype/internal/keywords/string.ts" 4 | 5 | contextualize(() => { 6 | let syntaxError: unknown 7 | 8 | try { 9 | JSON.parse("{") 10 | } catch (e) { 11 | syntaxError = e 12 | } 13 | 14 | // this error varies between Node versions, so easiest to compare it this way 15 | const expectedSyntaxErrorProblem = writeJsonSyntaxErrorProblem(syntaxError) 16 | 17 | it("string.json", () => { 18 | const parseJson = type("string.json") 19 | attest(parseJson('{"a": "hello"}')).snap('{"a": "hello"}') 20 | attest(parseJson(123).toString()).snap("must be a string (was a number)") 21 | 22 | attest(parseJson("{").toString()).equals(expectedSyntaxErrorProblem) 23 | }) 24 | 25 | it("string.json.parse", () => { 26 | const parseJson = type("string.json.parse") 27 | 28 | attest(parseJson('{"a": "hello"}')).snap({ a: "hello" }) 29 | attest(parseJson(123)?.toString()).snap("must be a string (was a number)") 30 | 31 | attest(parseJson("{")?.toString()).equals(expectedSyntaxErrorProblem) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/omit.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("parsed", () => { 6 | const types = scope({ 7 | from: { 8 | foo: "1", 9 | "bar?": "1", 10 | baz: "1", 11 | "quux?": "1" 12 | }, 13 | actual: "Omit", 14 | expected: { 15 | baz: "1", 16 | "quux?": "1" 17 | } 18 | }).export() 19 | 20 | attest(types.actual.t) 21 | attest(types.actual.expression).equals(types.expected.expression) 22 | }) 23 | 24 | it("chained", () => { 25 | const User = type({ 26 | name: "string", 27 | "age?": "number", 28 | isAdmin: "boolean", 29 | "isActive?": "boolean" 30 | }) 31 | 32 | const extras = User.omit("name", "age") 33 | 34 | const Expected = type({ 35 | isAdmin: "boolean", 36 | "isActive?": "boolean" 37 | }) 38 | 39 | attest(extras.t) 40 | 41 | attest(extras.expression).equals(Expected.expression) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("number", () => { 6 | const parseNum = type("string.numeric.parse") 7 | attest(parseNum("5")).equals(5) 8 | attest(parseNum(".5")).equals(0.5) 9 | attest(parseNum("5.5")).equals(5.5) 10 | attest(parseNum("five").toString()).snap( 11 | 'must be a well-formed numeric string (was "five")' 12 | ) 13 | }) 14 | 15 | it("integer", () => { 16 | const parseInt = type("string.integer.parse") 17 | attest(parseInt("5")).equals(5) 18 | attest(parseInt("5.5").toString()).snap( 19 | 'must be a well-formed integer string (was "5.5")' 20 | ) 21 | attest(parseInt("five").toString()).snap( 22 | 'must be a well-formed integer string (was "five")' 23 | ) 24 | attest(parseInt(5).toString()).snap("must be a string (was a number)") 25 | attest(parseInt("9007199254740992").toString()).snap( 26 | 'must be an integer in the range Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER (was "9007199254740992")' 27 | ) 28 | }) 29 | 30 | it("date", () => { 31 | const parseDate = type("string.date.parse") 32 | attest(parseDate("5/21/1993").toString()).snap( 33 | "Fri May 21 1993 00:00:00 GMT-0400 (Eastern Daylight Time)" 34 | ) 35 | attest(parseDate("foo").toString()).snap( 36 | 'must be a parsable date (was "foo")' 37 | ) 38 | attest(parseDate(5).toString()).snap("must be a string (was a number)") 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/partial.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("parsed", () => { 6 | const types = scope({ 7 | user: { 8 | name: "string", 9 | "age?": "number" 10 | }, 11 | actual: "Partial", 12 | expected: { 13 | "name?": "string", 14 | "age?": "number" 15 | } 16 | }).export() 17 | 18 | attest(types.actual.t) 19 | attest(types.actual.expression).equals(types.expected.expression) 20 | }) 21 | 22 | it("chained", () => { 23 | const T = type({ 24 | "[string]": "number", 25 | foo: "1", 26 | "bar?": "1" 27 | }).partial() 28 | 29 | attest<{ 30 | // really this should just be number for the index signature, seems like a TS bug? 31 | [x: string]: number | undefined 32 | foo?: 1 33 | bar?: 1 34 | }>(T.t) 35 | 36 | attest(T.expression).snap("{ [string]: number, bar?: 1, foo?: 1 }") 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/pick.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { 3 | writeInvalidKeysMessage, 4 | writeNonStructuralOperandMessage 5 | } from "@ark/schema" 6 | import { scope, type } from "arktype" 7 | 8 | contextualize(() => { 9 | it("parsed", () => { 10 | const types = scope({ 11 | from: { 12 | foo: "1", 13 | "bar?": "1", 14 | baz: "1", 15 | "quux?": "1" 16 | }, 17 | actual: "Pick", 18 | expected: { 19 | foo: "1", 20 | "bar?": "1" 21 | } 22 | }).export() 23 | 24 | attest(types.actual.t) 25 | attest(types.actual.expression).equals(types.expected.expression) 26 | }) 27 | 28 | it("chained", () => { 29 | const User = type({ 30 | name: "string", 31 | "age?": "number", 32 | isAdmin: "boolean" 33 | }) 34 | 35 | const BasicUser = User.pick("name", "age") 36 | 37 | const Expected = type({ 38 | name: "string", 39 | "age?": "number" 40 | }) 41 | 42 | attest(BasicUser.t) 43 | 44 | attest(BasicUser.expression).equals(Expected.expression) 45 | }) 46 | 47 | it("invalid key", () => { 48 | const User = type({ 49 | name: "string" 50 | }) 51 | 52 | // @ts-expect-error 53 | attest(() => User.pick("length")) 54 | .throws(writeInvalidKeysMessage(User.expression, ["length"])) 55 | .type.errors.snap( 56 | 'Argument of type \'"length"\' is not assignable to parameter of type \'"name" | cast<"name">\'.' 57 | ) 58 | }) 59 | 60 | it("non-structure", () => { 61 | // @ts-expect-error 62 | attest(() => type("string").pick("length")) 63 | .throws(writeNonStructuralOperandMessage("pick", "string")) 64 | .type.errors("Property 'pick' does not exist") 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/record.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { 3 | intrinsic, 4 | writeIndivisibleMessage, 5 | writeUnsatisfiedParameterConstraintMessage 6 | } from "@ark/schema" 7 | import { keywords, type } from "arktype" 8 | 9 | contextualize(() => { 10 | it("parsed", () => { 11 | const Expected = type({ "[string]": "number" }) 12 | 13 | const Expression = type("Record") 14 | attest(Expression.json).equals(Expected.json) 15 | attest(Expression.t) 16 | }) 17 | 18 | it("invoked", () => { 19 | const Expected = type({ "[string]": "number" }) 20 | 21 | const T = keywords.Record("string", "number") 22 | 23 | attest(T.json).equals(Expected.json) 24 | attest(T.t) 25 | }) 26 | 27 | it("invoked validation error", () => { 28 | // @ts-expect-error 29 | attest(() => keywords.Record("string", "string % 2")).throwsAndHasTypeError( 30 | writeIndivisibleMessage(intrinsic.string) 31 | ) 32 | }) 33 | 34 | it("invoked constraint error", () => { 35 | // @ts-expect-error 36 | attest(() => keywords.Record("boolean", "number")) 37 | .throws( 38 | writeUnsatisfiedParameterConstraintMessage( 39 | "K", 40 | "string | symbol", 41 | "boolean" 42 | ) 43 | ) 44 | .type.errors(`ErrorType<"Invalid argument for K", [expected: Key]>`) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/required.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { scope, type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("parsed", () => { 6 | const types = scope({ 7 | user: { 8 | name: "string", 9 | "age?": "number" 10 | }, 11 | actual: "Required", 12 | expected: { 13 | name: "string", 14 | age: "number" 15 | } 16 | }).export() 17 | 18 | attest(types.actual.t) 19 | attest(types.actual.expression).equals(types.expected.expression) 20 | }) 21 | 22 | it("chained", () => { 23 | const T = type({ 24 | "[string]": "number", 25 | foo: "1", 26 | "bar?": "1" 27 | }).required() 28 | 29 | attest<{ 30 | [x: string]: number 31 | foo: 1 32 | bar: 1 33 | }>(T.t) 34 | 35 | attest(T.expression).snap("{ [string]: number, bar: 1, foo: 1 }") 36 | }) 37 | 38 | // https://github.com/arktypeio/arktype/issues/1156 39 | it("with default", () => { 40 | const T = type({ foo: "string = 'bar'" }).required() 41 | 42 | const Expected = type({ 43 | foo: "string" 44 | }) 45 | 46 | // https://github.com/arktypeio/arktype/issues/1160 47 | // attest(); 48 | 49 | attest(T.expression).equals(Expected.expression) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/url.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | 4 | contextualize(() => { 5 | it("root", () => { 6 | const Url = type("string.url") 7 | 8 | attest(Url).type.toString.snap("Type") 9 | 10 | attest(Url("https://arktype.io")).snap("https://arktype.io") 11 | attest(Url("arktype").toString()).snap( 12 | 'must be a URL string (was "arktype")' 13 | ) 14 | }) 15 | 16 | it("parse", () => { 17 | const parseUrl = type("string.url.parse") 18 | 19 | attest(parseUrl).type.toString.snap("Type<(In: string) => To, {}>") 20 | attest(parseUrl("https://arktype.io")).instanceOf(URL) 21 | attest(parseUrl("arktype").toString()).snap( 22 | 'must be a URL string (was "arktype")' 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /ark/type/__tests__/keywords/uuid.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { keywords, type } from "arktype" 3 | 4 | const validUuidV4 = "f70b8242-dd57-4e6b-b0b7-649d997140a0" 5 | 6 | const validUuidV5 = "f70b8242-dd57-5e6b-b0b7-649d997140a0" 7 | 8 | contextualize(() => { 9 | it("root", () => { 10 | const Uuid = type("string.uuid") 11 | attest(Uuid(validUuidV4)).equals(validUuidV4) 12 | attest(Uuid("1234").toString()).snap('must be a UUID (was "1234")') 13 | }) 14 | 15 | it("version subtype", () => { 16 | const Uuidv4 = type("string.uuid.v4") 17 | 18 | attest(Uuidv4(validUuidV4)).equals(validUuidV4) 19 | attest(Uuidv4("1234").toString()).snap('must be a UUIDv4 (was "1234")') 20 | 21 | attest(keywords.string.uuid.v5(validUuidV5)).equals(validUuidV5) 22 | 23 | attest(Uuidv4(validUuidV5).toString()).equals( 24 | 'must be a UUIDv4 (was "f70b8242-dd57-5e6b-b0b7-649d997140a0")' 25 | ) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /ark/type/__tests__/object.bench.ts: -------------------------------------------------------------------------------- 1 | import { bench } from "@ark/attest" 2 | import { type } from "arktype" 3 | 4 | bench.baseline(() => { 5 | type({ 6 | _: "symbol[]", 7 | __: { ___: "symbol[]" } 8 | }) 9 | 10 | type(["symbol[]", "symbol[]", ["symbol[]"]]) 11 | }) 12 | 13 | bench("object literal", () => 14 | type({ 15 | a: "string[]", 16 | b: "number[]", 17 | c: { nested: "boolean[]" } 18 | }) 19 | ).types([2492, "instantiations"]) 20 | 21 | bench("object literal with optional keys", () => 22 | type({ 23 | "a?": "string[]", 24 | "b?": "number[]", 25 | "c?": { "nested?": "boolean[]" } 26 | }) 27 | ).types([2317, "instantiations"]) 28 | 29 | bench("tuple", () => type(["string[]", "number[]", ["boolean[]"]])).types([ 30 | 3026, 31 | "instantiations" 32 | ]) 33 | 34 | bench("inline definition", () => 35 | type({ 36 | a: "string" 37 | }) 38 | ).types([909, "instantiations"]) 39 | 40 | bench("referenced type", () => { 41 | const A = type("string") 42 | return type({ 43 | A 44 | }) 45 | }).types([1115, "instantiations"]) 46 | 47 | // https://github.com/arktypeio/arktype/issues/787 48 | bench("inline reference", () => 49 | type({ 50 | a: type("string") 51 | }) 52 | ).types([1131, "instantiations"]) 53 | 54 | bench("nested type invocations", () => 55 | type({ 56 | foo: type({ 57 | bar: type({ 58 | zoo: "string[]" 59 | }) 60 | .array() 61 | .or("number"), 62 | superBar: type([ 63 | type("string"), 64 | type("number[]"), 65 | type({ inner: type("boolean") }) 66 | ]) 67 | }) 68 | .or({ 69 | baz: "string", 70 | quux: "1 | 2 | 3" 71 | }) 72 | .array() 73 | }) 74 | ).types([12909, "instantiations"]) 75 | -------------------------------------------------------------------------------- /ark/type/__tests__/operand.bench.ts: -------------------------------------------------------------------------------- 1 | import { bench } from "@ark/attest" 2 | import { type } from "arktype" 3 | 4 | bench.baseline(() => type("never")) 5 | 6 | bench("single-quoted", () => type("'nineteen characters'")).types([ 7 | 716, 8 | "instantiations" 9 | ]) 10 | 11 | bench("double-quoted", () => type('"nineteen characters"')).types([ 12 | 716, 13 | "instantiations" 14 | ]) 15 | 16 | bench("regex literal", () => type("/nineteen characters/")).types([ 17 | 706, 18 | "instantiations" 19 | ]) 20 | 21 | bench("keyword", () => type("string")).types([554, "instantiations"]) 22 | 23 | bench("number", () => type("-98765.4321")).types([507, "instantiations"]) 24 | 25 | bench("bigint", () => type("-987654321n")).types([579, "instantiations"]) 26 | 27 | bench("object", () => type({ foo: "string" })).types([1115, "instantiations"]) 28 | 29 | bench("union", () => 30 | // Union is automatically discriminated using shallow or deep keys 31 | type({ 32 | kind: "'admin'", 33 | "powers?": "string[]" 34 | }) 35 | .or({ 36 | kind: "'superadmin'", 37 | "superpowers?": "string[]" 38 | }) 39 | .or({ 40 | kind: "'pleb'" 41 | }) 42 | ).types([4976, "instantiations"]) 43 | -------------------------------------------------------------------------------- /ark/type/__tests__/optional.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { type } from "arktype" 3 | import { shallowOptionalMessage } from "arktype/internal/parser/ast/validate.ts" 4 | 5 | contextualize(() => { 6 | it("no shallow default in tuple expression", () => { 7 | attest(() => 8 | // @ts-expect-error 9 | type(["string?", "|", "number"]) 10 | ).throwsAndHasTypeError(shallowOptionalMessage) 11 | 12 | attest(() => 13 | // @ts-expect-error 14 | type(["string", "|", ["number", "?"]]) 15 | ).throwsAndHasTypeError(shallowOptionalMessage) 16 | }) 17 | 18 | it("no shallow default in scope", () => { 19 | // @ts-expect-error 20 | attest(() => type.module({ foo: "string?" })).throwsAndHasTypeError( 21 | shallowOptionalMessage 22 | ) 23 | 24 | // @ts-expect-error 25 | attest(() => type.module({ foo: ["string", "?"] })).throwsAndHasTypeError( 26 | shallowOptionalMessage 27 | ) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /ark/type/__tests__/select.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import type { DomainNode } from "@ark/schema" 3 | import { type } from "arktype" 4 | 5 | contextualize(() => { 6 | const User = type({ 7 | name: "string", 8 | platform: "'android' | 'ios'", 9 | "version?": "number | string" 10 | }) 11 | 12 | it("can select a domain", () => { 13 | const selected = User.select({ 14 | kind: "domain", 15 | where: d => d.domain === "string" 16 | }) 17 | 18 | attest(selected).snap([{ domain: "string" }]) 19 | }) 20 | 21 | it("can configure based on a selector", () => { 22 | const ConfiguredUser = User.configure( 23 | { description: "A STRING" }, 24 | { 25 | kind: "domain", 26 | where: d => d.domain === "string" 27 | } 28 | ) 29 | 30 | attest(ConfiguredUser).snap({ 31 | required: [ 32 | { key: "name", value: { domain: "string", meta: "A STRING" } }, 33 | { key: "platform", value: [{ unit: "android" }, { unit: "ios" }] } 34 | ], 35 | optional: [ 36 | { 37 | key: "version", 38 | value: ["number", { domain: "string", meta: "A STRING" }] 39 | } 40 | ], 41 | domain: "object" 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /ark/type/__tests__/serialization.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { rootSchema } from "@ark/schema" 3 | import { type } from "arktype" 4 | 5 | contextualize(() => { 6 | it("built-in prototypes", () => { 7 | const A = type({ 8 | age: "number" 9 | }) 10 | 11 | const B = type({ 12 | ages: A.array() 13 | }) 14 | 15 | const C = rootSchema(B.json as never) 16 | 17 | attest(B.json).equals(C.json) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /ark/type/__tests__/standardSchema.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import type { StandardSchemaV1 } from "@ark/schema" 3 | import type { promisable } from "@ark/util" 4 | import { type } from "arktype" 5 | 6 | contextualize(() => { 7 | it("validation conforms to spec", () => { 8 | const T = type({ foo: "string" }) 9 | const standard: StandardSchemaV1<{ foo: string }> = T 10 | const standardOut = standard["~standard"].validate({ 11 | foo: "bar" 12 | }) 13 | attest>>( 14 | standardOut 15 | ).equals({ 16 | value: { foo: "bar" } 17 | }) 18 | 19 | const badStandardOut = standard["~standard"].validate({ 20 | foo: 5 21 | }) as StandardSchemaV1.FailureResult 22 | 23 | attest(badStandardOut.issues).instanceOf(type.errors) 24 | attest(badStandardOut.issues.toString()).snap( 25 | "foo must be a string (was a number)" 26 | ) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /ark/type/__tests__/typeReference.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { writeUnresolvableMessage } from "@ark/schema" 3 | import { ark, scope, type } from "arktype" 4 | 5 | contextualize(() => { 6 | it("shallow type reference", () => { 7 | const T = type(type("boolean")) 8 | attest(T.infer) 9 | }) 10 | 11 | it("bad shallow type reference", () => { 12 | attest(() => { 13 | // @ts-expect-error 14 | type(type("foolean")) 15 | }).throwsAndHasTypeError(writeUnresolvableMessage("foolean")) 16 | }) 17 | 18 | it("deep type reference", () => { 19 | const T = type({ a: type("boolean") }) 20 | attest<{ a: boolean }>(T.infer) 21 | }) 22 | 23 | it("type reference in scope", () => { 24 | const A = type({ a: "string" }) 25 | const $ = scope({ a: A }) 26 | const types = $.export() 27 | attest(types.a.json).equals(A.json) 28 | attest(A.$.json).equals(ark.json) 29 | attest(types.a.$.json).equals($.json) 30 | attest<{ a: string }>(types.a.infer) 31 | }) 32 | 33 | it("bad deep type reference", () => { 34 | attest(() => { 35 | // @ts-expect-error 36 | type({ a: type("goolean") }) 37 | }).throwsAndHasTypeError(writeUnresolvableMessage("goolean")) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /ark/type/config.ts: -------------------------------------------------------------------------------- 1 | import type { arkKind, TypeMeta } from "@ark/schema" 2 | import type { Ark } from "./keywords/keywords.ts" 3 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports 4 | import { configureSchema, type ArkSchemaConfig } from "@ark/schema/config" 5 | import type { anyOrNever } from "@ark/util" 6 | 7 | export type KeywordConfig = { 8 | [k in keyof Ark.flat as parseConfigurableFlatAlias< 9 | k, 10 | Ark.flat[k] 11 | >]?: TypeMeta.Collapsible 12 | } 13 | 14 | type parseConfigurableFlatAlias = 15 | [v] extends [anyOrNever] ? k 16 | : v extends { [arkKind]: "generic" | "module" } ? never 17 | : k extends `${infer prefix}.root` ? prefix 18 | : k 19 | 20 | export interface ArkConfig extends ArkSchemaConfig { 21 | keywords?: KeywordConfig 22 | } 23 | 24 | export const configure: (config: config) => config = 25 | configureSchema as never 26 | 27 | declare global { 28 | export interface ArkEnv { 29 | $(): Ark 30 | } 31 | } 32 | 33 | /** 34 | * This mirrors the global ArkEnv namespace as a local export. We use it instead 35 | * of the global internally due to a bug in twoslash that prevents `ark/docs` 36 | * from building if we refer to the global directly. 37 | * 38 | * If, in the future, docs can build while arktype refers to `ArkEnv.$` directly, 39 | * this can be removed. 40 | */ 41 | export declare namespace ArkAmbient { 42 | export type $ = ReturnType 43 | 44 | export type meta = ArkEnv.meta 45 | 46 | export type prototypes = ArkEnv.prototypes 47 | } 48 | -------------------------------------------------------------------------------- /ark/type/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ArkError, 3 | ArkErrors, 4 | Traversal, 5 | TraversalError, 6 | type ArkSchemaConfig, 7 | type ArkSchemaScopeConfig, 8 | type JsonSchema 9 | } from "@ark/schema" 10 | export { Hkt, inferred, ParseError } from "@ark/util" 11 | export type { distill, Out } from "./attributes.ts" 12 | export * from "./config.ts" 13 | export { Generic } from "./generic.ts" 14 | export { 15 | ark, 16 | declare, 17 | define, 18 | generic, 19 | keywords, 20 | match, 21 | type, 22 | type Ark 23 | } from "./keywords/keywords.ts" 24 | export type { BaseType } from "./methods/base.ts" 25 | export { Module, type BoundModule, type Submodule } from "./module.ts" 26 | export type { 27 | inferDefinition, 28 | validateDefinition 29 | } from "./parser/definition.ts" 30 | export { scope, type bindThis, type Scope } from "./scope.ts" 31 | export { Type } from "./type.ts" 32 | -------------------------------------------------------------------------------- /ark/type/keywords/Array.ts: -------------------------------------------------------------------------------- 1 | import { genericNode, intrinsic, rootSchema } from "@ark/schema" 2 | import { Hkt, liftArray, type Digit } from "@ark/util" 3 | import type { To } from "../attributes.ts" 4 | import type { Module, Submodule } from "../module.ts" 5 | import { Scope } from "../scope.ts" 6 | 7 | class liftFromHkt extends Hkt<[element: unknown]> { 8 | declare body: liftArray extends infer lifted ? 9 | (In: this[0] | lifted) => To 10 | : never 11 | } 12 | 13 | const liftFrom = genericNode("element")(args => { 14 | const nonArrayElement = args.element.exclude(intrinsic.Array) 15 | const lifted = nonArrayElement.array() 16 | return nonArrayElement 17 | .rawOr(lifted) 18 | .pipe(liftArray) 19 | .distribute( 20 | branch => branch.assertHasKind("morph").declareOut(lifted), 21 | rootSchema 22 | ) 23 | }, liftFromHkt) 24 | 25 | export const arkArray: arkArray.module = Scope.module( 26 | { 27 | root: intrinsic.Array, 28 | readonly: "root", 29 | index: intrinsic.nonNegativeIntegerString, 30 | liftFrom 31 | }, 32 | { 33 | name: "Array" 34 | } 35 | ) 36 | 37 | export declare namespace arkArray { 38 | export type module = Module 39 | 40 | export type submodule = Submodule<$> 41 | 42 | export type $ = { 43 | root: unknown[] 44 | readonly: readonly unknown[] 45 | index: NonNegativeIntegerString 46 | liftFrom: typeof liftFrom.t 47 | } 48 | } 49 | 50 | export type NonNegativeIntegerString = 51 | | `${Digit}` 52 | | (`${Exclude}${string}` & `${bigint}`) 53 | -------------------------------------------------------------------------------- /ark/type/keywords/TypedArray.ts: -------------------------------------------------------------------------------- 1 | import type { Module, Submodule } from "../module.ts" 2 | import { Scope } from "../scope.ts" 3 | 4 | export const TypedArray: TypedArray.module = Scope.module( 5 | { 6 | Int8: ["instanceof", Int8Array], 7 | Uint8: ["instanceof", Uint8Array], 8 | Uint8Clamped: ["instanceof", Uint8ClampedArray], 9 | Int16: ["instanceof", Int16Array], 10 | Uint16: ["instanceof", Uint16Array], 11 | Int32: ["instanceof", Int32Array], 12 | Uint32: ["instanceof", Uint32Array], 13 | Float32: ["instanceof", Float32Array], 14 | Float64: ["instanceof", Float64Array], 15 | BigInt64: ["instanceof", BigInt64Array], 16 | BigUint64: ["instanceof", BigUint64Array] 17 | }, 18 | { 19 | name: "TypedArray" 20 | } 21 | ) 22 | 23 | export declare namespace TypedArray { 24 | export type module = Module 25 | 26 | // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray 27 | export type submodule = Submodule<$> 28 | 29 | export type $ = { 30 | Int8: Int8Array 31 | Uint8: Uint8Array 32 | Uint8Clamped: Uint8ClampedArray 33 | Int16: Int16Array 34 | Uint16: Uint16Array 35 | Int32: Int32Array 36 | Uint32: Uint32Array 37 | Float32: Float32Array 38 | Float64: Float64Array 39 | BigInt64: BigInt64Array 40 | BigUint64: BigUint64Array 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ark/type/keywords/builtins.ts: -------------------------------------------------------------------------------- 1 | import { genericNode, intrinsic } from "@ark/schema" 2 | import type * as util from "@ark/util" 3 | import { Hkt, type Key } from "@ark/util" 4 | import type { Module, Submodule } from "../module.ts" 5 | import { Scope } from "../scope.ts" 6 | 7 | class MergeHkt extends Hkt<[base: object, props: object]> { 8 | declare body: util.merge 9 | 10 | description = 11 | 'merge an object\'s properties onto another like `Merge(User, { isAdmin: "true" })`' 12 | } 13 | 14 | const Merge = genericNode( 15 | ["base", intrinsic.object], 16 | ["props", intrinsic.object] 17 | )(args => args.base.merge(args.props), MergeHkt) 18 | 19 | export const arkBuiltins: arkBuiltins = Scope.module({ 20 | Key: intrinsic.key, 21 | Merge 22 | }) 23 | 24 | export type arkBuiltins = Module 25 | 26 | export declare namespace arkBuiltins { 27 | export type submodule = Submodule<$> 28 | 29 | export type $ = { 30 | Key: Key 31 | Merge: typeof Merge.t 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ark/type/keywords/number.ts: -------------------------------------------------------------------------------- 1 | import { intrinsic, rootSchema } from "@ark/schema" 2 | import type { Module, Submodule } from "../module.ts" 3 | import { Scope } from "../scope.ts" 4 | 5 | /** 6 | * As per the ECMA-262 specification: 7 | * A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds. 8 | * 9 | * @see https://262.ecma-international.org/15.0/index.html#sec-time-values-and-time-range 10 | */ 11 | 12 | export const epoch = rootSchema({ 13 | domain: { 14 | domain: "number", 15 | meta: "a number representing a Unix timestamp" 16 | }, 17 | divisor: { 18 | rule: 1, 19 | meta: `an integer representing a Unix timestamp` 20 | }, 21 | min: { 22 | rule: -8640000000000000, 23 | meta: `a Unix timestamp after -8640000000000000` 24 | }, 25 | max: { 26 | rule: 8640000000000000, 27 | meta: "a Unix timestamp before 8640000000000000" 28 | }, 29 | meta: "an integer representing a safe Unix timestamp" 30 | }) 31 | 32 | export const integer = rootSchema({ 33 | domain: "number", 34 | divisor: 1 35 | }) 36 | 37 | export const number: number.module = Scope.module( 38 | { 39 | root: intrinsic.number, 40 | integer, 41 | epoch, 42 | safe: rootSchema({ 43 | domain: { 44 | domain: "number", 45 | numberAllowsNaN: false 46 | }, 47 | min: Number.MIN_SAFE_INTEGER, 48 | max: Number.MAX_SAFE_INTEGER 49 | }), 50 | NaN: ["===", Number.NaN], 51 | Infinity: ["===", Number.POSITIVE_INFINITY], 52 | NegativeInfinity: ["===", Number.NEGATIVE_INFINITY] 53 | }, 54 | { 55 | name: "number" 56 | } 57 | ) 58 | 59 | export declare namespace number { 60 | export type module = Module 61 | 62 | export type submodule = Submodule<$> 63 | 64 | export type $ = { 65 | root: number 66 | epoch: number 67 | integer: number 68 | safe: number 69 | NaN: number 70 | Infinity: number 71 | NegativeInfinity: number 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ark/type/methods/array.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ExactLength, 3 | ExclusiveNumericRangeSchema, 4 | InclusiveNumericRangeSchema 5 | } from "@ark/schema" 6 | import type { ObjectType } from "./object.ts" 7 | 8 | interface Type< 9 | /** @ts-ignore cast variance */ 10 | out t extends readonly unknown[] = readonly unknown[], 11 | $ = {} 12 | > extends ObjectType { 13 | atLeastLength(schema: InclusiveNumericRangeSchema): this 14 | 15 | atMostLength(schema: InclusiveNumericRangeSchema): this 16 | 17 | moreThanLength(schema: ExclusiveNumericRangeSchema): this 18 | 19 | lessThanLength(schema: ExclusiveNumericRangeSchema): this 20 | 21 | exactlyLength(schema: ExactLength.Schema): this 22 | } 23 | 24 | export type { Type as ArrayType } 25 | -------------------------------------------------------------------------------- /ark/type/methods/date.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ExclusiveDateRangeSchema, 3 | InclusiveDateRangeSchema 4 | } from "@ark/schema" 5 | import type { ObjectType } from "./object.ts" 6 | 7 | /** @ts-ignore cast variance */ 8 | interface Type 9 | extends ObjectType { 10 | atOrAfter(schema: InclusiveDateRangeSchema): this 11 | 12 | atOrBefore(schema: InclusiveDateRangeSchema): this 13 | 14 | laterThan(schema: ExclusiveDateRangeSchema): this 15 | 16 | earlierThan(schema: ExclusiveDateRangeSchema): this 17 | } 18 | 19 | export type { Type as DateType } 20 | -------------------------------------------------------------------------------- /ark/type/methods/instantiate.ts: -------------------------------------------------------------------------------- 1 | import type { anyOrNever, array } from "@ark/util" 2 | import type { ArrayType } from "./array.ts" 3 | import type { BaseType } from "./base.ts" 4 | import type { DateType } from "./date.ts" 5 | import type { NumberType } from "./number.ts" 6 | import type { ObjectType } from "./object.ts" 7 | import type { StringType } from "./string.ts" 8 | 9 | export type instantiateType = 10 | // all branches have to conform to a single basis type those methods to be available 11 | [t] extends [anyOrNever] ? BaseType 12 | : [t] extends [object] ? 13 | [t] extends [array] ? ArrayType 14 | : [t] extends [Date] ? DateType 15 | : ObjectType 16 | : [t] extends [string] ? StringType 17 | : [t] extends [number] ? NumberType 18 | : BaseType 19 | -------------------------------------------------------------------------------- /ark/type/methods/number.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Divisor, 3 | ExclusiveNumericRangeSchema, 4 | InclusiveNumericRangeSchema 5 | } from "@ark/schema" 6 | import type { BaseType } from "./base.ts" 7 | 8 | /** @ts-ignore cast variance */ 9 | interface Type extends BaseType { 10 | divisibleBy(schema: Divisor.Schema): this 11 | 12 | atLeast(schema: InclusiveNumericRangeSchema): this 13 | 14 | atMost(schema: InclusiveNumericRangeSchema): this 15 | 16 | moreThan(schema: ExclusiveNumericRangeSchema): this 17 | 18 | lessThan(schema: ExclusiveNumericRangeSchema): this 19 | } 20 | 21 | export type { Type as NumberType } 22 | -------------------------------------------------------------------------------- /ark/type/methods/string.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ExactLength, 3 | ExclusiveNumericRangeSchema, 4 | InclusiveNumericRangeSchema, 5 | Pattern 6 | } from "@ark/schema" 7 | import type { BaseType } from "./base.ts" 8 | 9 | /** @ts-ignore cast variance */ 10 | interface Type extends BaseType { 11 | matching(schema: Pattern.Schema): this 12 | 13 | atLeastLength(schema: InclusiveNumericRangeSchema): this 14 | 15 | atMostLength(schema: InclusiveNumericRangeSchema): this 16 | 17 | moreThanLength(schema: ExclusiveNumericRangeSchema): this 18 | 19 | lessThanLength(schema: ExclusiveNumericRangeSchema): this 20 | 21 | exactlyLength(schema: ExactLength.Schema): this 22 | } 23 | 24 | export type { Type as StringType } 25 | -------------------------------------------------------------------------------- /ark/type/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RootModule, 3 | type GenericAst, 4 | type PreparsedNodeResolution 5 | } from "@ark/schema" 6 | import type { anyOrNever, inferred } from "@ark/util" 7 | import type { Generic } from "./generic.ts" 8 | import type { Type } from "./type.ts" 9 | 10 | export const Module: new <$ extends {}>(exports: exportScope<$>) => Module<$> = 11 | RootModule as never 12 | 13 | export interface Module<$ extends {} = {}> extends RootModule> {} 14 | 15 | export type exportScope<$> = bindExportsToScope<$, $> 16 | 17 | export const BoundModule: new ( 18 | exports: bindExportsToScope, 19 | $: $ 20 | ) => BoundModule = RootModule as never 21 | 22 | export interface BoundModule 23 | extends RootModule> {} 24 | 25 | export type bindExportsToScope = { 26 | [k in keyof exports]: instantiateExport 27 | } & unknown 28 | 29 | export type Submodule = RootModule< 30 | exports & 31 | ("root" extends keyof exports ? { [inferred]: exports["root"] } : {}) 32 | > 33 | 34 | export type instantiateExport = 35 | [t] extends [PreparsedNodeResolution] ? 36 | [t] extends [anyOrNever] ? Type 37 | : t extends GenericAst ? 38 | Generic 39 | : t extends Submodule ? BoundModule 40 | : never 41 | : Type 42 | -------------------------------------------------------------------------------- /ark/type/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arktype", 3 | "description": "TypeScript's 1:1 validator, optimized from editor to runtime", 4 | "version": "2.1.20", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/arktypeio/arktype.git", 9 | "directory": "ark/type" 10 | }, 11 | "author": { 12 | "name": "David Blass", 13 | "email": "david@arktype.io", 14 | "url": "https://arktype.io" 15 | }, 16 | "type": "module", 17 | "main": "./out/index.js", 18 | "types": "./out/index.d.ts", 19 | "exports": { 20 | ".": { 21 | "ark-ts": "./index.ts", 22 | "default": "./out/index.js" 23 | }, 24 | "./config": { 25 | "ark-ts": "./config.ts", 26 | "default": "./out/config.js" 27 | }, 28 | "./internal/*.ts": { 29 | "ark-ts": "./*.ts", 30 | "default": "./out/*.js" 31 | } 32 | }, 33 | "files": [ 34 | "out" 35 | ], 36 | "scripts": { 37 | "build": "ts ../repo/build.ts", 38 | "test": "ts ../repo/testPackage.ts; pnpm testIntegration", 39 | "testIntegration": "pnpm testSimpleConfig && pnpm testAllConfig && pnpm testOnFailConfig && pnpm testEoptConfig", 40 | "testSimpleConfig": "ts ./__tests__/integration/testSimpleConfig.ts", 41 | "testAllConfig": "ts ./__tests__/integration/testAllConfig.ts", 42 | "testOnFailConfig": "ts ./__tests__/integration/testOnFailConfig.ts", 43 | "testEoptConfig": "ts ./__tests__/integration/testEoptConfig.ts" 44 | }, 45 | "dependencies": { 46 | "@ark/util": "workspace:*", 47 | "@ark/schema": "workspace:*" 48 | }, 49 | "publishConfig": { 50 | "access": "public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ark/type/parser/ast/default.ts: -------------------------------------------------------------------------------- 1 | import type { writeUnassignableDefaultValueMessage } from "@ark/schema" 2 | import type { ErrorMessage } from "@ark/util" 3 | import type { type } from "../../keywords/keywords.ts" 4 | import type { UnitLiteral } from "../shift/operator/default.ts" 5 | import type { inferAstIn } from "./infer.ts" 6 | import type { astToString } from "./utils.ts" 7 | import type { validateAst } from "./validate.ts" 8 | 9 | export type validateDefault = 10 | validateAst extends infer e extends ErrorMessage ? e 11 | : // check against the output of the type since morphs will not occur 12 | // ambient infer is safe since the default value is always a literal 13 | type.infer extends inferAstIn ? undefined 14 | : ErrorMessage< 15 | writeUnassignableDefaultValueMessage, unitLiteral> 16 | > 17 | -------------------------------------------------------------------------------- /ark/type/parser/ast/divisor.ts: -------------------------------------------------------------------------------- 1 | import type { writeIndivisibleMessage } from "@ark/schema" 2 | import type { ErrorMessage } from "@ark/util" 3 | import type { InferredMorph } from "../../attributes.ts" 4 | import type { inferAstRoot } from "./infer.ts" 5 | import type { writeConstrainedMorphMessage } from "./utils.ts" 6 | import type { validateAst } from "./validate.ts" 7 | 8 | export type validateDivisor = 9 | inferAstRoot extends infer data ? 10 | [data] extends [number] ? validateAst 11 | : [data] extends [InferredMorph] ? 12 | ErrorMessage> 13 | : ErrorMessage> 14 | : never 15 | -------------------------------------------------------------------------------- /ark/type/parser/ast/keyof.ts: -------------------------------------------------------------------------------- 1 | import type { writeNonStructuralOperandMessage } from "@ark/schema" 2 | import type { ErrorMessage, typeToString } from "@ark/util" 3 | import type { inferAstRoot } from "./infer.ts" 4 | import type { validateAst } from "./validate.ts" 5 | 6 | export type validateKeyof = 7 | inferAstRoot extends infer data ? 8 | [data] extends [object] ? 9 | validateAst 10 | : ErrorMessage>> 11 | : never 12 | -------------------------------------------------------------------------------- /ark/type/parser/ast/utils.ts: -------------------------------------------------------------------------------- 1 | import type { satisfy, Stringifiable } from "@ark/util" 2 | import type { Comparator } from "../reduce/shared.ts" 3 | import type { ArkTypeScanner } from "../shift/scanner.ts" 4 | import type { 5 | DefAst, 6 | InferredAst, 7 | InfixExpression, 8 | PostfixExpression 9 | } from "./infer.ts" 10 | 11 | export type astToString = 12 | ast extends InferredAst | DefAst ? ast[2] 13 | : ast extends PostfixExpression ? 14 | operator extends "[]" ? 15 | `${astToString}[]` 16 | : never 17 | : ast extends InfixExpression ? 18 | operator extends "&" | "|" | "%" | Comparator ? 19 | `${astToString} ${operator} ${astToString}` 20 | : never 21 | : ast extends Stringifiable ? `${ast extends bigint ? `${ast}n` : ast}` 22 | : "..." 23 | 24 | export type ConstraintOperator = satisfy< 25 | ArkTypeScanner.OperatorToken, 26 | "%" | Comparator 27 | > 28 | 29 | export type writeConstrainedMorphMessage = 30 | `To constrain the output of ${astToString}, pipe like myMorph.to('number > 0'). 31 | To constrain the input, intersect like myMorph.and('number > 0').` 32 | -------------------------------------------------------------------------------- /ark/type/parser/shift/operator/brand.ts: -------------------------------------------------------------------------------- 1 | import type { emptyBrandNameMessage } from "@ark/schema" 2 | import type { DynamicStateWithRoot } from "../../reduce/dynamic.ts" 3 | import type { StaticState, state } from "../../reduce/static.ts" 4 | import type { ArkTypeScanner } from "../scanner.ts" 5 | 6 | export const parseBrand = (s: DynamicStateWithRoot): void => { 7 | s.scanner.shiftUntilNonWhitespace() 8 | const brandName = s.scanner.shiftUntilNextTerminator() 9 | s.root = s.root.brand(brandName) 10 | } 11 | 12 | export type parseBrand = 13 | ArkTypeScanner.shiftUntilNextTerminator< 14 | ArkTypeScanner.skipWhitespace 15 | > extends ( 16 | ArkTypeScanner.shiftResult<`${infer brandName}`, infer nextUnscanned> 17 | ) ? 18 | brandName extends "" ? 19 | state.error 20 | : state.setRoot 21 | : never 22 | -------------------------------------------------------------------------------- /ark/type/parser/shift/operator/divisor.ts: -------------------------------------------------------------------------------- 1 | import { tryParseInteger } from "@ark/util" 2 | import type { DynamicStateWithRoot } from "../../reduce/dynamic.ts" 3 | import type { StaticState, state } from "../../reduce/static.ts" 4 | import type { ArkTypeScanner } from "../scanner.ts" 5 | 6 | export const parseDivisor = (s: DynamicStateWithRoot): void => { 7 | const divisorToken = s.scanner.shiftUntilNextTerminator() 8 | const divisor = tryParseInteger(divisorToken, { 9 | errorOnFail: writeInvalidDivisorMessage(divisorToken) 10 | }) 11 | if (divisor === 0) s.error(writeInvalidDivisorMessage(0)) 12 | 13 | s.root = s.root.constrain("divisor", divisor) 14 | } 15 | 16 | export type parseDivisor = 17 | ArkTypeScanner.shiftUntilNextTerminator< 18 | ArkTypeScanner.skipWhitespace 19 | > extends ArkTypeScanner.shiftResult ? 20 | scanned extends `${infer divisor extends number}` ? 21 | divisor extends 0 ? 22 | state.error> 23 | : state.setRoot 24 | : state.error> 25 | : never 26 | 27 | export const writeInvalidDivisorMessage = ( 28 | divisor: divisor 29 | ): writeInvalidDivisorMessage => 30 | `% operator must be followed by a non-zero integer literal (was ${divisor})` 31 | 32 | export type writeInvalidDivisorMessage = 33 | `% operator must be followed by a non-zero integer literal (was ${divisor})` 34 | -------------------------------------------------------------------------------- /ark/util/__tests__/hkt.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import type { Hkt } from "@ark/util" 3 | 4 | contextualize(() => { 5 | interface AppendKind 6 | extends Hkt<[element: element, to: readonly element[]]> { 7 | body: [...this[1], this[0]] 8 | } 9 | 10 | it("base", () => { 11 | type result = Hkt.apply 12 | attest<[0, 1, 2], result>() 13 | }) 14 | 15 | it("type error on unsatisfied constraint", () => { 16 | // @ts-expect-error 17 | attest((t: Hkt.apply) => t).type.errors( 18 | "Type 'number' is not assignable to type 'readonly unknown[]'" 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /ark/util/__tests__/overloads.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import type { array, conform, overloadOf } from "@ark/util" 3 | 4 | type fn = { 5 | (): void 6 | (a?: 1): 1 7 | (a: 2, b: 2): 2 8 | } 9 | 10 | const pipe = unknown, args extends array>( 11 | args: conform>>, 12 | f: fn 13 | ): ReturnType> => f(...args) as never 14 | 15 | contextualize(() => { 16 | it("parameters", () => { 17 | const T = {} as Parameters> 18 | attest<[a: 2, b: 2] | [a?: 1 | undefined] | []>(T) 19 | }) 20 | 21 | it("returns", () => { 22 | const T = {} as ReturnType> 23 | attest(T) 24 | }) 25 | 26 | it("overload return", () => { 27 | type limit = ((s: string) => string) & ((n: number) => number) 28 | type fromNumber = ReturnType> 29 | attest() 30 | // currently doesn't work for subtypes 31 | // type fromString = overloadOf 32 | // attest() 33 | }) 34 | 35 | it("()=>never", () => { 36 | type result = Parameters< 37 | overloadOf<{ 38 | (): void 39 | (a?: 1): 1 40 | (a: 2, b: 2): 2 41 | (): never 42 | }> 43 | > 44 | attest<[a: 2, b: 2] | [a?: 1 | undefined] | [], result>() 45 | }) 46 | 47 | it("pipe", () => { 48 | const limit = (_ => _) as ((s: string) => string) & ((n: number) => number) 49 | const n = pipe([5], limit) 50 | attest(n) 51 | const s = pipe(["foo"], limit) 52 | attest(s) 53 | // @ts-expect-error 54 | attest(() => pipe([], limit)) 55 | // @ts-expect-error 56 | attest(() => pipe(["foo", "bar"], limit)) 57 | // @ts-expect-error 58 | attest(() => pipe([true], limit)) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /ark/util/__tests__/path.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { ReadonlyPath } from "@ark/util" 3 | 4 | contextualize(() => { 5 | it("creates an array of a single number", () => { 6 | const path = new ReadonlyPath(5) 7 | 8 | attest([...path]).snap([5]) 9 | }) 10 | 11 | it("arary methods preserve subclass", () => { 12 | const path = new ReadonlyPath() 13 | 14 | attest(path.slice()).instanceOf(ReadonlyPath) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /ark/util/__tests__/registry.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { readPackageJson } from "@ark/fs" 3 | import { arkUtilVersion } from "@ark/util" 4 | 5 | contextualize(() => { 6 | it("version matches package.json", () => { 7 | const { version } = readPackageJson() 8 | attest(arkUtilVersion).equals(version) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /ark/util/__tests__/string.test.ts: -------------------------------------------------------------------------------- 1 | import { attest, contextualize } from "@ark/attest" 2 | import { anchoredSource, deanchoredSource } from "@ark/util" 3 | 4 | contextualize(() => { 5 | it("anchors a simple source correctly", () => { 6 | const source = "abc" 7 | const anchored = anchoredSource(source) 8 | attest(anchored).equals("^(?:abc)$") 9 | }) 10 | 11 | it("re-anchors an already anchored source correctly", () => { 12 | const source = "^abc$" 13 | const anchored = anchoredSource(source) 14 | attest(anchored).equals("^(?:^abc$)$") 15 | }) 16 | 17 | it("anchors a source with union correctly", () => { 18 | const source = "abc|def" 19 | const anchored = anchoredSource(source) 20 | attest(anchored).equals("^(?:abc|def)$") 21 | }) 22 | 23 | it("deanchors an anchored source correctly", () => { 24 | const source = "^(?:abc)$" 25 | const deanchored = deanchoredSource(source) 26 | attest(deanchored).equals("abc") 27 | }) 28 | 29 | it("deanchors a complex anchored source with union correctly", () => { 30 | const source = "^(?:abc|def)$" 31 | const deanchored = deanchoredSource(source) 32 | attest(deanchored).equals("abc|def") 33 | }) 34 | 35 | it("deanchors a re-anchored source correctly", () => { 36 | const source = "^(?:^abc$)$" 37 | const deanchored = deanchoredSource(source) 38 | attest(deanchored).equals("^abc$") 39 | }) 40 | 41 | it("leaves a non-anchored source unchanged when deanchoring", () => { 42 | const source = "abc" 43 | const deanchored = deanchoredSource(source) 44 | attest(deanchored).equals("abc") 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /ark/util/clone.ts: -------------------------------------------------------------------------------- 1 | import { getBuiltinNameOfConstructor } from "./objectKinds.ts" 2 | 3 | /** Shallowly copy the properties of the object. */ 4 | export const shallowClone: ( 5 | input: input 6 | ) => input = input => _clone(input, null) 7 | 8 | /** Deeply copy the properties of the a non-subclassed Object, Array or Date.*/ 9 | export const deepClone = (input: input): input => 10 | _clone(input, new Map()) 11 | 12 | const _clone = (input: unknown, seen: Map | null): any => { 13 | if (typeof input !== "object" || input === null) return input 14 | if (seen?.has(input)) return seen.get(input) 15 | 16 | const builtinConstructorName = getBuiltinNameOfConstructor(input.constructor) 17 | 18 | if (builtinConstructorName === "Date") 19 | return new Date((input as Date).getTime()) 20 | 21 | // we don't try and clone other prototypes here since this we can't guarantee arrow functions attached to the object 22 | // are rebound in case they reference `this` (see https://x.com/colinhacks/status/1818422039210049985) 23 | if (builtinConstructorName && builtinConstructorName !== "Array") return input 24 | 25 | const cloned = 26 | Array.isArray(input) ? 27 | input.slice() 28 | : Object.create(Object.getPrototypeOf(input)) 29 | 30 | const propertyDescriptors = Object.getOwnPropertyDescriptors(input) 31 | 32 | if (seen) { 33 | seen.set(input, cloned) 34 | for (const k in propertyDescriptors) { 35 | const desc = propertyDescriptors[k] 36 | 37 | if ("get" in desc || "set" in desc) continue 38 | desc.value = _clone(desc.value, seen) 39 | } 40 | } 41 | 42 | Object.defineProperties(cloned, propertyDescriptors) 43 | 44 | return cloned 45 | } 46 | -------------------------------------------------------------------------------- /ark/util/errors.ts: -------------------------------------------------------------------------------- 1 | import type { brand } from "./generics.ts" 2 | 3 | export class InternalArktypeError extends Error {} 4 | 5 | export const throwInternalError: (message: string) => never = message => 6 | throwError(message, InternalArktypeError) 7 | 8 | export const throwError: ( 9 | message: string, 10 | ctor?: new (message: string) => Error 11 | ) => never = (message, ctor = Error) => { 12 | throw new ctor(message) 13 | } 14 | 15 | export class ParseError extends Error { 16 | readonly name = "ParseError" 17 | } 18 | 19 | export const throwParseError: (message: string) => never = message => 20 | throwError(message, ParseError) 21 | 22 | /** 23 | * TypeScript won't suggest strings beginning with a space as properties. 24 | * Useful for symbol-like string properties. 25 | */ 26 | export const noSuggest = (s: s): noSuggest => ` ${s}` 27 | 28 | /** 29 | * TypeScript won't suggest strings beginning with a space as properties. 30 | * Useful for symbol-like string properties. 31 | */ 32 | export type noSuggest = ` ${s}` 33 | 34 | /** "Hair Space" character, used as a non-rendered sentinel for an error message string: 35 | * https://www.compart.com/en/unicode/U+200A 36 | */ 37 | export const zeroWidthSpace = " " 38 | 39 | /** "Hair Space" character, used as a non-rendered sentinel for an error message string: 40 | * https://www.compart.com/en/unicode/U+200A 41 | */ 42 | export type ZeroWidthSpace = typeof zeroWidthSpace 43 | 44 | export type ErrorMessage = 45 | `${message}${ZeroWidthSpace}` 46 | 47 | export interface ErrorType< 48 | message extends string = string, 49 | ctx extends {} = {} 50 | > { 51 | [brand]: "ErrorType" 52 | message: message 53 | ctx: ctx 54 | } 55 | 56 | export type Completion = 57 | `${text}${ZeroWidthSpace}${ZeroWidthSpace}` 58 | -------------------------------------------------------------------------------- /ark/util/get.ts: -------------------------------------------------------------------------------- 1 | import type { conform } from "./generics.ts" 2 | import type { keyOf } from "./records.ts" 3 | 4 | type getKey = 5 | k extends keyof o ? o[k] 6 | : k extends `${infer n extends number & keyof o}` ? o[n] 7 | : never 8 | 9 | type getPath = 10 | path extends `${infer head}.${infer tail}` ? getPath, tail> 11 | : getKey 12 | 13 | type validatePath = 14 | path extends `${infer head}.${infer tail}` ? 15 | head extends keyOf ? 16 | validatePath, tail, `${prefix}${head}.`> 17 | : `Key '${head}' is not valid following '${prefix}'` 18 | : { 19 | // find suffixes that would make the segment valid 20 | [k in keyOf]: k extends `${path}${string}` ? `${prefix}${k}` : never 21 | }[keyOf] 22 | 23 | export const get = ( 24 | data: o, 25 | pathStr: conform> 26 | ): getPath => { 27 | let target: any = data 28 | const path = pathStr.split(".") 29 | while (path.length) target = target[path.shift()!] 30 | return target 31 | } 32 | -------------------------------------------------------------------------------- /ark/util/hkt.ts: -------------------------------------------------------------------------------- 1 | import { noSuggest } from "./errors.ts" 2 | 3 | const args = noSuggest("args") 4 | type args = typeof args 5 | 6 | export abstract class Hkt { 7 | declare [args]: unknown[] 8 | declare constraints: constraints 9 | declare args: this[args] extends infer args extends unknown[] ? args : never 10 | declare 0: this[args] extends [infer arg, ...any] ? arg : never 11 | declare 1: this[args] extends [any, infer arg, ...any] ? arg : never 12 | declare 2: this[args] extends [any, any, infer arg, ...any] ? arg : never 13 | declare 3: this[args] extends [any, any, any, infer arg, ...any] ? arg : never 14 | abstract body: unknown 15 | 16 | declare description?: string 17 | 18 | constructor() {} 19 | } 20 | 21 | /** A small set of HKT utility types based on https://github.com/gvergnaud/hotscript 22 | * See https://github.com/gvergnaud/hotscript/blob/main/src/internals/core/Core.ts 23 | */ 24 | export declare namespace Hkt { 25 | export type constructor = 26 | new () => Hkt 27 | 28 | export type args = typeof args 29 | 30 | export type apply< 31 | hkt extends Hkt, 32 | args extends { [i in keyof args]: hkt["constraints"][i] } 33 | > = (hkt & { 34 | [args]: args 35 | })["body"] 36 | } 37 | -------------------------------------------------------------------------------- /ark/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./arrays.ts" 2 | export * from "./clone.ts" 3 | export * from "./describe.ts" 4 | export * from "./domain.ts" 5 | export * from "./errors.ts" 6 | export * from "./flatMorph.ts" 7 | export * from "./functions.ts" 8 | export * from "./generics.ts" 9 | export * from "./hkt.ts" 10 | export * from "./intersections.ts" 11 | export * from "./isomorphic.ts" 12 | export * from "./keys.ts" 13 | export * from "./lazily.ts" 14 | export * from "./numbers.ts" 15 | export * from "./objectKinds.ts" 16 | export * from "./path.ts" 17 | export * from "./primitive.ts" 18 | export * from "./records.ts" 19 | export * from "./registry.ts" 20 | export * from "./scanner.ts" 21 | export * from "./serialize.ts" 22 | export * from "./strings.ts" 23 | export * from "./traits.ts" 24 | export * from "./unionToTuple.ts" 25 | -------------------------------------------------------------------------------- /ark/util/isomorphic.ts: -------------------------------------------------------------------------------- 1 | // based on the util of the same name in @ark/fs 2 | // isolated here for use with registry 3 | 4 | import type { autocomplete } from "./generics.ts" 5 | 6 | /** get a CJS/ESM compatible string representing the current file */ 7 | const fileName = (): string => { 8 | try { 9 | const error = new Error() 10 | const stackLine = error.stack?.split("\n")[2]?.trim() || "" // [1]=this func, [2]=caller 11 | const filePath = 12 | stackLine.match(/\(?(.+?)(?::\d+:\d+)?\)?$/)?.[1] || "unknown" 13 | return filePath.replace(/^file:\/\//, "") 14 | } catch { 15 | return "unknown" 16 | } 17 | } 18 | 19 | type ArkKnownEnvVar = "ARK_DEBUG" 20 | 21 | const env: Record< 22 | autocomplete, 23 | string | undefined 24 | > = (globalThis.process?.env as never) ?? {} 25 | 26 | export const isomorphic = { 27 | fileName, 28 | env 29 | } 30 | -------------------------------------------------------------------------------- /ark/util/keys.ts: -------------------------------------------------------------------------------- 1 | import type { array, join } from "./arrays.ts" 2 | import type { NonNegativeIntegerLiteral } from "./numbers.ts" 3 | 4 | export type Key = string | symbol 5 | 6 | export type toArkKey = 7 | k extends number ? 8 | [o, number] extends [array, k] ? 9 | NonNegativeIntegerLiteral 10 | : `${k}` 11 | : k 12 | 13 | export type arkIndexableOf = 14 | arkKeyOf extends infer k ? 15 | k extends `${infer index extends number}` ? 16 | index | k 17 | : k 18 | : never 19 | 20 | export type arkKeyOf = 21 | [o] extends [object] ? 22 | [o] extends [array] ? 23 | arkArrayKeyOf 24 | : arkObjectLiteralKeyOf 25 | : never 26 | 27 | type arkArrayKeyOf = 28 | number extends a["length"] ? NonNegativeIntegerLiteral 29 | : keyof a extends infer i ? 30 | i extends `${number}` ? 31 | i 32 | : never 33 | : never 34 | 35 | type arkObjectLiteralKeyOf = 36 | keyof o extends infer k ? 37 | k extends number ? 38 | `${k}` 39 | : k 40 | : never 41 | 42 | export type arkGet> = o[k extends keyof o ? k 43 | : NonNegativeIntegerLiteral extends k ? number & keyof o 44 | : k extends number ? `${k}` & keyof o 45 | : never] 46 | 47 | export type writeInvalidKeysMessage< 48 | o extends string, 49 | keys extends array 50 | > = `Key${keys["length"] extends 1 ? "" : "s"} ${join} ${keys["length"] extends 1 ? "does" : "do"} not exist on ${o}` 51 | -------------------------------------------------------------------------------- /ark/util/lazily.ts: -------------------------------------------------------------------------------- 1 | export const lazily = (thunk: () => t): t => { 2 | let cached: any 3 | return new Proxy({} as t, { 4 | get: (_, prop) => { 5 | if (!cached) cached = thunk() 6 | 7 | return cached[prop as keyof t] 8 | }, 9 | set: (_, prop, value) => { 10 | if (!cached) cached = thunk() 11 | 12 | cached[prop] = value 13 | return true 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /ark/util/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ark/util", 3 | "version": "0.46.0", 4 | "license": "MIT", 5 | "author": { 6 | "name": "David Blass", 7 | "email": "david@arktype.io", 8 | "url": "https://arktype.io" 9 | }, 10 | "type": "module", 11 | "main": "./out/index.js", 12 | "types": "./out/index.d.ts", 13 | "exports": { 14 | ".": { 15 | "ark-ts": "./index.ts", 16 | "default": "./out/index.js" 17 | }, 18 | "./internal/*": { 19 | "ark-ts": "./*", 20 | "default": "./out/*" 21 | }, 22 | "./tsconfig.base.json": "./tsconfig.base.json" 23 | }, 24 | "files": [ 25 | "out", 26 | "tsconfig.base.json" 27 | ], 28 | "scripts": { 29 | "build": "ts ../repo/build.ts", 30 | "test": "ts ../repo/testPackage.ts" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ark/util/primitive.ts: -------------------------------------------------------------------------------- 1 | import type { inferDomain } from "./domain.ts" 2 | import type { BigintLiteral } from "./numbers.ts" 3 | 4 | type SerializedString = `"${value}"` 5 | 6 | export type SerializedPrimitives = { 7 | string: SerializedString 8 | number: `${number}` 9 | bigint: BigintLiteral 10 | boolean: "true" | "false" 11 | null: "null" 12 | undefined: "undefined" 13 | } 14 | 15 | export type SerializedPrimitive = 16 | SerializedPrimitives[keyof SerializedPrimitives] 17 | 18 | export type SerializablePrimitive = inferDomain 19 | 20 | export const serializePrimitive = ( 21 | value: value 22 | ): serializePrimitive => 23 | (typeof value === "string" ? JSON.stringify(value) 24 | : typeof value === "bigint" ? `${value}n` 25 | : `${value}`) as never 26 | 27 | export type serializePrimitive = 28 | value extends string ? `"${value}"` 29 | : value extends bigint ? `${value}n` 30 | : `${value}` 31 | -------------------------------------------------------------------------------- /ark/util/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "target": "ES2022", 5 | "moduleResolution": "NodeNext", 6 | "lib": ["ESNext"], 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noUncheckedSideEffectImports": true, 11 | "declaration": true, 12 | "verbatimModuleSyntax": true, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "exactOptionalPropertyTypes": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "stripInternal": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "ark/*" 3 | 4 | catalog: 5 | typescript: 5.8.2 6 | "@ark/attest-ts-min": npm:typescript@5.1.6 7 | "@ark/attest-ts-next": npm:typescript@next 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./ark/util/tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "rewriteRelativeImportExtensions": true, 6 | "customConditions": ["ark-ts"], 7 | "types": ["mocha", "node"] 8 | // "noErrorTruncation": true 9 | // disabling until compatible with tsgo 10 | // "erasableSyntaxOnly": true 11 | }, 12 | "exclude": ["**/out", "**/node_modules", "./ark/docs", "**/*.scratch.ts"] 13 | } 14 | --------------------------------------------------------------------------------