Product
Introducing License Enforcement in Socket
Ensure open-source compliance with Socket’s License Enforcement Beta. Set up your License Policy and secure your software!
@arktype/attest
Advanced tools
Attest is a testing library that makes your TypeScript types available at runtime, giving you access to precise type-level assertions and performance benchmarks.
Attest is a testing library that makes your TypeScript types available at runtime, giving you access to precise type-level assertions and performance benchmarks.
Assertions are framework agnostic and can be seamlessly integrated with your existing Vitest, Jest, or Mocha tests.
Benchmarks can run from anywhere and will deterministically report the number of type instantiations contributed by the contents of the bench
call.
If you've ever wondered how ArkType can guarantee identical behavior between its runtime and static parser implementations and highly optimized editor performance, Attest is your answer⚡
npm install @arktype/attest
Note: This package is still in alpha! Your feedback will help us iterate toward a stable 1.0.
To use attest's type assertions, you'll need to call our setup/cleanup methods before your first test and after your last test, respectively. This usually involves some kind of globalSetup/globalTeardown config.
For example, in mocha:
import { cleanup, setup } from "@arktype/attest"
export const mochaGlobalSetup = setup
export const mochaGlobalTeardown = cleanup
Bun support is currently pending a bug in the way their source maps translate to stack traces.
Here are some simple examples of type assertions and snapshotting:
// @arktype/attest assertions can be made from any unit test framework with a global setup/teardown
describe("attest features", () => {
it("type and value assertions", () => {
const even = type("number%2")
// asserts even.infer is exactly number
attest<number>(even.infer)
// make assertions about types and values seamlessly
attest(even.infer).type.toString.snap("number")
// including object literals- no more long inline strings!
attest(even.json).snap({
intersection: [{ domain: "number" }, { divisor: 2 }]
})
})
it("error assertions", () => {
// Check type errors, runtime errors, or both at the same time!
// @ts-expect-error
attest(() => type("number%0")).throwsAndHasTypeError(
"% operator must be followed by a non-zero integer literal (was 0)"
)
// @ts-expect-error
attest(() => type({ "[object]": "string" })).type.errors(
"Indexed key definition 'object' must be a string, number or symbol"
)
})
it("completion snapshotting", () => {
// snapshot expected completions for any string literal!
// @ts-expect-error (if your expression would throw, prepend () =>)
attest(() => type({ a: "a", b: "b" })).completions({
a: ["any", "alpha", "alphanumeric"],
b: ["bigint", "boolean"]
})
type Legends = { faker?: "🐐"; [others: string]: unknown }
// works for keys or index access as well (may need prettier-ignore to avoid removing quotes)
// prettier-ignore
attest({ "f": "🐐" } as Legends).completions({ "f": ["faker"] })
})
it("integrate runtime logic with type assertions", () => {
const arrayOf = type("<t>", "t[]")
const numericArray = arrayOf("number | bigint")
// flexibly combine runtime logic with type assertions to customize your
// tests beyond what is possible from pure static-analysis based type testing tools
if (getTsVersionUnderTest().startsWith("5")) {
// this assertion will only occur when testing TypeScript 5+!
attest<(number | bigint)[]>(numericArray.infer)
}
})
})
Benches are run separately from tests and don't require any special setup. If the below file was benches.ts
, you could run it using something like tsx benches.ts
or ts-node benches.ts
:
// Combinatorial template literals often result in expensive types- let's benchmark this one!
type makeComplexType<s extends string> = s extends `${infer head}${infer tail}`
? head | tail | makeComplexType<tail>
: s
bench("bench type", () => {
return {} as makeComplexType<"defenestration">
// This is an inline snapshot that will be populated or compared when you run the file
}).types([169, "instantiations"])
bench(
"bench runtime and type",
() => {
return {} as makeComplexType<"antidisestablishmentarianism">
},
fakeCallOptions
)
// Average time it takes the function execute
.mean([2, "ms"])
// Seems like our type is O(n) with respect to the length of the input- not bad!
.types([337, "instantiations"])
If you're a library author wanting to integrate type into your own assertions instead of using the attest
API, you'll need to call setup
with a list of attestAliases
to ensure type data is collected from your own functions:
// attest will only collect type data from functions with names listed in `attestAliases`
setup({ attestAliases: ["yourCustomAssert"] })
// There are many other config options, but some are primarily internal- use others at your own risk!
You'll need to make sure that setup with whatever aliases you need before the first test runs. As part of the setup process, attest will search for the specified assertion calls and cache their types in a temporary file that will be referenced during test execution.
This ensures that type assertions can be made across processes without creating a new TSServer instance for each.
Attest provides utilities for testing your code against multiple versions of TypeScript. This is particularly useful for library authors who want to ensure compatibility across different TypeScript versions.
For example, here's the script ArkType uses to ensure consistency across TS versions in CI:
import { forEachTypeScriptVersion } from "@arktype/attest"
import { shell } from "@arktype/fs"
forEachTypeScriptVersion(() => {
shell("pnpm test")
})
By default, forEachTypeScriptVersion
will look for TypeScript versions in node_modules
with the prefix attest-ts
. This way, you can maintain a list of versions you'd like to support that will be installed alongside your other dev dependencies and referenced only during these tests:
"devDependencies": {
"typescript": "5.3.2",
"attest-ts50": "npm:typescript@5.0",
"attest-ts51": "npm:typescript@5.1",
"attest-ts52": "npm:typescript@5.2",
"attest-ts53": "npm:typescript@5.3",
"attest-ts54": "npm:typescript@next"
}
Otherwise, you can pass a custom list of directories containing TS installations to forEachTypeScriptVersion
's optional second parameter.
The most flexible attest APIs are getAssertionDataAtPosition
and caller
.
Here's an example of how you might use them in your own API:
import { getAssertionDataAtPosition, caller } from "@arktype/attest"
const yourCustomAssert = <expectedType>(actualValue: expectedType) => {
const position = caller()
const types = getAssertionDataAtPosition(position)
// assert that the type of actualValue is the same as the type of expectedType
const relationship = types.args[0].relationships.typeArgs[0]
if (relationship === undefined) {
throw new Error(
`yourCustomAssert requires a type arg representing the expected type, e.g. 'yourCustomAssert<"foo">("foo")'`
)
}
if (relationship !== "equality") {
throw new Error(
`Expected ${types.typeArgs[0].type}, got ${types.args[0].type} with relationship ${relationship}`
)
}
}
A user might then use yourCustomAssert
like this:
import { yourCustomAssert } from "your-package"
test("my code", () => {
// Ok
yourCustomAssert<"foo">(`${"f"}oo` as const)
// Error: `Expected boolean, got true with relationship subtype`
yourCustomAssert<boolean>(true)
// Error: `Expected 5, got number with relationship supertype`
yourCustomAssert<5>(2 + 3)
})
Please don't hesitate to a GitHub issue or discussion or reach out on ArkType's Discord if you have any questions or feedback- we'd love to hear from you! ⛵
FAQs
Attest is a testing library that makes your TypeScript types available at runtime, giving you access to precise type-level assertions and performance benchmarks.
The npm package @arktype/attest receives a total of 728 weekly downloads. As such, @arktype/attest popularity was classified as not popular.
We found that @arktype/attest demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Ensure open-source compliance with Socket’s License Enforcement Beta. Set up your License Policy and secure your software!
Product
We're launching a new set of license analysis and compliance features for analyzing, managing, and complying with licenses across a range of supported languages and ecosystems.
Product
We're excited to introduce Socket Optimize, a powerful CLI command to secure open source dependencies with tested, optimized package overrides.