Shared Logic & Type Merging

Learn how to share, replace, or extend classes, interfaces, and functions across modules.

Overview

While @alias/ui/ is for visual components, @alias/lib/ is the home for your business logic. It allows modules to share classes, interfaces, and utility functions through a virtual namespace.

In JopiJS, sharing logic is not limited to simple imports. You can:

  • Share functions and classes via the @/lib/ virtual path.
  • Replace a logic item with a higher-priority version.
  • Extend existing classes or interfaces using "Merging" techniques.

1. Sharing & Importing Logic

To share code, place it in a folder within your module's @alias/lib/ directory.

Project Structure:

src/ mod_utils/
└── @alias/
    └── lib/
        └── math/
            └── index.ts    # Contains: export function sum(...)

Using the code: Regardless of which module provides the logic, you always import it from the virtual path:

src/mod_feature/logic.ts
import { sum } from "@/lib/math";

const total = sum(10, 20);

2. Replacing Logic (Default)

By default, the Priority System applies a Replace logic. If two modules provide the same item name in lib/, the one with the highest priority completely replaces the other.

This is perfect for "patching" a third-party module's internal logic without touching its source code.


3. Extending/Merging Logic

Sometimes, you don't want to replace a class or interface; you want to add to it. This is a common pattern for plugins (e.g., adding a loyaltyPoints field to a User class).

To enable this, JopiJS uses Marker Files to change the behavior from Replace to Merge.

A. Extending a Class (class.merge)

When you use class.merge, JopiJS creates an inheritance chain. Your version will extend the previous one, allowing you to add new methods or fields.

Structure for Class Extension
src/
├── mod_core/
│   └── @alias/lib/User/
│       └── index.ts      # Base Class (fields: name, email)
└── mod_billing/
    └── @alias/lib/User/
        ├── index.ts      # Extension Class (add: invoiceHistory())
        ├── class.merge   # <--- Tells JopiJS to MERGE/EXTEND
        └── high.priority

B. Extending an Interface (interface.merge)

Since TypeScript supports "Interface Merging", JopiJS can combine multiple interface definitions into one.

mod_auth/@alias/lib/UserInfos/index.ts
export default interface UserInfos {
    id: string;
}
mod_profile/@alias/lib/UserInfos/index.ts
// This will be merged into the final UserInfos interface
export default interface UserInfos {
    avatarUrl: string;
}

Note: Ensure you include the interface.merge file in the folder so JopiJS knows how to handle the code generation.


4. Comparison Table

BehaviorMarker FileEffectUse Case
ReplaceNoneHigher priority replaces lower.Hotfixing a bug in a library.
Merge Classclass.mergeHigher priority extends lower.Adding features to a core entity.
Merge Interfaceinterface.mergeCombines all definitions.Adding shared typing/metadata.

Performance Tip: JopiJS does not use a full TypeScript parser to detect if an item is a class or an interface. These marker files (class.merge, interface.merge) are essential for JopiJS to generate the correct bridge code with high performance.


5. Practical Examples

Example 1: Adding a Method to a Class

In this example, mod_catalog provides the base Product class, and mod_pricing adds a method to calculate the price with taxes.

1. File Structure

Project Organization
src/
├── mod_catalog/
│   └── @alias/lib/Product/
│       └── index.ts
└── mod_pricing/
    └── @alias/lib/Product/
        ├── index.ts
        ├── class.merge      # <--- Enables merging
        └── high.priority    # <--- Gives priority to this extension

2. Original Class (in mod_catalog)

src/mod_catalog/@alias/lib/Product/index.ts
export default class Product {
    name: string = "Coffee Mug";
    basePrice: number = 10;
}

3. The Extension (in mod_pricing) You redéclare the class with the same name. JopiJS will bridge them.

src/mod_pricing/@alias/lib/Product/index.ts
export default class Product {
    getPriceWithTax() {
        // You can access 'basePrice' from the original class!
        return this.basePrice * 1.2; 
    }
}

Example 2: Merging Interface Fields

Adding inventory data to a shared product metadata type.

1. File Structure

Project Organization
src/
├── mod_catalog/
│   └── @alias/lib/ProductInfo/
│       └── index.ts
└── mod_inventory/
    └── @alias/lib/ProductInfo/
        ├── index.ts
        └── interface.merge   # <--- Enables interface merging

2. Original Interface (in mod_catalog)

src/mod_catalog/@alias/lib/ProductInfo/index.ts
export default interface ProductInfo {
    sku: string;
}

3. Adding Fields (in mod_inventory)

src/mod_inventory/@alias/lib/ProductInfo/index.ts
export default interface Profile {
    stockLevel: number;
    warehouseLocation: string;
}

Result: Everywhere you import ProductInfo from @/lib/ProductInfo, the object type will include sku, stockLevel, and warehouseLocation.


Summary

  • Use @alias/lib/ for shared business logic and types.
  • Import everything via the @/lib/ virtual path.
  • Use class.merge or interface.merge to add features to existing structures without breaking them.
  • This creates a highly extensible system where modules can "inject" logic into the core application seamlessly.