Exercise 10: TypeScript Type Generation with Mockserver Admin

Exercise 10 demonstrates how to leverage @sap-ux/fe-mockserver-admin to automatically generate TypeScript type definitions from your OData metadata. This provides type safety, IntelliSense support, and compile-time validation for mock data development.

10.1 Overview: Type-Safe Mock Data Development

Exercise 10 demonstrates how to leverage @sap-ux/fe-mockserver-admin to automatically generate TypeScript type definitions from your OData metadata. This provides:

  • Type safety for mock data development
  • IntelliSense support in your IDE
  • Compile-time validation of mock data structures
  • Automated type generation from OData metadata

The fe-mockserver-admin package analyzes your service metadata and generates corresponding TypeScript interfaces, eliminating manual type definitions and ensuring consistency with your actual OData service.

10.2 Package Installation and Setup

Exercise 10 includes the required dependencies and scripts for type generation:

Key Dependencies in package.json:

ex10/package.json View on GitHub ↗
{
  "devDependencies": {
    "@sap-ux/fe-mockserver-admin": "0.0.14",
    "typescript": "^5.9.3",
    "ts-node": "^10.9.2"
  },
  "scripts": {
    "generate-entity-files": "fe-mockserver-admin generate-entity-files -m ./webapp/localService/mainService/metadata.xml --output ./webapp/localService/mainService/data",
    "generate-types": "fe-mockserver-admin generate-types -m ./webapp/localService/mainService/metadata.xml --output ./webapp/localService/mainService/data"
  }
}

10.3 TypeScript Configuration

The tsconfig.json is configured for optimal mock data development:

ex10/tsconfig.json View on GitHub ↗
{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "skipLibCheck": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": false,
    "strictPropertyInitialization": false,
    "moduleResolution": "node",
    "rootDir": "./webapp",
    "noEmitOnError": false,
    "baseUrl": "./"
  }
}

10.4 Automatic Type Generation from Metadata

Generate TypeScript types from your OData metadata:

npm run generate-types

This command analyzes metadata.xml and creates comprehensive type definitions:

Generated Files Structure:

ex10/webapp/localService/mainService/data/ODataTypes.d.ts View on GitHub ↗
// ====== Entity Types ======

// Type definitions for Books
export type Books = {
  createdAt?: string;
  createdBy?: string;
  modifiedAt?: string;
  modifiedBy?: string;
  ID: string;
  title?: string;
  author?: string;
  price?: number;
  currency_code?: string;
  stock?: number;
  description?: string;
  coverUrl?: string;
  IsActiveEntity: boolean;
  HasActiveEntity: boolean;
  HasDraftEntity: boolean;
  currency?: NavPropTo;
  chapters?: NavPropTo;
  reviews?: NavPropTo;
  DraftAdministrativeData?: NavPropTo;
  SiblingEntity?: NavPropTo;
}

export type BooksKeys = {
  ID: string;
  IsActiveEntity: boolean;
}

// ====== Complex Types ======

// Type definitions for return_BookshopService_Books_setDiscount
export type return_BookshopService_Books_setDiscount = {
  message?: string;
  newPrice?: number;
}

10.5 Type-Safe Mock Data Development

With generated types, you can create type-safe mock data files:

Example: Type-safe Books.ts mock data:

ex10/webapp/localService/mainService/data/Books.ts View on GitHub ↗
import type {
  ODataRequest,
  Action,
  NavigationProperty,
  PartialReferentialConstraint,
} from '@sap-ux/ui5-middleware-fe-mockserver';
import { MockDataContributorClass } from '@sap-ux/ui5-middleware-fe-mockserver';
import type {
  Books,
  BooksKeys,
  return_BookshopService_Books_promoteBook,
  return_BookshopService_Books_setDiscount,
} from './ODataTypes';

export default class BooksContributor extends MockDataContributorClass {
  getInitialDataSet(contextId: string): Books[] {
    const books = [];
    const authors = ['Jane Austen', 'Mark Twain', 'Charles Dickens', 'Ernest Hemingway', 'Virginia Woolf'];
    const genres = ['Fiction', 'Mystery', 'Romance', 'Science Fiction', 'Biography'];

    for (let i = 0; i < 20; i++) {
      const randomAuthor = authors[Math.floor(Math.random() * authors.length)];
      const randomGenre = genres[Math.floor(Math.random() * genres.length)];

      books.push({
        ID: `550e8400-e29b-41d4-a716-44665544${String(i).padStart(4, '0')}`,
        title: `${randomGenre} Book ${i + 1}`,
        author: randomAuthor,
        price: Math.floor(Math.random() * 50) + 10,
        currency_code: Math.random() > 0.5 ? 'EUR' : 'USD',
        stock: Math.floor(Math.random() * 100),
        description: `A compelling ${randomGenre.toLowerCase()} story by ${randomAuthor}.`,
        coverUrl: `https://picsum.photos/300/400?random=${i}`,
        createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(),
        createdBy: 'admin',
        modifiedAt: new Date().toISOString(),
        modifiedBy: 'admin',
        IsActiveEntity: true,
        HasActiveEntity: false,
        HasDraftEntity: Math.random() > 0.8, // 20% chance of being in draft
      });
    }
    return books;
  }

  async setDiscount(_actionDefinition: Action, actionData: BooksAction_setDiscountData, keys: BooksKeys, odataRequest: ODataRequest): Promise {
    // Simple validation example - discount cannot exceed 90%
    if (actionData.percentage > 90) {
      this.throwError('Discount cannot exceed 90%', 400, {
        error: {
          code: 'INVALID_DISCOUNT',
          message: 'Company policy: Maximum discount is 90%',
        },
      });
    }

    // Normal business logic continues...
    const currentEntries = await this.base.fetchEntries(keys, odataRequest);
    if (currentEntries.length > 0) {
      const currentBook = currentEntries[0];
      const discountPercentage = actionData.percentage || 10; // Default 10% if not provided
      const discountMultiplier = (100 - discountPercentage) / 100;
      const newPrice = Math.round(currentBook.price * discountMultiplier * 100) / 100;

      // Update the book with new price
      await this.base.updateEntry(keys, { price: newPrice }, odataRequest);

      return {
        message: `${discountPercentage}% discount applied to "${currentBook.title}"`,
        newPrice: newPrice,
      };
    }
  }
}

10.6 Benefits of Type-Safe Mock Development

1. Compile-Time Validation

// ❌ TypeScript catches errors at compile time
const invalidBook: Books = {
  ID: "123",
  price: "invalid", // Error: Type 'string' is not assignable to type 'number'
  IsActiveEntity: true,
  HasActiveEntity: false,
  HasDraftEntity: false
};

2. IntelliSense Support

  • Auto-completion for entity properties
  • Type hints for action parameters and return types
  • Navigation property type safety

3. Metadata Synchronization

  • Types automatically reflect metadata changes
  • Consistent data structures across all mock files

10.7 Integration with Existing Mockserver Workflows

The generated TypeScript files work seamlessly with the existing mockserver:

Standard ui5-mock.yaml Configuration:

ex10/ui5-mock.yaml View on GitHub ↗
- name: sap-fe-mockserver
  beforeMiddleware: csp
  configuration:
    services:
      - urlPath: /bookshop
        metadataPath: ./webapp/localService/mainService/data
        generateMockData: false

10.8 Running Exercise 10

Start the application with TypeScript-generated mock data:

npm run start:ex10

What you'll experience:

  • Type-safe mock data loaded from .ts files
  • Consistent data structure matching your OData service
  • Full IntelliSense support during development
  • Compile-time validation of all mock data

10.9 Best Practices

1. Regenerate Types After Metadata Changes

# After updating metadata.xml
npm run generate-types

2. Version Control Strategy

  • Include: ODataTypes.d.ts and other generated type files
  • Exclude: Generated .js files (if using ts-node)

3. Development Workflow

  1. Update service metadata
  2. Regenerate types with npm run generate-types
  3. Update mock data files with TypeScript validation
  4. Test with npm run start:ex10

10.10 Key Learning Points

Automated Type Generation: No manual type definitions needed
Metadata Synchronization: Types stay in sync with OData service
IDE Integration: Full IntelliSense and error checking
Compile-Time Safety: Catch data structure errors before runtime
Navigation Properties: Proper typing for complex relationships
Draft Support: Built-in SAP Fiori draft entity handling

Next Level: Combine type-safe mock data with the JavaScript logic patterns from Exercise 3 for the most robust mock data development experience.