[![npm version](https://img.shields.io/npm/v/@unchainedshop/core.svg)](https://npmjs.com/package/@unchainedshop/core) [![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://opensource.org/licenses/EUPL-1.2) # @unchainedshop/core Core orchestration package for the Unchained Engine. Integrates all core-* modules, provides business services, directors for plugins, and bulk import functionality. ## Installation ```bash npm install @unchainedshop/core ``` ## Usage ```typescript import { initCore, type UnchainedCore } from '@unchainedshop/core'; const unchainedCore = await initCore({ db, migrationRepository, options: { // Module-specific options }, }); // Access modules const products = await unchainedCore.modules.products.findProducts({}); // Use services const pricing = await unchainedCore.services.orders.pricingSheet(order); // Bulk import await unchainedCore.bulkImporter.prepare('PRODUCT'); ``` ## API Overview ### Initialization | Export | Description | |--------|-------------| | `initCore` | Initialize the core with all modules and services | | `getAllAdapters` | Get all registered adapters across all directors | ### Modules The `modules` object provides access to all core-* module APIs: | Module | Description | |--------|-------------| | `products` | Product management | | `assortments` | Category/assortment management | | `filters` | Product filtering and search | | `orders` | Order management | | `users` | User management | | `payment` | Payment providers | | `delivery` | Delivery providers | | `warehousing` | Inventory and stock | | `enrollments` | Subscriptions | | `quotations` | Quote management | | `bookmarks` | User bookmarks | | `countries` | Country management | | `currencies` | Currency management | | `languages` | Language management | | `files` | File management | | `events` | Event history | | `worker` | Background jobs | ### Services Business logic services that orchestrate multiple modules: | Service | Description | |---------|-------------| | `orders` | Order pricing, checkout workflows | | `products` | Product pricing calculations | | `users` | User-related operations | | `files` | File operations with adapters | ### Directors Plugin directors provide extensibility through the Director/Adapter pattern. Each director manages a collection of adapters that implement specific behaviors. | Director | Description | |----------|-------------| | `PaymentDirector` | Payment processing adapters | | `DeliveryDirector` | Delivery/shipping adapters | | `WarehousingDirector` | Inventory and stock adapters | | `WorkerDirector` | Background job workers | | `FilterDirector` | Product search and filtering | | `EnrollmentDirector` | Subscription plan handling | | `QuotationDirector` | Quote/RFQ processing | | `ProductPricingDirector` | Product price calculations | | `OrderPricingDirector` | Order total calculations | | `DeliveryPricingDirector` | Delivery fee calculations | | `PaymentPricingDirector` | Payment fee calculations | | `ProductDiscountDirector` | Product-level discounts | | `OrderDiscountDirector` | Order-level discounts | | `MessagingDirector` | Email/notification templates | ## Director/Adapter Architecture The Unchained Engine uses a Director/Adapter pattern for extensibility. Directors manage collections of adapters, and adapters implement specific behaviors. ### Base Classes ```typescript import { BaseAdapter, BaseDirector } from '@unchainedshop/utils'; ``` - **BaseDirector**: Factory function that creates a generic director with methods to register, unregister, and retrieve adapters - **BaseAdapter**: Base implementation providing logging and utility methods for all adapters ### Creating a Custom Adapter All adapters extend from `BaseAdapter` and must implement: ```typescript const MyAdapter = { key: 'my-adapter', // Unique identifier label: 'My Custom Adapter', // Human-readable label version: '1.0.0', // Adapter version // Adapter-specific methods... }; // Register with the appropriate director SomeDirector.registerAdapter(MyAdapter); ``` ### Payment Director Manages payment processing and orchestrates payment adapters. ```typescript import { PaymentDirector, type IPaymentAdapter } from '@unchainedshop/core'; const MyPaymentAdapter: IPaymentAdapter = { key: 'my-payment', label: 'My Payment Gateway', version: '1.0.0', typeSupported(type) { return type === 'CARD'; }, actions(config, context) { return { configurationError() { return null; }, isActive() { return true; }, isPayLaterAllowed() { return false; }, async charge() { // Process payment charge return { transactionId: '...' }; }, async confirm() { // Confirm payment return { transactionId: '...' }; }, async cancel() { // Cancel payment return true; }, async register() { // Register payment method return { token: '...' }; }, async sign() { // Sign payment request return '...'; }, async validate(token) { // Validate payment token return true; }, }; }, }; PaymentDirector.registerAdapter(MyPaymentAdapter); ``` ### Delivery Director Manages delivery operations and coordinates shipping adapters. ```typescript import { DeliveryDirector, type IDeliveryAdapter } from '@unchainedshop/core'; const MyDeliveryAdapter: IDeliveryAdapter = { key: 'my-delivery', label: 'My Shipping Provider', version: '1.0.0', typeSupported(type) { return type === 'SHIPPING'; }, actions(config, context) { return { configurationError() { return null; }, isActive() { return true; }, isAutoReleaseAllowed() { return false; }, async send() { // Trigger delivery return { trackingNumber: '...' }; }, estimatedDeliveryThroughput(warehousingTime) { // Return estimated delivery time in ms return 3 * 24 * 60 * 60 * 1000; // 3 days }, async pickUpLocations() { // Return available pickup locations return []; }, async pickUpLocationById(locationId) { return null; }, }; }, }; DeliveryDirector.registerAdapter(MyDeliveryAdapter); ``` ### Warehousing Director Manages inventory and stock operations, including NFT/token support. ```typescript import { WarehousingDirector, type IWarehousingAdapter } from '@unchainedshop/core'; const MyWarehousingAdapter: IWarehousingAdapter = { key: 'my-warehouse', label: 'My Inventory System', version: '1.0.0', typeSupported(type) { return type === 'PHYSICAL'; }, actions(config, context) { return { configurationError() { return null; }, isActive() { return true; }, async stock(referenceDate) { // Return current stock quantity return 100; }, async productionTime(quantity) { // Return production time in ms return 0; }, async commissioningTime(quantity) { // Return commissioning time in ms return 24 * 60 * 60 * 1000; // 1 day }, async estimatedStock() { return 100; }, async estimatedDispatch() { return new Date(); }, // For tokenized products (NFTs): async tokenize() { return []; }, async tokenMetadata(serial, date) { return {}; }, async isInvalidateable(serial, date) { return false; }, }; }, }; WarehousingDirector.registerAdapter(MyWarehousingAdapter); ``` ### Worker Director Manages background job processing and scheduled tasks. ```typescript import { WorkerDirector, type IWorkerAdapter } from '@unchainedshop/core'; interface MyInput { email: string; subject: string; } interface MyOutput { messageId: string; } const MyWorkerAdapter: IWorkerAdapter = { key: 'my-worker', label: 'My Background Worker', version: '1.0.0', type: 'MY_WORK_TYPE', // Work type identifier external: false, // Runs in-process maxParallelAllocations: 10, // Max concurrent executions async doWork(input, unchainedAPI, workId) { // Process the work item const { email, subject } = input; // Return result return { success: true, result: { messageId: 'msg-123' }, }; }, }; WorkerDirector.registerAdapter(MyWorkerAdapter); // Schedule recurring work WorkerDirector.configureAutoscheduling({ type: 'MY_WORK_TYPE', input: { email: 'test@example.com', subject: 'Test' }, schedule: '0 * * * *', // Every hour (cron syntax) }); ``` ### Pricing Directors Pricing directors calculate prices using a chain of adapters. Each adapter can add, modify, or discount prices. ```typescript import { ProductPricingDirector, type IProductPricingAdapter } from '@unchainedshop/core'; const MyPricingAdapter: IProductPricingAdapter = { key: 'my-pricing', label: 'My Pricing Logic', version: '1.0.0', orderIndex: 10, // Lower numbers run first isActivatedFor(context) { // Return true if this adapter should apply return true; }, actions(params) { return { calculate() { // Add price calculations this.calculation.addItem({ category: 'BASE', amount: 1000, // in smallest currency unit }); }, }; }, }; ProductPricingDirector.registerAdapter(MyPricingAdapter); ``` ### Discount Directors Discount directors manage coupon codes and automatic discounts. ```typescript import { OrderDiscountDirector, type IDiscountAdapter } from '@unchainedshop/core'; const MyDiscountAdapter: IDiscountAdapter = { key: 'my-discount', label: 'My Discount System', version: '1.0.0', orderIndex: 10, isManualAdditionAllowed(code) { return code.startsWith('PROMO'); }, isManualRemovalAllowed() { return true; }, actions(context) { return { isValidForSystemTriggering() { // Auto-apply discount? return false; }, isValidForCodeTriggering(code) { // Apply when code entered? return code === 'PROMO10'; }, discountForPricingAdapterKey(params) { // Return discount configuration for pricing adapter return { isNetPrice: false, rate: 0.1, // 10% off }; }, async reserve(code) { // Reserve discount (e.g., decrement coupon balance) }, async release() { // Release reservation on order cancellation }, }; }, }; OrderDiscountDirector.registerAdapter(MyDiscountAdapter); ``` ### Filter Director Manages product filtering and search functionality. ```typescript import { FilterDirector, type IFilterAdapter } from '@unchainedshop/core'; const MyFilterAdapter: IFilterAdapter = { key: 'my-filter', label: 'My Search Filter', version: '1.0.0', orderIndex: 10, actions(context) { return { async aggregateProductIds(params) { // Return product IDs matching filter return ['product-1', 'product-2']; }, async searchProducts(params, options) { // Search products return { productIds: [], totalCount: 0 }; }, async searchAssortments(params, options) { // Search assortments return { assortmentIds: [], totalCount: 0 }; }, transformProductSelector(selector, options) { // Modify MongoDB product selector return selector; }, transformFilterSelector(selector, options) { // Modify MongoDB filter selector return selector; }, transformSortStage(sort, options) { // Modify MongoDB sort stage return sort; }, }; }, }; FilterDirector.registerAdapter(MyFilterAdapter); ``` ### Messaging Director The Messaging Director uses a template resolver pattern instead of traditional adapters. ```typescript import { MessagingDirector } from '@unchainedshop/core'; // Register a message template MessagingDirector.registerTemplate('ORDER_CONFIRMATION', async (context) => { const { order, user } = context; return [ { type: 'EMAIL', input: { to: user.email, subject: `Order Confirmation #${order.orderNumber}`, html: '

Thank you for your order!

', }, }, { type: 'SMS', input: { to: user.phone, text: `Order #${order.orderNumber} confirmed!`, }, }, ]; }); ``` ### Quotation Director Handles quotation/RFQ (Request for Quote) operations. ```typescript import { QuotationDirector, type IQuotationAdapter } from '@unchainedshop/core'; const MyQuotationAdapter: IQuotationAdapter = { key: 'my-quotation', label: 'My Quote System', version: '1.0.0', isActivatedFor(quotationContext, unchainedAPI) { return true; }, actions(context) { return { configurationError() { return null; }, isManualProposalRequired() { return true; }, isManualRequestVerificationRequired() { return false; }, async quote() { // Generate quote return { price: 1000, currency: 'CHF' }; }, async submitRequest(quotationContext) { // Submit RFQ }, async verifyRequest(quotationContext) { // Verify RFQ }, async rejectRequest(quotationContext) { // Reject RFQ }, transformItemConfiguration(params) { return params.configuration; }, }; }, }; QuotationDirector.registerAdapter(MyQuotationAdapter); ``` ### Enrollment Director Manages subscription/enrollment plans and recurring billing. ```typescript import { EnrollmentDirector, type IEnrollmentAdapter } from '@unchainedshop/core'; const MyEnrollmentAdapter: IEnrollmentAdapter = { key: 'my-enrollment', label: 'My Subscription System', version: '1.0.0', isActivatedFor(productPlan) { return productPlan.type === 'PLAN_PRODUCT'; }, transformOrderItemToEnrollmentPlan(orderPosition, unchainedAPI) { return { configuration: orderPosition.configuration, }; }, actions(context) { return { async nextPeriod() { // Calculate next billing period return { start: new Date(), end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), }; }, isValidForActivation() { return true; }, isOverdue() { return false; }, async configurationForOrder(period) { // Return order configuration for period return {}; }, }; }, }; EnrollmentDirector.registerAdapter(MyEnrollmentAdapter); ``` ### Bulk Importer | Method | Description | |--------|-------------| | `bulkImporter.prepare` | Prepare bulk import for entity type | | `bulkImporter.process` | Process prepared import data | ### Types | Export | Description | |--------|-------------| | `UnchainedCore` | Core instance type | | `UnchainedCoreOptions` | Initialization options | | `Modules` | All modules type | | `Services` | All services type | | `BulkImporter` | Bulk importer type | ## Configuration ```typescript const core = await initCore({ db, migrationRepository, modules: { // Custom modules }, services: { // Custom services }, bulkImporter: { handlers: { // Custom import handlers }, }, options: { // Module-specific options }, }); ``` ## License EUPL-1.2