Skip to main content

Examples

3 min read

Multi-Module App Example

Compose modules for a complete application.


Auth Module

const authModule = createModule("auth", {
  schema: {
    facts: {
      user: t.object<User>().nullable(),
      token: t.string().nullable(),
      loading: t.boolean(),
    },
  },

  // Start logged out –a token can be restored from localStorage later
  init: (facts) => {
    facts.user = null;
    facts.token = null;
    facts.loading = false;
  },

  // Other modules can read this to gate authenticated features
  derive: {
    isAuthenticated: (facts) => facts.user !== null,
  },

  // When a token exists but we haven't fetched the user yet, validate it
  constraints: {
    validateSession: {
      when: (facts) => facts.token && !facts.user && !facts.loading,
      require: { type: "VALIDATE_SESSION" },
    },
  },

  resolvers: {
    validateSession: {
      requirement: "VALIDATE_SESSION",
      resolve: async (req, context) => {
        context.facts.loading = true;
        try {
          // Exchange the token for user data
          context.facts.user = await api.validateToken(context.facts.token);
        } catch {
          // Invalid token –clear it so the user can log in again
          context.facts.token = null;
        } finally {
          context.facts.loading = false;
        }
      },
    },
  },
});

Cart Module

const cartModule = createModule("cart", {
  schema: {
    facts: {
      items: t.array(t.object<CartItem>()),
      couponCode: t.string().nullable(),
      discount: t.number(),
    },
  },

  // Empty cart with no coupon applied
  init: (facts) => {
    facts.items = [];
    facts.couponCode = null;
    facts.discount = 0;
  },

  derive: {
    // Sum up price * quantity for every item
    subtotal: (facts) =>
      facts.items.reduce((sum, item) => sum + item.price * item.qty, 0),
    // Composition: total depends on another derivation (subtotal)
    total: (facts, derive) =>
      derive.subtotal - facts.discount,
    // Badge count for the cart icon
    itemCount: (facts) =>
      facts.items.reduce((sum, item) => sum + item.qty, 0),
  },

  // When a coupon code is entered but not yet validated, validate it
  constraints: {
    applyCoupon: {
      when: (facts) => facts.couponCode && facts.discount === 0,
      require: { type: "APPLY_COUPON" },
    },
  },

  resolvers: {
    applyCoupon: {
      requirement: "APPLY_COUPON",
      resolve: async (req, context) => {
        // Server validates the code and returns the discount amount
        const result = await api.validateCoupon(context.facts.couponCode);
        context.facts.discount = result.discount;
      },
    },
  },
});

Checkout Module

const checkoutModule = createModule("checkout", {
  schema: {
    facts: {
      // Wizard-style flow through four steps
      step: t.string<"shipping" | "payment" | "review" | "complete">(),
      shippingAddress: t.object<Address>().nullable(),
      paymentMethod: t.object<PaymentMethod>().nullable(),
      processing: t.boolean(),
    },
  },

  // Begin at the first step with no data collected
  init: (facts) => {
    facts.step = "shipping";
    facts.shippingAddress = null;
    facts.paymentMethod = null;
    facts.processing = false;
  },

  // Submit the order once the user reaches "review" with all required info
  constraints: {
    processOrder: {
      when: (facts) =>
        facts.step === "review" &&
        facts.shippingAddress &&
        facts.paymentMethod &&
        !facts.processing,
      require: { type: "PROCESS_ORDER" },
    },
  },

  resolvers: {
    processOrder: {
      requirement: "PROCESS_ORDER",
      resolve: async (req, context) => {
        context.facts.processing = true;

        // Send the collected data to the order API
        await api.createOrder({
          shipping: context.facts.shippingAddress,
          payment: context.facts.paymentMethod,
        });

        // Advance to the confirmation step
        context.facts.step = "complete";
        context.facts.processing = false;
      },
    },
  },
});

Composing Modules

import { createSystem } from '@directive-run/core';

// Pass multiple modules via a named map instead of a single `module`
const system = createSystem({
  modules: {
    auth: authModule,
    cart: cartModule,
    checkout: checkoutModule,
  },
});
system.start();

// Each module's facts live under its namespace –fully typed
system.facts.auth.user;
system.facts.cart.items;
system.facts.checkout.step;

Next Steps

Previous
Permissions

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript