# OpenAPI TypeScript Generation - Design Document

## Overview

This document describes the design for generating TypeScript OpenAPI specifications with integrated authentication support. The feature extends `gkm openapi` with a `--ts` flag that outputs a TypeScript module instead of JSON, enabling:

1. **Type-safe paths** - Full TypeScript interface for API routes
2. **Runtime auth map** - Per-endpoint authentication requirements
3. **Reusable schemas** - TypeScript interfaces extracted from Zod/Valibot schemas
4. **Security scheme definitions** - OpenAPI security schemes as typed constants

## Motivation

### Current Flow (JSON-based)

```
Endpoints → gkm openapi → openapi.json → openapi-typescript → types.d.ts
                                      ↓
                          (no auth info at runtime)
```

**Problems:**
- Auth requirements lost at runtime - OpenAPI security info exists but isn't usable by the fetcher
- Two-step generation - need external tool (`openapi-typescript`) for types
- No schema reuse - generated types are isolated, can't reference shared interfaces

### Proposed Flow (TypeScript-native)

```
Endpoints → gkm openapi --ts → openapi.ts
                                   ↓
                          Exports:
                          ├── paths (type)
                          ├── endpointAuth (runtime map)
                          ├── securitySchemes (runtime definitions)
                          └── schema interfaces (reusable types)
```

## Command Interface

```bash
# Default: generates TypeScript
gkm openapi --output ./src/api/openapi.ts

# Explicit JSON output (legacy)
gkm openapi --json --output ./openapi.json
```

### Options

| Flag | Description | Default |
|------|-------------|---------|
| `--json` | Generate JSON instead of TypeScript (legacy) | `false` |
| `--output` | Output file path | `openapi.ts` |

## Generated Output Structure

### File: `openapi.ts`

```typescript
// Auto-generated by @geekmidas/cli - DO NOT EDIT
import type { OpenAPIV3_1 } from 'openapi-types';

// ============================================================
// Security Schemes
// ============================================================

/**
 * Available security schemes for this API.
 * Maps authorizer names to OpenAPI security scheme definitions.
 */
export const securitySchemes = {
  bearer: {
    type: 'http',
    scheme: 'bearer',
    bearerFormat: 'JWT',
  },
  iam: {
    type: 'apiKey',
    in: 'header',
    name: 'Authorization',
    'x-amazon-apigateway-authtype': 'awsSigv4',
  },
  apiKey: {
    type: 'apiKey',
    in: 'header',
    name: 'X-API-Key',
  },
} as const satisfies Record<string, OpenAPIV3_1.SecuritySchemeObject>;

export type SecuritySchemeId = keyof typeof securitySchemes;

// ============================================================
// Endpoint Authentication Map
// ============================================================

/**
 * Runtime map of endpoints to their required authentication scheme.
 * `null` indicates a public endpoint (no auth required).
 */
export const endpointAuth = {
  'POST /tenants': 'iam',
  'GET /tenants/{id}': 'bearer',
  'PUT /tenants/{id}': 'bearer',
  'DELETE /tenants/{id}': 'bearer',
  'GET /health': null,
  'GET /docs': null,
} as const satisfies Record<string, SecuritySchemeId | null>;

export type AuthenticatedEndpoint = {
  [K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? never : K;
}[keyof typeof endpointAuth];

export type PublicEndpoint = {
  [K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? K : never;
}[keyof typeof endpointAuth];

// ============================================================
// Schema Definitions (Reusable Types)
// ============================================================

export interface Tenant {
  id: string;
  name: string;
  createdAt: string;
  updatedAt: string;
}

export interface CreateTenantInput {
  name: string;
}

export interface UpdateTenantInput {
  name?: string;
}

// ============================================================
// OpenAPI Paths
// ============================================================

export interface paths {
  '/tenants': {
    post: {
      requestBody: {
        content: {
          'application/json': CreateTenantInput;
        };
      };
      responses: {
        201: {
          content: {
            'application/json': Tenant;
          };
        };
      };
    };
  };
  '/tenants/{id}': {
    parameters: {
      path: {
        id: string;
      };
    };
    get: {
      responses: {
        200: {
          content: {
            'application/json': Tenant;
          };
        };
      };
    };
    put: {
      requestBody: {
        content: {
          'application/json': UpdateTenantInput;
        };
      };
      responses: {
        200: {
          content: {
            'application/json': Tenant;
          };
        };
      };
    };
    delete: {
      responses: {
        204: {
          content: never;
        };
      };
    };
  };
  '/health': {
    get: {
      responses: {
        200: {
          content: {
            'application/json': { status: string };
          };
        };
      };
    };
  };
}

// ============================================================
// Full OpenAPI Specification (Optional)
// ============================================================

export const spec = {
  openapi: '3.1.0',
  info: {
    title: 'Tenant API',
    version: '1.0.0',
  },
  paths: { /* ... */ },
  components: {
    securitySchemes,
    schemas: { /* ... */ },
  },
} as const satisfies OpenAPIV3_1.Document;
```

## Implementation Details

### 1. Authorizer to Security Scheme Mapping

The `Authorizer.type` field maps to OpenAPI security schemes:

| Authorizer Type | OpenAPI Security Scheme |
|-----------------|------------------------|
| `jwt`, `bearer` | `{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }` |
| `iam`, `aws-sigv4` | `{ type: 'apiKey', in: 'header', name: 'Authorization', 'x-amazon-apigateway-authtype': 'awsSigv4' }` |
| `apiKey` | `{ type: 'apiKey', in: metadata.in, name: metadata.name }` |
| `oauth2` | `{ type: 'oauth2', flows: { ... } }` |
| `oidc` | `{ type: 'openIdConnect', openIdConnectUrl: metadata.issuer }` |
| `none` / undefined | `null` (public endpoint) |

### 2. Schema Extraction

Schemas are extracted from StandardSchema (Zod/Valibot) definitions:

```typescript
// From endpoint definition
const endpoint = e
  .post('/tenants')
  .body(z.object({ name: z.string() }))
  .output(z.object({ id: z.string(), name: z.string() }))
  .handle(async ({ body }) => { ... });

// Extracted as
export interface CreateTenantInput {
  name: string;
}

export interface CreateTenantOutput {
  id: string;
  name: string;
}
```

### 3. Naming Strategy

| Schema Location | Generated Name |
|-----------------|----------------|
| Body schema | `{OperationId}Input` or `{Method}{Route}Input` |
| Output schema | `{OperationId}Output` or `{Method}{Route}Output` |
| Params schema | `{OperationId}Params` |
| Query schema | `{OperationId}Query` |

### 4. Component Collector Enhancement

```typescript
// packages/schema/src/openapi.ts
export interface ComponentCollector {
  schemas: Record<string, OpenAPIV3_1.SchemaObject>;
  securitySchemes: Record<string, OpenAPIV3_1.SecuritySchemeObject>;
  addSchema(id: string, schema: OpenAPIV3_1.SchemaObject): void;
  addSecurityScheme(id: string, scheme: OpenAPIV3_1.SecuritySchemeObject): void;
  getReference(id: string): OpenAPIV3_1.ReferenceObject;
}
```

## Integration with Auth-Aware Fetcher

The generated `endpointAuth` map enables automatic auth handling:

```typescript
// packages/client/src/auth-fetcher.ts
import { endpointAuth, securitySchemes, type paths } from './openapi';
import { TokenClient } from '@geekmidas/auth/client';

export function createAuthAwareFetcher<Paths>(options: AuthFetcherOptions) {
  const { tokenClient, awsSigner, apiKeyProvider } = options;

  return async <T extends TypedEndpoint<Paths>>(
    endpoint: T,
    config?: FilteredRequestConfig<Paths, T>,
  ) => {
    const authScheme = endpointAuth[endpoint as keyof typeof endpointAuth];
    let headers: Record<string, string> = {};

    if (authScheme) {
      const scheme = securitySchemes[authScheme];

      switch (scheme.type) {
        case 'http':
          if (scheme.scheme === 'bearer') {
            headers = await tokenClient.createValidAuthHeaders();
          }
          break;
        case 'apiKey':
          if (scheme['x-amazon-apigateway-authtype'] === 'awsSigv4') {
            headers = await awsSigner.sign(endpoint, config);
          } else {
            headers[scheme.name] = await apiKeyProvider.getKey();
          }
          break;
      }
    }

    return baseFetcher.request(endpoint, {
      ...config,
      headers: { ...headers, ...config?.headers },
    });
  };
}
```

## File Structure Changes

```
packages/
├── cli/
│   └── src/
│       ├── openapi.ts              # Updated: add --ts support
│       └── generators/
│           └── OpenApiTsGenerator.ts  # New: TypeScript generation
├── schema/
│   └── src/
│       └── openapi.ts              # Updated: security scheme support
└── client/
    └── src/
        └── auth-fetcher.ts         # New: auth-aware fetcher
```

## Migration Path

### Breaking Change

TypeScript is now the default output format. Existing JSON users must add `--json` flag.

### Existing JSON Users

```bash
# Before
gkm openapi --output ./openapi.json

# After (explicit JSON)
gkm openapi --json --output ./openapi.json
```

### Adopting TypeScript Output (New Default)

1. Run `gkm openapi --output ./src/api/openapi.ts`
2. Update imports from `./openapi-types` to `./openapi`
3. Use `createAuthAwareFetcher` instead of `createTypedFetcher`

## Testing Strategy

1. **Unit Tests**
   - Authorizer → security scheme mapping
   - Schema extraction from StandardSchema
   - TypeScript code generation

2. **Integration Tests**
   - Full endpoint → TypeScript output pipeline
   - Generated code compiles without errors
   - Auth map matches endpoint authorizers

3. **E2E Tests**
   - Auth-aware fetcher selects correct auth per endpoint
   - Bearer endpoints get JWT headers
   - IAM endpoints get SigV4 signatures
   - Public endpoints get no auth headers

## Open Questions

1. **Schema naming conflicts** - What if two endpoints have the same operationId?
   - Proposal: Append route hash suffix if conflict detected

2. **Circular schema references** - How to handle `User { friends: User[] }`?
   - Proposal: Use TypeScript `interface` declarations which support self-reference

3. **Optional vs required auth** - Some endpoints allow optional auth (return more data if authenticated)
   - Proposal: Add `optional` auth type: `'bearer' | 'bearer?' | null`

## Timeline

| Phase | Description | Estimate |
|-------|-------------|----------|
| 1 | Schema package updates (ComponentCollector) | - |
| 2 | CLI TypeScript generator | - |
| 3 | Client auth-aware fetcher | - |
| 4 | Documentation and examples | - |
| 5 | Testing and refinement | - |
