UNPKG

invoiceddd

Version:

Complete invoice system with domain-driven design - gateway package for easy integration

377 lines (290 loc) • 10.8 kB
# InvoiceDDD - Complete Invoice System > A comprehensive, domain-driven TypeScript package for invoice management with PDF generation, storage, and event handling. [![NPM Version](https://img.shields.io/npm/v/invoiceddd)](https://www.npmjs.com/package/invoiceddd) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![Effect](https://img.shields.io/badge/Effect-Powered-green.svg)](https://effect.website/) ## ✨ Features - **šŸ—ļø Domain-Driven Design**: Clean architecture with distinct domain, application, and infrastructure layers - **⚔ Effect-TS Powered**: Built on Effect framework for functional programming and composability - **šŸ“„ PDF Generation**: PDF/A-3 compliant invoices with German legal requirements - **šŸ’¾ Multiple Storage Options**: Support for local, S3, and custom storage backends - **šŸ”„ Event-Driven**: Domain events for extensibility and integration - **šŸŽÆ Type-Safe**: Full TypeScript support with runtime validation - **šŸ“¦ Tree-Shakable**: Modular design for optimal bundle sizes - **šŸš€ Production Ready**: Battle-tested architecture with comprehensive error handling ## šŸ“š Quick Start ### Installation ```bash npm install invoiceddd # or yarn add invoiceddd # or pnpm add invoiceddd ``` ### Important notes This package uses the infrastructure package, which provides implementations for database repositories. This means, when you use InvoiceDDD in a full-stack environment like Next.js, you can not import this package on the client side. We have **experimental** support for server-only imports. On the server, you can use `invoiceddd/server` to use server-only InvoiceDDD features (like migrations, database setup and more). On the client you can use InvoiceDDD as usual, without the server-only parts from the `invoiceddd` import. **NOTE**: currently, some parts of the infrastructure package are not server-only, but only available in `invoiceddd/server`. We are working on splitting that up more granularly in the future. However, most of them are useless on the client anyway (for example, `StorageService` is not dependant on any server-only part, but is only available in `invoiceddd/server`) - keep an eye on changes. They currently happen very frequently, so it shouldn't take too long. ### Basic Usage ```typescript import { InvoiceSystem } from 'invoiceddd'; // Create system with default configuration const system = await InvoiceSystem.create(); // Start HTTP server (optional) await system.startServer(); // Create an invoice const result = await system.createInvoice({ orderId: "ORD-2024-001", customerSnapshot: { fullName: "John Doe", email: "john@example.com", address: { street: "123 Main St", city: "Berlin", postalCode: "10115", country: "Germany" } }, itemsSnapshot: [{ name: "Premium Widget", unitPrice: 29.99, quantity: 2, linePrice: 59.98 }], financials: { subtotal: 59.98, taxes: 11.40, shippingCost: 5.99, discount: 0.00, grandTotal: 77.37, currency: "EUR" }, createdBy: "admin@company.com" }); if (result._tag === "Right") { console.log(`Invoice created: ${result.right.invoiceNumber}`); // PDF bytes available at: result.right.pdfBytes } ``` ### Custom Configuration ```typescript import { InvoiceSystem, ConfigurationBuilder } from 'invoiceddd'; const system = await InvoiceSystem.create({ config: { database: { url: 'file:./my-invoices.db' }, server: { port: 8080, host: '0.0.0.0' }, storage: { endpoint: 'https://s3.amazonaws.com', accessKey: process.env.AWS_ACCESS_KEY!, secretKey: process.env.AWS_SECRET_KEY!, bucket: 'invoice-pdfs' }, invoice: { prefix: 'INVOICE' } }, autoStartServer: true }); ``` ### Development Setup ```typescript import { InvoiceSystem, ConfigurationBuilder } from 'invoiceddd'; // Pre-configured development environment const devSystem = await InvoiceSystem.create({ config: ConfigurationBuilder.development({ database: { url: 'file:./dev-invoices.db' } }) }); ``` ## šŸ—ļø Package Architecture InvoiceDDD is built as a modular system with clear separation of concerns: ``` invoiceddd/ # Gateway package (this package) ā”œā”€ā”€ @invoiceddd/domain # Core business logic ā”œā”€ā”€ @invoiceddd/application # Use cases and ports └── @invoiceddd/infrastructure # External integrations ``` ### Individual Packages If you need more granular control, you can install individual packages: ```bash # Core domain logic only npm install @invoiceddd/domain # Application layer with use cases npm install @invoiceddd/application # Infrastructure implementations npm install @invoiceddd/infrastructure ``` ## šŸ“– Documentation - **[Getting Started Guide](../../docs/getting-started.md)** - Complete setup and first invoice - **[API Reference](../../docs/api-reference.md)** - Detailed API documentation - **[Configuration Guide](../../docs/configuration.md)** - All configuration options - **[Domain Model](../../docs/domain-model.md)** - Business rules and entities - **[Examples](../../examples/)** - Working code examples ## šŸŽÆ Use Cases ### E-commerce Integration ```typescript // In your order fulfillment process import { InvoiceSystem } from 'invoiceddd'; const invoiceSystem = await InvoiceSystem.create(); // When order is completed const invoice = await invoiceSystem.createInvoice({ orderId: order.id, customerSnapshot: { fullName: `${order.customer.firstName} ${order.customer.lastName}`, email: order.customer.email, address: order.shippingAddress }, itemsSnapshot: order.items.map(item => ({ name: item.product.name, unitPrice: item.price, quantity: item.quantity, linePrice: item.total })), financials: { subtotal: order.subtotal, taxes: order.taxes, shippingCost: order.shipping, discount: order.discount, grandTotal: order.total, currency: order.currency }, createdBy: order.processedBy }); ``` ### Subscription Billing ```typescript // For recurring invoices const system = await InvoiceSystem.create({ config: { invoice: { prefix: 'SUB' } // SUB-1001, SUB-1002, etc. } }); // Generate monthly invoice const invoice = await system.createInvoice({ orderId: `subscription-${customerId}-${month}`, // ... other details }); ``` ### Multi-tenant Applications ```typescript // Separate database per tenant const createTenantSystem = (tenantId: string) => InvoiceSystem.create({ config: { database: { url: `file:./invoices-${tenantId}.db` }, storage: { bucket: `invoices-${tenantId}` } } }); const tenantA = await createTenantSystem('tenant-a'); const tenantB = await createTenantSystem('tenant-b'); ``` ## šŸ”§ Advanced Configuration ### Custom Storage Provider ```typescript import { InvoiceSystem, StorageService } from 'invoiceddd'; import { Effect, Layer } from 'effect'; // Implement custom storage const CustomStorageLive = Layer.succeed(StorageService, { uploadFile: (key: string, content: Uint8Array) => Effect.sync(() => { // Your custom storage logic console.log(`Uploading ${key} to custom storage`); return { url: `https://mystorage.com/${key}` }; }) }); const system = await InvoiceSystem.create({ customLayers: { storage: CustomStorageLive } }); ``` ### Event Subscribers ```typescript import { InvoiceSystem } from 'invoiceddd'; const system = await InvoiceSystem.create({ subscribers: [ // Custom event handler (event) => { if (event._tag === 'InvoiceCreated') { console.log(`New invoice: ${event.invoiceNumber}`); // Send email, update CRM, etc. } } ] }); ``` ## Adding custom service implementations > The docs for the LayerBuilder API are not complete yet. We are working on it. With the flexible `LayerBuilder API`, you can dynamically construct your own service implementation layers with ease. This allows for selective, modular injection of custom implementations while keeping everything type-safe and robust. Look at this simple example: ```typescript import { InvoiceNumberGenerator } from 'invoiceddd'; import { LayerBuilder } from 'invoiceddd/layer-builder'; const layerConfig = LayerBuilder.start() .withCustomInvoiceNumberGenerator( InvoiceNumberGenerator.of({ // ... }) ) .build() const runtime = createSimpleCustomRuntime({}, layerConfig); // ^ ManagedRuntime<any, any> ``` Yes, the `ManagedRuntime` is untyped, but there is a fix in progress to automatically flatten and adapt the types from `LayerBuilder`. For most layer builder parts, you have two options to provide your service implementation: - **Most flexible**: Put in a custom `Layer`, like `Layer.succeed` or `Layer.effect`. All builder parts are just `Context.Tag`s, so it's highly compatible - **Less verbose**: Just give it the schema. For example, you can just do `InvoiceNumberGenerator.of({ ... })` - it is a shorter version of the first option. The option you choose depends on your needs. At the end, it's all Effect - do what you want. > [!NOTE] > `LayerBuilder` is also available in the `invoiceddd` import. But we recommend importing from `invoiceddd/layer-builder`, because it doesn't depend on external services that might be incompatible with browser environments. We are [working on it](#important-notes). ## šŸš€ Production Deployment ### Environment Variables ```bash # Database DATABASE_URL=postgresql://user:pass@localhost:5432/invoices # Server PORT=3000 HOST=0.0.0.0 # Storage (S3) STORAGE_ENDPOINT=https://s3.amazonaws.com STORAGE_ACCESS_KEY=your-access-key STORAGE_SECRET_KEY=your-secret-key STORAGE_BUCKET=production-invoices # Invoice settings INVOICE_PREFIX=INV ``` ### Docker Deployment ```dockerfile FROM oven/bun:1-alpine WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "start"] ``` ### Health Checks ```typescript const system = await InvoiceSystem.create(); // Built-in health check endpoint // GET /health returns system status await system.startServer(); ``` ## šŸ¤ Contributing We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details. ## šŸ“„ License MIT License - see [LICENSE](../../LICENSE) file for details. ## šŸ†˜ Support - **Issues**: [GitHub Issues](https://github.com/benjamin-kraatz/invoiceddd/issues) - **Discussions**: [GitHub Discussions](https://github.com/benjamin-kraatz/invoiceddd/discussions) - **Documentation**: [Full Documentation](../../docs/) --- **Made with ā¤ļø, Effect and TypeScript**