Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 16 additions & 34 deletions .github/workflows/main.yml → .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ name: CI

on:
push:
branches:
- main
tags:
- 'v*'
branches: ['main']
tags: ['v*']
pull_request:
branches: ['*']

Expand All @@ -17,49 +15,35 @@ concurrency:

jobs:
main:
name: Validate and Deploy
name: CI Pipeline
runs-on: ubuntu-latest

env:
CI: true

steps:
- name: Setup timezone
uses: zcong1993/setup-timezone@master
with:
timezone: America/Sao_Paulo

- name: Setup repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
version: 10

- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
version: 8
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
node-version: 22
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'

- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Upgrade npm
run: npm install -g npm@11

- name: Install Packages
- name: Install dependencies
run: pnpm install
timeout-minutes: 3

- name: Validate
if: "!startsWith(github.ref, 'refs/tags/')"
Expand All @@ -75,6 +59,4 @@ jobs:

- name: Publish Package
if: startsWith(github.ref, 'refs/tags/')
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance
115 changes: 115 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**is-lite** is a lightweight JavaScript type-checking library (< 2kB) with TypeScript type guards. It provides runtime type validation with automatic type narrowing in conditionals.

## Code Architecture

### Dual Export Strategy

The library provides two export patterns:

1. **Default export** (`src/index.ts`): Main `is` object with all checkers as methods
- Used as: `is.string(value)` or `is(value)` to get type name
- Single import, namespaced API

2. **Standalone functions** (`src/standalone.ts`): Individual checker functions
- Used as: `import { isString } from 'is-lite/standalone'`
- Tree-shakeable, one function per import
- Each function is independently defined for optimal tree-shaking

Both entry points are built separately via tsup (dual CJS/ESM).

### Type System

Core types in `src/types.ts`:
- `TypeName`: Union of `ObjectTypes | PrimitiveTypes` - all possible return values from `is(value)`
- `Primitive`: JS primitive types including null
- `ObjectTypes`: Const array of recognized object types ('Array', 'Date', 'Map', etc.)
- `PrimitiveTypes`: Const array of primitive type names ('string', 'number', etc.)

### Helper Functions (`src/helpers.ts`)

Core utilities for type checking:
- `getObjectType()`: Parses `Object.prototype.toString.call()` result to extract type name
- `isObjectOfType<T>(type)`: Returns type guard function checking object type
- `isOfType<T>(type)`: Returns type guard function checking `typeof` result
- `isPrimitiveType(name)`: Checks if name is in `primitiveTypes` array

These helpers create reusable type guard factories used throughout `index.ts`.

### Type Checking Patterns

All checker methods return TypeScript type guards (`value is T`), enabling type narrowing:

```ts
if (is.string(value)) {
// value is now typed as string
}
```

Special cases:
- `is.number()`: Excludes `NaN` (unlike `typeof`)
- `is.instanceOf()`: Checks direct instance, not inheritance chain
- `is.plainObject()`: Only `{}`, `new Object()`, or `Object.create(null)`
- `is.empty()`: Works with strings, arrays, objects, Maps, and Sets

## Development Commands

### Testing
- `npm test` - Auto-detects CI: runs coverage in CI, watch mode locally
- `npm run test:coverage` - Run tests with coverage report
- `npm run test:watch` - Interactive watch mode
- **Single test**: `TZ=UTC vitest run test/index.spec.ts` (or use `-t "pattern"` to filter)

### Validation
- `npm run validate` - Full validation pipeline (lint → typecheck → test → build → typevalidation → size)
- `npm run lint` - ESLint with auto-fix
- `npm run typecheck` - TypeScript compilation check
- `npm run typevalidation` - Check package exports with `@arethetypeswrong/cli`
- `npm run size` - Verify bundle size limits (main: 1.5kB, standalone: 2kB)

### Build
- `npm run build` - Clean + build dual CJS/ESM bundles
- `npm run watch` - Build in watch mode
- `npm run clean` - Remove dist directory

### Git Hooks (Husky)
- **pre-commit**: Runs `repo-tools check-remote && npm run validate` (ensures branch is up-to-date and all checks pass)
- **post-merge**: Runs `repo-tools install-packages` (auto-installs deps after merge)

## Testing Guidelines

- Test files in `test/` mirror `src/` structure
- Vitest with globals enabled (no need to import `describe`, `it`, `expect`)
- Fixtures in `test/__fixtures.ts`
- Tests must run with `TZ=UTC` environment variable
- Coverage expected for all type checkers

## Bundle Size Constraints

Strict size limits enforced via `size-limit`:
- Main bundle (index): **1.5kB** (both CJS and ESM)
- Standalone bundle: **1.5kB** (both CJS and ESM)

Build fails if bundles exceed limits. Keep implementation minimal.

## TypeScript Configuration

- Extends `@gilbarbara/tsconfig`
- Target: ES2020
- `noEmit: true` (tsup handles compilation)
- ESLint extends `@gilbarbara/eslint-config/base` and `/vitest`

## Adding New Type Checkers

When adding a new checker:

1. Add the standalone `isX` function in `src/standalone.ts` with type guard signature
2. Import and add method to `is` object in `src/index.ts`
3. Add comprehensive tests in `test/index.spec.ts` and `test/standalone.spec.ts`
4. Update README.md API documentation
5. Run full validation before commit (enforced by pre-commit hook)
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ is('value'); // string
is.string('value'); // true
```

You can also import any checker individually since 1.0
You can also import any checker individually (tree-shakeable)

```ts
import { isString } from 'is-lite/exports';
import { isString } from 'is-lite/standalone';

isString('value'); // true
```
Expand All @@ -44,7 +44,7 @@ Check if all items in an array are of the same type.

```ts
is.arrayOf(['a', 'b'], is.string); // true
is.arrayOf([123, 456], is.nnumber); // true
is.arrayOf([123, 456], is.number); // true

is.arrayOf(['a', 1], is.string); // false
```
Expand All @@ -63,6 +63,14 @@ is.asyncFunction(() => {}); // false

**is.boolean(value)**

**is.class(value)**
Check if the `value` is a class constructor (not a regular function).

```ts
is.class(class Foo {}); // true
is.class(function() {}); // false
```

**is.date(value)**

**is.defined(value)**
Expand All @@ -89,7 +97,7 @@ Check for an object that has its own .next() and .throw() methods and has a func

**is.generatorFunction(value)**

**is.instanceOf(value, class)**
**is.instanceOf(value, class)**
Check if the `value` is a direct instance of the `class`.

```ts
Expand All @@ -101,6 +109,15 @@ is.instanceOf(error, APIError); // true
is.instanceOf(error, Error); // false
```

**is.integer(value)**
Check if the `value` is an integer.

```ts
is.integer(42); // true
is.integer(42.5); // false
is.integer(NaN); // false
```

**is.iterable(value)**

**is.map(value)**
Expand All @@ -111,7 +128,18 @@ is.instanceOf(error, Error); // false

**is.nullOrUndefined(value)**

**is.number(value)**
**is.nonEmptyString(value)**
Check for a string with content after trimming whitespace.

```ts
is.nonEmptyString('hello'); // true
is.nonEmptyString(' text '); // true

is.nonEmptyString(''); // false
is.nonEmptyString(' '); // false
```

**is.number(value)**
Note: `is.number(NaN)` returns `false`

**is.numericString(value)**
Expand All @@ -120,7 +148,7 @@ Check for a string that represents a number.
```ts
is.numericString('42'); // true
is.numericString('-5'); // true
is.numericString('Inifinity'); // true
is.numericString('Infinity'); // true
is.numericString('NaN'); // true
```

Expand Down Expand Up @@ -171,6 +199,14 @@ is.propertyOf(map, 'isLogged', is.string); // false

**is.undefined(value)**

**is.url(value)**
Check if the `value` is a URL object.

```ts
is.url(new URL('https://example.com')); // true
is.url('https://example.com'); // false
```

**is.weakMap(value)**

**is.weakSet(value)**
Expand Down
3 changes: 3 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { base, vitest } from '@gilbarbara/eslint-config';

export default [...base, ...vitest];
Loading
Loading