npm package: @libar-dev/eslint-plugin-zod-convex
Description: ESLint rules for zod-convex hybrid validation patterns
Author: Libar AI
Latest version: 0.1.0

# @libar-dev/eslint-plugin-zod-convex

[![npm version](https://img.shields.io/npm/v/@libar-dev/eslint-plugin-zod-convex.svg)](https://www.npmjs.com/package/@libar-dev/eslint-plugin-zod-convex)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue)](https://www.typescriptlang.org/)

ESLint plugin that enforces the zod-convex hybrid validation pattern — a 3-layer trust boundary model that achieves **90% memory reduction** (60-65MB to 10-15MB) in Convex's 64MB runtime.

## What is the Hybrid Validation Pattern?

The hybrid validation pattern combines:
- **Zod schemas** (in `src/validation-schemas/`) — Define structure and business rules
- **Build-time generation** (via `@libar-dev/zod-convex-gen`) — Convert to Convex validators
- **Type-only imports** — Zero runtime Zod cost, full TypeScript type safety

## Installation

```bash
npm install --save-dev @libar-dev/eslint-plugin-zod-convex
```

**Peer Dependencies**:
- `eslint` ^8.0.0 || ^9.0.0
- `@typescript-eslint/parser` ^8.49.0

## Quick Start

Enable all recommended rules with a single config:

```javascript
// eslint.config.mjs
import zodConvex from '@libar-dev/eslint-plugin-zod-convex';

export default [
  {
    plugins: {
      'zod-convex': zodConvex,
    },
    rules: {
      ...zodConvex.configs.recommended.rules,
    },
  },
];
```

### Individual Rule Configuration

```javascript
// eslint.config.mjs
import zodConvex from '@libar-dev/eslint-plugin-zod-convex';

export default [
  {
    plugins: {
      'zod-convex': zodConvex,
    },
    rules: {
      'zod-convex/require-schema-type-aliases': 'error',
      'zod-convex/no-runtime-zod-in-convex': 'error',
      'zod-convex/require-api-validation': 'error',
      'zod-convex/no-layer-3-validation': 'warn',
    },
  },
];
```

## Rules

### `require-schema-type-aliases`

**Severity**: error | **Scope**: `convex/` directory

Enforces type alias exports from schema files and correct usage in consumer files.

**Problem**: Using `z.infer<typeof Schema>` with type-only imports breaks TypeScript compilation.

```typescript
// WRONG: TypeScript error
import type { MyArgsSchema } from '../../src/validation-schemas/domain/mySchema';

handler: async (ctx, args: z.infer<typeof MyArgsSchema>) => {
  // ERROR: MyArgsSchema only refers to a type, but is being used as a value
}
```

**Why**: The `typeof` operator requires a runtime value, but `import type` erases values at compile time.

**Solution**: Schema files must export type aliases, consumers import the type alias.

```typescript
// Schema file (src/validation-schemas/domain/mySchema.ts)
export const MyArgsSchema = z.object({
  userId: ids.userId(),
  data: z.string()
}).strict();

export type MyArgs = z.infer<typeof MyArgsSchema>; // REQUIRED

// Consumer file (convex/domain/myFunction.ts)
import type { MyArgs } from '../../src/validation-schemas/domain/mySchema';

handler: async (ctx, args: MyArgs) => {
  // Works perfectly - type alias is compile-time only
}
```

**Naming Convention**: The rule automatically derives type alias names by removing the `Schema` suffix:
- `FooArgsSchema` -> `FooArgs`
- `CreateInvoiceArgsSchema` -> `CreateInvoiceArgs`
- `UsersTableSchema` -> `User` (singular for table schemas)

### `no-runtime-zod-in-convex`

**Severity**: error | **Scope**: `convex/` directory

Prevents runtime Zod imports in the Convex directory (Layer 2 enforcement).

**Problem**: Convex runtime has a 64MB memory limit. Including Zod adds 20-30MB to the bundle.

**Solution**: Use hybrid validation pattern with generated validators + type-only imports.

```typescript
// WRONG: Runtime Zod in Convex (20-30MB overhead)
import { z } from 'zod';
import { zodToConvex } from '@libar-dev/zod-convex-core';

// RIGHT: Generated validators (zero Zod at runtime)
import type { MyArgs } from '../../src/validation-schemas/domain/mySchema';
import { myArgsFields } from '../generatedValidators/mySchema';

export const myMutation = mutation({
  args: myArgsFields,
  handler: async (ctx, args: MyArgs) => { ... }
});
```

**Architectural Directives**: When runtime Zod is architecturally justified (e.g., validating external component responses), use an architectural directive to suppress the warning:

```typescript
/**
 * @architectural-directive: workflow-step-validation
 * Reason: Validates external Convex component responses.
 * Impact: Requires runtime Zod for parseWithSchema() validation.
 */
import { z } from 'zod';
```

**Supported Directives** (9 total):

| Category | Directive | Use Case |
|----------|-----------|----------|
| Validation | `workflow-step-validation` | Validating external component responses |
| Validation | `validation-at-boundary` | Hybrid pattern with generated validators |
| Infrastructure | `schema-infrastructure` | Schema factory utilities |
| Infrastructure | `workflow-validation-infrastructure` | Workflow validation helpers |
| Infrastructure | `projection-validation-infrastructure` | CQRS projection validation |
| Infrastructure | `schema-composition-utility` | Zod composition utilities |
| External | `ai-response-normalization` | AI/external response validation |
| Testing | `test-infrastructure` | Testing utilities |
| Testing | `test-enforcement-safe` | Test mocks requiring runtime Zod |

### `require-api-validation`

**Severity**: error | **Scope**: `app/api/`, `pages/api/`, webhook files

Enforces Zod validation at API route boundaries (Layer 1 enforcement).

**Problem**: External API boundaries are security-critical. Unvalidated input leads to runtime errors and potential vulnerabilities.

```typescript
// WRONG: No validation on external input
export async function POST(request: Request) {
  const body = await request.json();
  await ctx.db.insert('users', body); // Dangerous: unvalidated data
}

// RIGHT: Validate all external input
export async function POST(request: Request) {
  const body = await request.json();
  const result = CreateUserSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json({ error: result.error }, { status: 400 });
  }
  await ctx.db.insert('users', result.data);
}
```

Supports Next.js App Router (`app/api/**/route.ts`), Pages Router (`pages/api/**/*.ts`), and webhook handlers.

### `no-layer-3-validation`

**Severity**: warn | **Scope**: `components/`, `hooks/`, `utils/`, `lib/`, `app/` (excluding `app/api/`)

Prevents redundant runtime validation in the trusted zone (Layer 3).

**Problem**: Frontend code should trust already-validated data from API routes (Layer 1) or Convex functions (Layer 2). Redundant validation adds bundle size without security benefit.

```typescript
// WRONG: Redundant validation in component
function UserCard({ user }: { user: unknown }) {
  const result = UserSchema.safeParse(user);
  // ...
}

// RIGHT: Trust validated data from API/Convex
import type { User } from '@/src/validation-schemas/users';

function UserCard({ user }: { user: User }) {
  // Data already validated at boundary
}
```

**Exception**: Form validation (react-hook-form, zodResolver) is acceptable for UX progressive enhancement. The rule detects form context and reports as informational instead of a warning.

## Known Limitations

- `require-schema-type-aliases`: Does not detect renamed imports (`import type { Schema as MySchema }`) or namespace imports (`import type * as schemas`)
- `require-api-validation`: Does not track validation in helper functions called by the handler; validation must be directly in the handler body
- `no-layer-3-validation`: Form detection relies on filename patterns and import heuristics
- `no-runtime-zod-in-convex`: Directive detection scans preceding 1000 characters; very long preambles may not be detected

## Related Packages

- [`@libar-dev/zod-convex-gen`](https://www.npmjs.com/package/@libar-dev/zod-convex-gen) — Build-time validator generation
- [`@libar-dev/zod-convex-core`](https://www.npmjs.com/package/@libar-dev/zod-convex-core) — Runtime Zod-to-Convex conversion
- [`@libar-dev/zod-convex-builders`](https://www.npmjs.com/package/@libar-dev/zod-convex-builders) — Auth-aware function builders

## Contributing

This package is part of the [@libar-dev/zod-convex](https://github.com/libar-dev/zod-convex-packages) monorepo. See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.

## License

MIT