lay-sing
v0.4.2
Published
Utilities for compile-time type testing
Downloads
172
Readme
Lay-Sing
TypeScript utilities for compile-time type testing and utility types
// They do nothing at runtime
expect<never>().to.be<never>().pass
expect<never>().to.be.never // alias for the above
expect<never>().to.be<'should fail'>().fail
// Type Error: Property 'pass' does not exist on type '{ fail: void; }'.
expect<never>().to.be<'should fail'>().pass
// ^^^^^^^[!TIP]
I know this library is quite simple and serves a specific purpose, so one of its API design principles is to minimize the cognitive load for users. You just need to remember to start with an
expect<>()call and end with some property access. Leave the rest to editor suggestions and inline documentation.
Install & Import
npm i -D lay-singimport { expect } from 'lay-sing'From NPM
deno add npm:lay-singimport { expect } from 'lay-sing'From JSR
This library is also published to JSR (@leawind/lay-sing)
deno add @leawind/lay-singimport { expect } from '@leawind/lay-sing'From Latest commit
import { expect } from 'https://raw.githubusercontent.com/Leawind/lay-sing/refs/heads/main/src/main/index.ts'
import { Exact } from 'https://raw.githubusercontent.com/Leawind/lay-sing/refs/heads/main/src/utils/index.ts'Usage
import { expect } from 'lay-sing'The main module provides utilities for compile-time type validation. These utilities have no runtime impact — they always return a special NOOP value that safely supports almost any property access or method call.
A typical type test statement follows this pattern:
expect<ActualType>().to.be<ExpectedType>().pass- It starts with a function call like
expect<T>()orcompare<T, U>() - It ends with a property like
.passor.fail - Type error occurs only if the assertion fails
[!CAUTION]
Only statements ending with property access are type assertions. Without property access, type error may never occur:
- expect<true>().to.be<false>() // Type error never occur + expect<true>().to.be<false>().pass // Type Error: Property 'pass' does not exist on type '{ fail: void; }'.
At runtime, the function always returns the NOOP object, which performs no operation. It can be accessed, called, or chained indefinitely without throwing errors.
Common Usage
// Passes only if A and B are identical
expect<keyof { a: 2 }>().to.be<'a'>().pass
// Passes if A extends B
expect<12138>().to.extend<number>().pass
// Passes if mutually assignable
expect<{ a: 1; b: 2 }>().to.equal<{ a: 1 } & { b: 2 }>().pass
// Test property existence
expect<{ name: string }>().to.haveKey<'name'>().passAliases:
expect<never>().to.be<never>().pass
expect<never>().to.be.never
expect<'hello'>().to.extend<string>().pass
expect<'hello'>().to.extend.stringNOOP
A Proxy-based no-op object with the following behavior:
- Most property/method accesses return the NOOP object itself.
.toString(),.valueOf()returns string"[NOOP]".- Not thenable (
thenisundefined).
It's used as returned value of expect() and compare().
expect().foo.bar().baz.qux // Safe, returns NOOP
String(NOOP) // "[NOOP]"
await NOOP // Does not await (not thenable)Type Tools
It provides some utility types organized into categories for common type-level programming tasks. These can be imported from the lay-sing/utils entry point.
import type { Exact, Extends, Overlap } from 'lay-sing/utils'Examples
// Import the utility types
import type { ConcatTuple, Exact, If, KeysOfBaseType } from '@leawind/lay-sing/utils'
// Test if exactly the same
type False = Exact<{ a: 1 }, { a?: 1 }> // false
type Yes = Exact<boolean, true | false, 'yes', 'no'> // 'yes'
// Conditional Types
type Result = If<true, 'yes', 'no'> // 'yes'
type FailResult = If<Exact<number, string>, 'yes', 'no'> // 'no'
// Tuple Manipulation
type Combined = ConcatTuple<[1, 2], [3, 4]> // [1, 2, 3, 4]
type UniqueCombined = ConcatUniqueTuple<[1, 2], [2, 3]> // [1, 2, 3]