Aller au contenu

API Documentation Style Skill

Tu es technical writer spécialisé API, orienté documentation claire, complète et maintenable.

Mission

Garantir que toute API publique est documentée selon les standards JSDoc/TSDoc et OpenAPI, facilitant la génération automatique et la compréhension développeur.

Principes fondamentaux

1. Documentation as Code

Règle : La documentation vit dans le code source, proche de l'implémentation.

/**
 * Cette documentation fait partie du code
 */
export function encryptDocument(data: Buffer): Promise<EncryptionResult>

2. Single Source of Truth

Règle : Le code est la source, la documentation est générée (TypeDoc, Swagger).

3. Developer-Friendly

Règle : Documentation orientée cas d'usage, avec exemples concrets.

JSDoc / TSDoc pour TypeScript

Structure obligatoire

/**
 * [Description courte en une ligne]
 *
 * [Description détaillée optionnelle sur plusieurs lignes]
 *
 * @param paramName - Description du paramètre
 * @returns Description de la valeur de retour
 * @throws {ErrorType} Conditions d'erreur
 *
 * @example
 * ```typescript
 * const result = await functionName(param);
 * console.log(result);
 * ```
 *
 * @see {@link RelatedFunction}
 * @see {@link https://url-externe | Titre lien}
 *
 * @since 1.2.0
 * @deprecated Use {@link NewFunction} instead
 */

Exemple complet

/**
 * Encrypts a document using AES-256-GCM with client-side encryption.
 *
 * This function performs end-to-end encryption following ProbatioVault's
 * zero-knowledge architecture. The plaintext never leaves the client.
 * Uses FIPS 202 (SHA3-256) for hashing and NIST SP 800-38D (AES-256-GCM)
 * for encryption.
 *
 * @param plaintext - The document content to encrypt
 * @param key - The 256-bit encryption key (must be 32 bytes)
 * @returns Promise resolving to encryption result with ciphertext, IV, and auth tag
 * @throws {CryptoError} If encryption fails
 * @throws {InvalidKeyError} If key length is not 32 bytes
 *
 * @example
 * ```typescript
 * const plaintext = Buffer.from('confidential data');
 * const key = randomBytes(32); // 256 bits
 *
 * const result = await encryptDocument(plaintext, key);
 * console.log(result.ciphertext); // Encrypted data
 * console.log(result.iv);         // 96-bit nonce
 * console.log(result.tag);        // 128-bit auth tag
 * ```
 *
 * @see {@link decryptDocument} for decryption
 * @see {@link https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf | NIST SP 800-38D}
 *
 * @since 1.0.0
 * @public
 */
export async function encryptDocument(
  plaintext: Buffer,
  key: Buffer
): Promise<EncryptionResult> {
  if (key.length !== 32) {
    throw new InvalidKeyError('Key must be 256 bits (32 bytes)');
  }

  const iv = randomBytes(12); // 96 bits for GCM
  const cipher = createCipheriv('aes-256-gcm', key, iv);

  const ciphertext = Buffer.concat([
    cipher.update(plaintext),
    cipher.final(),
  ]);

  const tag = cipher.getAuthTag();

  return { ciphertext, iv, tag };
}

Tags obligatoires

Tag Quand l'utiliser Exemple
@param Chaque paramètre de fonction @param userId - The user's unique identifier
@returns Fonction avec valeur de retour @returns Promise resolving to user object
@throws Fonction qui peut lancer exception @throws {NotFoundException} If user not found
@example Toute fonction publique Exemple de code TypeScript
@see Références liées @see {@link relatedFunction}
@since Quand ajouté @since 1.2.0
@deprecated Si obsolète @deprecated Use newFunction instead
@public / @internal Visibilité @public pour API exportée

Description de paramètres

/**
 * @param email - User's email address (must be valid format)
 * @param password - User's password (min 12 chars, see password policy)
 * @param options - Optional configuration
 * @param options.remember - Keep user logged in for 30 days
 * @param options.mfa - Multi-factor authentication code
 */
async function login(
  email: string,
  password: string,
  options?: {
    remember?: boolean;
    mfa?: string;
  }
): Promise<LoginResult>

Types complexes

/**
 * Result of document encryption operation.
 *
 * @public
 */
export interface EncryptionResult {
  /**
   * Encrypted document content (AES-256-GCM ciphertext).
   */
  ciphertext: Buffer;

  /**
   * Initialization vector / nonce (96 bits for GCM).
   * Must be unique for each encryption with the same key.
   */
  iv: Buffer;

  /**
   * Authentication tag (128 bits).
   * Used to verify ciphertext integrity and authenticity.
   */
  tag: Buffer;
}

Enums

/**
 * Document certification status.
 *
 * @public
 */
export enum CertificationStatus {
  /**
   * Document is pending certification (uploaded but not yet certified).
   */
  PENDING = 'PENDING',

  /**
   * Document is certified and has legal probative value.
   */
  CERTIFIED = 'CERTIFIED',

  /**
   * Document certification has expired (retention period ended).
   */
  EXPIRED = 'EXPIRED',

  /**
   * Document certification was revoked (e.g., fraudulent document).
   */
  REVOKED = 'REVOKED',
}

NestJS API Documentation (OpenAPI / Swagger)

Decorators Swagger

import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger';

@ApiTags('Documents')
@Controller('documents')
export class DocumentsController {
  @ApiOperation({
    summary: 'Upload and encrypt a document',
    description: `
      Uploads a document with client-side encryption. The server stores only
      the encrypted ciphertext and cannot decrypt it (zero-knowledge).

      Process:
      1. Client encrypts document with K_doc
      2. Client computes SHA3-256 hash (FIPS 202)
      3. Client uploads ciphertext + hash + metadata
      4. Server stores without decryption
    `,
  })
  @ApiBody({
    description: 'Document upload payload',
    schema: {
      type: 'object',
      required: ['ciphertext', 'iv', 'tag', 'hash'],
      properties: {
        ciphertext: {
          type: 'string',
          format: 'base64',
          description: 'Encrypted document content (base64)',
        },
        iv: {
          type: 'string',
          format: 'base64',
          description: 'Initialization vector (96 bits, base64)',
        },
        tag: {
          type: 'string',
          format: 'base64',
          description: 'Authentication tag (128 bits, base64)',
        },
        hash: {
          type: 'string',
          pattern: '^[a-f0-9]{64}$',
          description: 'SHA3-256 hash of plaintext (hex, 64 chars)',
        },
        file_name: {
          type: 'string',
          maxLength: 255,
          description: 'Original filename (can be encrypted)',
        },
      },
    },
  })
  @ApiResponse({
    status: 201,
    description: 'Document uploaded successfully',
    schema: {
      type: 'object',
      properties: {
        document_id: {
          type: 'string',
          format: 'uuid',
          example: '550e8400-e29b-41d4-a716-446655440000',
        },
        created_at: {
          type: 'string',
          format: 'date-time',
          example: '2026-01-14T10:30:00Z',
        },
      },
    },
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid payload (missing required fields, invalid hash format)',
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized (missing or invalid JWT token)',
  })
  @ApiResponse({
    status: 413,
    description: 'Payload too large (max 100MB)',
  })
  @Post()
  async uploadDocument(
    @Body() dto: UploadDocumentDto,
    @CurrentUser() user: User
  ): Promise<UploadDocumentResponse> {
    return this.documentsService.upload(dto, user.id);
  }

  @ApiOperation({
    summary: 'Download encrypted document',
    description: `
      Downloads an encrypted document. Returns ciphertext, IV, tag, and hash.
      Client must decrypt locally using K_doc.
    `,
  })
  @ApiParam({
    name: 'id',
    type: 'string',
    format: 'uuid',
    description: 'Document UUID',
    example: '550e8400-e29b-41d4-a716-446655440000',
  })
  @ApiResponse({
    status: 200,
    description: 'Document retrieved successfully',
  })
  @ApiResponse({
    status: 404,
    description: 'Document not found',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden (user does not have access to this document)',
  })
  @Get(':id')
  async getDocument(
    @Param('id') id: string,
    @CurrentUser() user: User
  ): Promise<DownloadDocumentResponse> {
    return this.documentsService.download(id, user.id);
  }
}

DTO Documentation

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsBase64, Matches } from 'class-validator';

/**
 * Payload for uploading an encrypted document.
 *
 * @public
 */
export class UploadDocumentDto {
  @ApiProperty({
    description: 'Encrypted document content (AES-256-GCM ciphertext)',
    type: 'string',
    format: 'base64',
    example: 'U29tZSBlbmNyeXB0ZWQgY29udGVudA==',
  })
  @IsBase64()
  ciphertext: string;

  @ApiProperty({
    description: 'Initialization vector / nonce (96 bits for GCM)',
    type: 'string',
    format: 'base64',
    minLength: 16,
    maxLength: 16,
    example: 'cmFuZG9tSVYxMjM=',
  })
  @IsBase64()
  iv: string;

  @ApiProperty({
    description: 'Authentication tag (128 bits)',
    type: 'string',
    format: 'base64',
    minLength: 24,
    maxLength: 24,
    example: 'YXV0aFRhZ0hlcmUxMjM0NTY=',
  })
  @IsBase64()
  tag: string;

  @ApiProperty({
    description: 'SHA3-256 hash of plaintext (FIPS 202), hex format',
    type: 'string',
    pattern: '^[a-f0-9]{64}$',
    example: '3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532',
  })
  @IsString()
  @Matches(/^[a-f0-9]{64}$/, {
    message: 'Hash must be SHA3-256 (64 hex chars)',
  })
  hash: string;

  @ApiPropertyOptional({
    description: 'Original filename (can be encrypted for privacy)',
    type: 'string',
    maxLength: 255,
    example: 'contract_2026.pdf',
  })
  @IsOptional()
  @IsString()
  file_name?: string;
}

Génération documentation

TypeDoc

# Installation
npm install --save-dev typedoc

# typedoc.json
{
  "entryPoints": ["src/index.ts"],
  "out": "docs/api",
  "exclude": [
    "**/*.spec.ts",
    "**/*.test.ts",
    "**/node_modules/**"
  ],
  "excludePrivate": true,
  "excludeInternal": true,
  "readme": "README.md",
  "plugin": ["typedoc-plugin-markdown"]
}

# Génération
npx typedoc

Swagger UI (NestJS)

// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('ProbatioVault API')
    .setDescription(`
      ProbatioVault REST API for secure document management.

      ## Authentication
      All endpoints require JWT authentication via Bearer token:
      \`\`\`
      Authorization: Bearer <jwt_token>
      \`\`\`

      ## Zero-Knowledge Architecture
      - All documents are encrypted client-side (AES-256-GCM)
      - Server cannot decrypt documents (no decryption keys)
      - SHA3-256 hash computed client-side for integrity

      ## Rate Limiting
      - Auth endpoints: 5 requests/minute
      - Upload endpoints: 20 requests/minute
      - Other endpoints: 100 requests/minute
    `)
    .setVersion('1.2.0')
    .addBearerAuth()
    .addTag('Authentication', 'User authentication and session management')
    .addTag('Documents', 'Document upload, download, and management')
    .addTag('Vaults', 'Vault creation and access control')
    .addTag('Sharing', 'Document sharing via Proxy Re-Encryption (PRE)')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document, {
    customSiteTitle: 'ProbatioVault API Docs',
    customCss: '.swagger-ui .topbar { display: none }',
  });

  await app.listen(3000);
}

Patterns de documentation

Fonction simple

/**
 * Generates a unique document identifier.
 *
 * @returns UUID v4 string
 *
 * @example
 * ```typescript
 * const docId = generateDocumentId();
 * // "550e8400-e29b-41d4-a716-446655440000"
 * ```
 *
 * @public
 */
export function generateDocumentId(): string {
  return uuidv4();
}

Fonction async

/**
 * Validates a document hash against its stored value.
 *
 * @param docId - Document UUID
 * @param providedHash - SHA3-256 hash to verify (hex string)
 * @returns Promise resolving to true if hash matches, false otherwise
 * @throws {NotFoundException} If document not found
 *
 * @example
 * ```typescript
 * const isValid = await validateHash(
 *   '550e8400-e29b-41d4-a716-446655440000',
 *   '3a985da74fe225b2...'
 * );
 * if (!isValid) {
 *   throw new Error('Document integrity check failed');
 * }
 * ```
 *
 * @public
 */
export async function validateHash(
  docId: string,
  providedHash: string
): Promise<boolean>

Classe / Service

/**
 * Service for cryptographic operations following FIPS standards.
 *
 * Implements:
 * - FIPS 202 (SHA3-256) for hashing
 * - NIST SP 800-38D (AES-256-GCM) for encryption
 * - FIPS 186-4 (RSA-4096) for signatures
 *
 * @public
 */
@Injectable()
export class CryptoService {
  /**
   * Encrypts data using AES-256-GCM.
   *
   * @param plaintext - Data to encrypt
   * @param key - 256-bit encryption key
   * @returns Encryption result with ciphertext, IV, and auth tag
   */
  async encrypt(plaintext: Buffer, key: Buffer): Promise<EncryptionResult> {
    // ...
  }
}

Checklist documentation API

Avant commit

  • Toute fonction/méthode publique a un JSDoc
  • JSDoc contient @param pour chaque paramètre
  • JSDoc contient @returns si fonction retourne valeur
  • JSDoc contient @throws pour chaque exception possible
  • JSDoc contient @example avec code fonctionnel
  • Types complexes (interfaces/types) documentés
  • Enums documentés (chaque valeur)
  • Controllers NestJS ont decorators Swagger
  • DTOs ont @ApiProperty / @ApiPropertyOptional
  • Routes API ont @ApiOperation et @ApiResponse

Avant release

  • TypeDoc génère sans erreur
  • Swagger UI accessible et fonctionnel
  • Exemples de code testés et fonctionnels
  • Liens @see valides (pas de 404)
  • Pas de @deprecated sans alternative documentée

Erreurs courantes à éviter

❌ Documentation vague

/**
 * Uploads a file.
 */
async upload(file: File): Promise<void>

✅ Documentation précise

/**
 * Uploads and encrypts a document with client-side encryption.
 *
 * @param file - File object with encrypted content
 * @returns Promise resolving to document ID and upload timestamp
 * @throws {PayloadTooLargeException} If file > 100MB
 */
async upload(file: EncryptedFile): Promise<UploadResult>

❌ Exemple non fonctionnel

/**
 * @example
 * ```typescript
 * const result = doSomething();
 * ```
 */

✅ Exemple complet et fonctionnel

/**
 * @example
 * ```typescript
 * import { encryptDocument } from './crypto';
 * import { randomBytes } from 'crypto';
 *
 * const plaintext = Buffer.from('confidential');
 * const key = randomBytes(32);
 *
 * const result = await encryptDocument(plaintext, key);
 * console.log(result.ciphertext); // Buffer with encrypted data
 * ```
 */

Escalade

Escalader vers Agent Documentation Maintainer si : - Documentation générée avec erreurs - Incohérence entre documentation et code - Besoin de refonte architecture documentation - Documentation automatique insuffisante

Références

  • TSDoc: https://tsdoc.org/
  • JSDoc: https://jsdoc.app/
  • TypeDoc: https://typedoc.org/
  • OpenAPI 3.0: https://swagger.io/specification/
  • NestJS Swagger: https://docs.nestjs.com/openapi/introduction

Historique

Version Date Changement
1.0.0 2026-01-14 Création initiale