Skip to content
This repository was archived by the owner on Jan 7, 2026. It is now read-only.

venikman/fsharp2ts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

F# Domain Modeling to TypeScript with Fable: A Wish List App Example

This project showcases how to define a domain model in F# for a Wish List application and then automatically generate TypeScript types using Fable. It also explores how these types can be integrated into a React frontend. The principles shown can be applied to various domains, and the initial focus on EventStorming (see below) is a valuable technique for domain discovery.

What is EventStorming? (Optional Domain Discovery Technique)

EventStorming is a flexible workshop format for collaborative exploration of complex business domains. It brings together domain experts and technical experts to create a shared understanding of a business process. Key characteristics include:

  • Collaborative: It involves active participation from diverse stakeholders.
  • Visual: It uses sticky notes (often orange for domain events) on a large modeling surface.
  • Discovery-focused: It aims to uncover unknown aspects of the domain and identify ambiguities.
  • Event-centric: It focuses on "domain events" – significant occurrences within the business process.

Benefits of EventStorming:

  • Shared Understanding: Builds a common language and understanding of the domain across different roles.
  • Rapid Learning: Allows teams to quickly learn and map out complex domains.
  • Identifies Bounded Contexts: Helps in defining clear boundaries between different parts of a system (useful for microservices).
  • Highlights Pain Points & Opportunities: Uncovers areas of confusion, bottlenecks, or potential improvements.
  • Drives Model Design: The outputs of an EventStorming session (events, commands, aggregates, read models) can directly inform the design of your software, including your F# types for applications like the Wish List example.

Using EventStorming to Define F# Types (General Approach)

  1. Conduct the Workshop:

    • Gather domain experts, developers, and other relevant stakeholders.
    • Use a large modeling space (wall or digital whiteboard).
    • Start by brainstorming Domain Events (things that happen in the system, e.g., "Order Placed", "Payment Processed", "Item Shipped"). Write them on orange sticky notes and place them chronologically.
    • Identify Commands (user actions or system triggers that cause events, e.g., "Place Order", "Process Payment").
    • Identify Aggregates (clusters of domain objects that are treated as a single unit for data changes, e.g., "Order", "Customer"). These often become the entities or records in your F# domain model.
    • Define Read Models / Views (data structures tailored for querying or displaying information, e.g., "Order Summary").
    • Discuss policies, rules, and external systems.
  2. Translate to F# Types:

    • The aggregates, events, and supporting value objects identified during EventStorming become the basis for your F# record types, discriminated unions, etc.
    • For example, an "Order" aggregate might translate to an Order record in F# with fields like OrderId, CustomerId, OrderLines, Status.
    • Domain events can also be modeled as F# types, often as part of a discriminated union representing all possible events.

Automated F# to TypeScript Type Generation

This project is set up to automatically generate TypeScript types from your F# domain models defined in Domain.fs using Fable.

Prerequisites

  • .NET SDK (Version 9.0 or higher, this project specifically uses .NET 9)
  • Node.js and npm (LTS version recommended)

Setup and Installation

  1. Clone the repository (if you haven't already).
  2. Restore .NET tools:
    dotnet tool restore
  3. Install Node.js dependencies:
    npm install

Defining F# Domain Models

  1. Open the Domain.fs file. Define your F# types (records, discriminated unions, etc.) within the Models namespace. For the Wish List application, this includes types like WishList, WishListItem, and UserProfile. For example:

    namespace Models
    
    // Example: WishListItem from Domain.fs
    type WishListItemDetails =
      { Title: string
        Description: string option
        Url: string option 
      }
    
    type WishListItem =
      { ItemId: System.Guid
        Details: WishListItemDetails
        AddedDate: System.DateTimeOffset
        IsPurchased: bool
        Priority: int 
      }
      
    // Other types like UserProfile, WishList, WishListVisibility, Address are also defined in Domain.fs

Generating TypeScript Types

  1. Once you have defined or updated your F# types in Domain.fs, run the following command in your terminal:

    npm run build:ts
  2. This command uses Fable to transpile your F# types into TypeScript.

  3. The generated TypeScript files will be placed in the dist/ts directory.

Sharing and Using TypeScript Types

The TypeScript files generated in the dist/ts directory can be directly imported into your TypeScript projects (e.g., a React front-end, a Node.js backend service). The main file containing your translated types will be dist/ts/Domain.ts (derived from Domain.fs).

Example:

Assuming your project is set up to resolve TypeScript modules, you can import types like so. (The example types here are from the Wish List domain):

// In your frontend/typescript project
// Adjust path as needed. Some bundlers/setups might allow omitting '.js'
import { UserProfile, WishList, WishListItem, WishListVisibility } from "./path/to/dist/ts/Domain"; 
// Or, to be more explicit, if your moduleResolution supports it:
// import { UserProfile, WishList } from "./path/to/dist/ts/Domain.js";

// Example function using WishList types
function displayWishListInfo(wishList: WishList) {
  console.log(`Wish List: ${wishList.ListName}`);
  console.log(`Owned by: ${wishList.Owner.FirstName}`);
  console.log(`Number of items: ${wishList.Items.length}`);

  // Working with a discriminated union like WishListVisibility
  switch (wishList.Visibility.Case) {
    case "Private":
      console.log("This list is private.");
      break;
    case "SharedWithLink":
      console.log("This list is shared via a link.");
      break;
    case "SharedWithUsers":
      console.log(`Shared with ${wishList.Visibility.Fields[0].length} users.`);
      break;
    case "Public":
      console.log("This list is public.");
      break;
  }
}

Important Considerations:

  • fable_modules: The generated code imports helpers from the dist/ts/fable_modules directory (which contains parts of fable-library-ts). When you use these types in another project, you'll need to ensure that these dependencies are resolvable.
    • Typically, you would add fable-library (or fable-library-ts) as a dependency to your consuming TypeScript project.
    • Alternatively, if you copy the dist/ts folder, ensure you copy fable_modules along with your domain types.
  • Module Resolution: How you import (Domain vs Domain.js vs Domain.ts) can depend on your tsconfig.json settings (specifically moduleResolution and allowImportingTsExtensions) in the consuming project and your bundler's capabilities. Fable's default output from fableconfig.json with "lang": "ts" should produce .ts files.
  • Type Complexity: Refer to the Fable documentation for specifics on how F# types (options, DUs, lists, Guid, decimal etc.) are translated to TypeScript. The generated _$reflection functions are for Fable's internal use but show the structure.

Next Steps

  • Explore the Wish List application types in Domain.fs.
  • Modify or extend the Wish List domain with new features (e.g., item categories, ratings).
  • Run the npm run build:ts command to see the generated TypeScript for your changes.
  • Integrate the generated types into a new or existing TypeScript project (Node.js, Deno, or a web frontend).

React Frontend Integration (Conceptual)

For a conceptual guide on how these F#-generated TypeScript types can be used within a React application (potentially with Material UI and Module Federation), please see:

Conceptual React Integration Guide

Running the TypeScript Usage Example

This project includes a runnable TypeScript example to demonstrate how to import and use the F#-generated types.

The example is located in the examples/ts-usage-example directory.

Prerequisites for the Example

Ensure you have already:

  1. Built the F# types to generate the TypeScript output in the dist/ts directory:
    npm run build:ts 
    (Run this from the project root directory)

Steps to Run the Example

  1. Navigate to the example directory:

    cd examples/ts-usage-example
  2. Install example-specific dependencies: This will install ts-node, typescript, and fable-library-ts locally for the example.

    npm install
  3. Run the example script:

    npm start

    This command executes ts-node run_example.ts. You should see output in your console demonstrating the creation and manipulation of the typed objects.

The run_example.ts script shows how to:

  • Import types from ../../dist/ts/Domain.
  • Import helpers like some from fable-library-ts/Option.
  • Instantiate records and discriminated unions.
  • Work with optional properties and discriminated union cases.

Refer to the examples/ts-usage-example/run_example.ts file for the complete code. You can also run npm run build_types from within the examples/ts-usage-example directory as a shortcut to rebuild the F# types from the root.

Deno Example

For users interested in Deno, a separate example is available that demonstrates using the F#-generated types in a Deno environment.

This example showcases Deno's native TypeScript capabilities and its module import system.

  • Location: examples/deno-example/
  • Instructions: See the detailed README.md within the examples/deno-example/ directory for setup and execution steps.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages