# @marianmeres/dtokit - Machine-Readable Package Report

## PACKAGE METADATA

name: @marianmeres/dtokit
version: 1.0.0
license: MIT
author: Marian Meres
repository: https://github.com/marianmeres/dtokit
platforms: Deno, Node.js (via npm)
dependencies: none (zero external dependencies)
typescript: required (heavy use of type system)

## PACKAGE PURPOSE

DTOKit is a type-safe factory library for working with discriminated unions in TypeScript.
It is specifically designed to work with OpenAPI-generated types (e.g., from openapi-typescript).

Primary use cases:
- Parsing unknown data (WebSocket messages, postMessage, API responses) into typed DTOs
- Type-safe narrowing using discriminator field patterns
- Exhaustive handling of all message types with compile-time guarantees
- Minimal runtime validation with maximum type safety

## INSTALLATION

```bash
# Deno (JSR)
deno add @marianmeres/dtokit

# Node.js (npm)
npm install @marianmeres/dtokit
```

## FILE STRUCTURE

```
src/
  mod.ts          # Main export barrel (re-exports from dtokit.ts)
  dtokit.ts       # Core implementation (~590 lines with JSDoc)
tests/
  dtokit.test.ts  # Comprehensive test suite (33 tests)
scripts/
  build-npm.ts    # NPM distribution build script
```

## PUBLIC API EXPORTS

### Functions

1. createDtoFactory<Schemas>()
   - Signature: <Schemas>() => <Field extends string>(discriminatorField: Field) => DtoFactory<Schemas, Field>
   - Purpose: Creates a type-safe DTO factory for parsing and type-narrowing
   - Curried: Yes (allows specifying Schemas type while inferring Field)
   - Returns: DtoFactory instance

2. createDtoHandler<Schemas>()
   - Signature: <Schemas>() => <Field extends string, R>(discriminatorField: Field, handlers: DtoHandlers<Schemas, Field, R>) => (dto: AnyDto<Schemas, Field>) => R
   - Purpose: Creates exhaustive switch-style handler for all DTO types
   - Curried: Yes
   - Returns: Handler function

### Interfaces

1. DtoFactory<Schemas, Field>
   - Properties:
     - field: Field (readonly) - The discriminator field name
   - Methods:
     - parse(raw: unknown): AnyDto<Schemas, Field> | null
     - is<K>(dto, id: K): dto is DiscriminatorMap<Schemas, Field>[K]
     - getId(dto): DiscriminatorId<Schemas, Field>
     - isValid(raw: unknown): boolean

### Type Utilities

1. HasLiteralField<T, Field>
   - Filters types with literal string discriminator field
   - Returns T if valid, never otherwise

2. ExtractFieldValue<T, Field>
   - Extracts literal string value from discriminator field
   - Returns the literal type or never

3. DiscriminatedKey<Schemas, Field>
   - Returns union of schema keys that have literal discriminator
   - Example: "MessageA" | "MessageB"

4. DiscriminatorMap<Schemas, Field>
   - Maps discriminator values to their corresponding types
   - Example: { "msg_a": MessageA; "msg_b": MessageB }

5. DiscriminatorId<Schemas, Field>
   - Union of all discriminator values
   - Example: "msg_a" | "msg_b" | "msg_c"

6. AnyDto<Schemas, Field>
   - Union of all DTO types matching the discriminator pattern
   - Example: MessageA | MessageB | MessageC

7. DtoHandlers<Schemas, Field, R>
   - Type for exhaustive handler objects
   - Maps each discriminator ID to handler function

## USAGE PATTERNS

### Basic Factory Creation
```typescript
import { createDtoFactory } from '@marianmeres/dtokit';
import type { components } from './api-types';

const messages = createDtoFactory<components['schemas']>()('id');
```

### Parsing Unknown Data
```typescript
const dto = messages.parse(rawData);
if (dto) {
  // dto is typed as union of all matching schemas
}
```

### Type Narrowing with Guards
```typescript
if (dto && messages.is(dto, 'bar')) {
  // dto is narrowed to BarMessage type
  console.log(dto.text);
}
```

### Switch Pattern with getId
```typescript
switch (messages.getId(dto)) {
  case 'start': handleStart(); break;
  case 'stop': handleStop(); break;
}
```

### Exhaustive Handler
```typescript
const handle = createDtoHandler<components['schemas']>()('id', {
  start: (dto) => { /* TypeScript knows exact type */ },
  stop: (dto) => { /* ... */ },
  // ERROR if any type is missing
});
```

## VALIDATION BEHAVIOR

The factory performs MINIMAL validation:
- Checks raw is non-null object
- Checks discriminator field exists
- Checks discriminator value is non-empty string

Does NOT validate:
- Full object structure
- Required fields beyond discriminator
- Field types

This is intentional for:
- Performance (minimal runtime overhead)
- Trust of upstream data (your own backend)
- OpenAPI types as source of truth

## TYPE SYSTEM REQUIREMENTS

- Schemas must have literal string discriminator values (not generic `string`)
- Example valid: `{ id: "my_message" }` (literal type)
- Example invalid: `{ id: string }` (generic type - excluded)

## DESIGN PATTERNS

1. Factory Pattern - Creates configured instances
2. Type Guard Pattern - Runtime checks with type narrowing
3. Discriminated Union Pattern - TypeScript's tagged union support
4. Curried Function Pattern - Partial type inference workaround
5. Exhaustive Handling Pattern - Compile-time completeness checks

## TEST COVERAGE

33 tests covering:
- Factory creation
- isValid() validation (null, undefined, non-objects, missing field, wrong types)
- parse() behavior (valid/invalid inputs, reference preservation)
- is() type guard (matching, non-matching, type narrowing)
- getId() extraction and switch usage
- Different discriminator fields
- createDtoHandler exhaustive handling
- Type utility compile-time checks
- Edge cases (extra properties, nested objects, whitespace)
- Real-world simulation (WebSocket, exhaustive handlers)

## KEYWORDS

typescript, dto, discriminated-union, type-safety, openapi, factory-pattern,
type-guard, exhaustive-check, websocket, parsing, validation, zero-dependencies
