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:
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.
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.priorityB. Extending an Interface (interface.merge)
Since TypeScript supports "Interface Merging", JopiJS can combine multiple interface definitions into one.
export default interface UserInfos {
id: string;
}// 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
| Behavior | Marker File | Effect | Use Case |
|---|---|---|---|
| Replace | None | Higher priority replaces lower. | Hotfixing a bug in a library. |
| Merge Class | class.merge | Higher priority extends lower. | Adding features to a core entity. |
| Merge Interface | interface.merge | Combines 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
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 extension2. Original Class (in mod_catalog)
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.
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
src/
├── mod_catalog/
│ └── @alias/lib/ProductInfo/
│ └── index.ts
└── mod_inventory/
└── @alias/lib/ProductInfo/
├── index.ts
└── interface.merge # <--- Enables interface merging2. Original Interface (in mod_catalog)
export default interface ProductInfo {
sku: string;
}3. Adding Fields (in mod_inventory)
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.mergeorinterface.mergeto add features to existing structures without breaking them. - This creates a highly extensible system where modules can "inject" logic into the core application seamlessly.