From 46e52ddb299ad58236fe170159e1feaef315b2ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:40:31 +0000 Subject: [PATCH 1/5] Initial plan From fd7b301790ff5a0d3c399a96d5639856b318b26b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:52:45 +0000 Subject: [PATCH 2/5] Add documentation and Strategy/Factory pattern examples - Created comprehensive Classical GoF Patterns documentation (11.2.4) - Implemented Strategy Pattern example in Python with payment processing - Implemented Factory Pattern example in Go with database connections - All examples include tests demonstrating SOLID principles - Strategy example demonstrates Open/Closed Principle - Factory example demonstrates Dependency Inversion Principle Co-authored-by: jburns24 <19497855+jburns24@users.noreply.github.com> --- .../11.2.4-classical-patterns.md | 686 ++++++++++++++++++ .../ch11/classical-patterns/factory/.keep | 0 .../classical-patterns/factory/factory.go | 261 +++++++ .../factory/factory_test.go | 207 ++++++ .../ch11/classical-patterns/factory/go.mod | 13 + .../ch11/classical-patterns/factory/main.go | 129 ++++ .../classical-patterns/strategy/README.md | 216 ++++++ .../strategy/pyproject.toml | 12 + .../classical-patterns/strategy/src/main.py | 77 ++ .../strategy/src/strategy.py | 168 +++++ .../strategy/tests/test_strategy.py | 130 ++++ .../ch11/classical-patterns/strategy/uv.lock | 78 ++ 12 files changed, 1977 insertions(+) create mode 100644 docs/11-application-development/11.2.4-classical-patterns.md create mode 100644 examples/ch11/classical-patterns/factory/.keep create mode 100644 examples/ch11/classical-patterns/factory/factory.go create mode 100644 examples/ch11/classical-patterns/factory/factory_test.go create mode 100644 examples/ch11/classical-patterns/factory/go.mod create mode 100644 examples/ch11/classical-patterns/factory/main.go create mode 100644 examples/ch11/classical-patterns/strategy/README.md create mode 100644 examples/ch11/classical-patterns/strategy/pyproject.toml create mode 100644 examples/ch11/classical-patterns/strategy/src/main.py create mode 100644 examples/ch11/classical-patterns/strategy/src/strategy.py create mode 100644 examples/ch11/classical-patterns/strategy/tests/test_strategy.py create mode 100644 examples/ch11/classical-patterns/strategy/uv.lock diff --git a/docs/11-application-development/11.2.4-classical-patterns.md b/docs/11-application-development/11.2.4-classical-patterns.md new file mode 100644 index 00000000..b59881f4 --- /dev/null +++ b/docs/11-application-development/11.2.4-classical-patterns.md @@ -0,0 +1,686 @@ +--- +docs/11-application-development/11.2.4-classical-patterns.md: + category: Software Development + estReadingMinutes: 45 + exercises: + - + name: Identify Classical Patterns in Production Code + description: Analyze a production codebase of your choice to identify Strategy, Factory, Observer, and Decorator patterns in use. + estMinutes: 90 + technologies: + - Design Patterns + - Python + - Go + - TypeScript +--- +# Classical Gang of Four Patterns + +The Gang of Four (GoF) design patterns, documented in the seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," catalog 23 design patterns that have become fundamental to software engineering. While all 23 patterns are valuable, this section focuses on four patterns that directly embody SOLID principles and are frequently encountered in production codebases. + +## Why These Four Patterns? + +We've selected these patterns based on three criteria: + +1. **Direct SOLID connections**: Each pattern explicitly demonstrates one or more SOLID principles in action +2. **Practical relevance**: These patterns appear frequently in modern enterprise applications +3. **Language agnostic**: They translate naturally across Python, Go, TypeScript, and other languages + +The patterns we'll cover: + +- **Strategy Pattern** (Behavioral) - Swappable algorithms demonstrating Open/Closed Principle +- **Factory Pattern** (Creational) - Object creation abstraction demonstrating Dependency Inversion Principle +- **Observer Pattern** (Behavioral) - Event-driven communication for loosely coupled systems +- **Decorator Pattern** (Structural) - Dynamic behavior extension demonstrating Open/Closed Principle + +## Pattern Organization + +The Gang of Four organized their 23 patterns into three categories based on their primary purpose: + +### Creational Patterns + +**Purpose**: Control object creation mechanisms to increase flexibility and reuse. + +Examples include Factory Method, Abstract Factory, Builder, Singleton, and Prototype. These patterns abstract the instantiation process, making systems independent of how objects are created, composed, and represented. + +**In this section**: Factory Pattern + +### Behavioral Patterns + +**Purpose**: Define communication patterns between objects and assign responsibilities. + +Examples include Observer, Strategy, Command, State, and Template Method. These patterns characterize complex control flow and communication between objects. + +**In this section**: Strategy Pattern, Observer Pattern + +### Structural Patterns + +**Purpose**: Compose classes and objects to form larger structures. + +Examples include Adapter, Bridge, Composite, Decorator, Facade, and Proxy. These patterns ease the design by identifying simple ways to realize relationships between entities. + +**In this section**: Decorator Pattern + +## Strategy Pattern + +> **Category**: Behavioral +> **SOLID Connection**: Open/Closed Principle +> **Use Case**: When you need interchangeable algorithms or behaviors + +### The Problem + +You have a class that needs to perform an operation, but the specific implementation of that operation varies. Hardcoding different implementations leads to brittle code full of conditional logic. + +```python +# BAD: Hardcoded payment logic +class OrderProcessor: + def process_payment(self, order, payment_type): + if payment_type == "credit_card": + # Credit card logic + return self._process_credit_card(order) + elif payment_type == "paypal": + # PayPal logic + return self._process_paypal(order) + elif payment_type == "bitcoin": + # Bitcoin logic + return self._process_bitcoin(order) + else: + raise ValueError("Unknown payment type") +``` + +**Problems**: +- Adding new payment methods requires modifying OrderProcessor (violates OCP) +- Testing each payment type requires testing the entire class +- Can't swap payment strategies at runtime easily + +### The Solution: Strategy Pattern + +The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The strategy lets the algorithm vary independently from clients that use it. + +```python +# GOOD: Strategy Pattern +from abc import ABC, abstractmethod + +class PaymentStrategy(ABC): + @abstractmethod + def process(self, order) -> bool: + pass + +class CreditCardPayment(PaymentStrategy): + def process(self, order) -> bool: + # Credit card specific logic + print(f"Processing ${order.total} via Credit Card") + return True + +class PayPalPayment(PaymentStrategy): + def process(self, order) -> bool: + # PayPal specific logic + print(f"Processing ${order.total} via PayPal") + return True + +class BitcoinPayment(PaymentStrategy): + def process(self, order) -> bool: + # Bitcoin specific logic + print(f"Processing ${order.total} via Bitcoin") + return True + +class OrderProcessor: + def __init__(self, payment_strategy: PaymentStrategy): + self.payment_strategy = payment_strategy + + def process_payment(self, order): + return self.payment_strategy.process(order) +``` + +**Benefits**: +- ✅ **Open/Closed**: Add new payment methods without modifying existing code +- ✅ **Single Responsibility**: Each strategy handles one payment type +- ✅ **Testability**: Test each strategy in isolation +- ✅ **Runtime flexibility**: Change payment strategies dynamically + +### Connection to Open/Closed Principle + +The Strategy Pattern is a direct implementation of OCP. The OrderProcessor is **closed for modification** (you never change it when adding payment methods) but **open for extension** (you add new payment strategies by creating new classes). + +See [Open/Closed Principle](11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp) for detailed explanation of OCP. + +### When to Use Strategy + +- ✅ You have multiple algorithms for a specific task +- ✅ Algorithms should be interchangeable at runtime +- ✅ You want to avoid conditional logic for algorithm selection +- ✅ Algorithms have similar interfaces but different implementations + +**Real-world examples**: Sorting algorithms, compression strategies, routing algorithms, payment processing, file export formats (PDF, CSV, JSON) + +### Implementation Example + +See the complete Python implementation with payment processing in: +`examples/ch11/classical-patterns/strategy/` + +## Factory Pattern + +> **Category**: Creational +> **SOLID Connection**: Dependency Inversion Principle +> **Use Case**: When you need to abstract object creation + +### The Problem + +Creating objects directly with constructors couples your code to specific classes. This makes it difficult to test and hard to change implementations. + +```go +// BAD: Direct instantiation couples code to MySQL +type UserService struct { + db *MySQLDatabase +} + +func NewUserService() *UserService { + return &UserService{ + db: &MySQLDatabase{ + host: "localhost", + port: 3306, + }, + } +} +``` + +**Problems**: +- UserService is tightly coupled to MySQL (can't test with different database) +- Switching databases requires changing UserService code +- Can't configure which database to use at runtime + +### The Solution: Factory Pattern + +The Factory Pattern defines an interface for creating objects but lets subclasses or implementations decide which concrete class to instantiate. + +```go +// GOOD: Factory Pattern +type Database interface { + Connect() error + Query(sql string) ([]Row, error) + Close() error +} + +type DatabaseFactory interface { + CreateDatabase(config Config) Database +} + +// Concrete factories +type MySQLFactory struct{} + +func (f *MySQLFactory) CreateDatabase(config Config) Database { + return &MySQLDatabase{ + host: config.Host, + port: config.Port, + } +} + +type PostgreSQLFactory struct{} + +func (f *PostgreSQLFactory) CreateDatabase(config Config) Database { + return &PostgreSQLDatabase{ + host: config.Host, + port: config.Port, + } +} + +// Client code depends on abstractions +type UserService struct { + db Database +} + +func NewUserService(factory DatabaseFactory, config Config) *UserService { + return &UserService{ + db: factory.CreateDatabase(config), + } +} +``` + +**Benefits**: +- ✅ **Dependency Inversion**: UserService depends on Database interface, not concrete classes +- ✅ **Testability**: Inject mock database for testing +- ✅ **Flexibility**: Switch database implementations via configuration +- ✅ **Encapsulation**: Creation logic is centralized and hidden + +### Connection to Dependency Inversion Principle + +The Factory Pattern embodies DIP by ensuring that high-level modules (UserService) depend on abstractions (Database interface) rather than concrete implementations (MySQLDatabase). The factory provides the concrete instance, but the client only knows about the interface. + +See [Dependency Inversion Principle](11-application-development/11.2.1-solid-principles.md#dependency-inversion-principle-dip) for detailed explanation of DIP. + +### When to Use Factory + +- ✅ Object creation is complex or requires configuration +- ✅ You want to decouple object creation from usage +- ✅ You need to create different types of objects based on conditions +- ✅ You want to centralize creation logic + +**Real-world examples**: Database connections, logger creation, document parsers (JSON, XML, YAML), HTTP clients, cloud service clients (AWS, GCP, Azure) + +### Implementation Example + +See the complete Go implementation with database factory in: +`examples/ch11/classical-patterns/factory/` + +## Observer Pattern + +> **Category**: Behavioral +> **SOLID Connection**: Dependency Inversion Principle, Interface Segregation +> **Use Case**: When you need one-to-many dependencies between objects + +### The Problem + +When one object's state changes, multiple other objects need to be notified and updated. Hardcoding these dependencies creates tight coupling. + +```typescript +// BAD: Tightly coupled notifications +class StockPrice { + private price: number = 0; + private chart: ChartDisplay; + private table: TableDisplay; + private alertSystem: AlertSystem; + + setPrice(newPrice: number) { + this.price = newPrice; + + // Tightly coupled to specific display types + this.chart.update(newPrice); + this.table.update(newPrice); + if (newPrice > 100) { + this.alertSystem.sendAlert(newPrice); + } + } +} +``` + +**Problems**: +- StockPrice is tightly coupled to specific display implementations +- Adding new displays requires modifying StockPrice +- Can't add/remove displays at runtime +- Hard to test in isolation + +### The Solution: Observer Pattern + +The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. + +```typescript +// GOOD: Observer Pattern +interface Observer { + update(price: number): void; +} + +class StockPrice { + private price: number = 0; + private observers: Observer[] = []; + + attach(observer: Observer): void { + this.observers.push(observer); + } + + detach(observer: Observer): void { + const index = this.observers.indexOf(observer); + if (index > -1) { + this.observers.splice(index, 1); + } + } + + setPrice(newPrice: number): void { + this.price = newPrice; + this.notify(); + } + + private notify(): void { + for (const observer of this.observers) { + observer.update(this.price); + } + } +} + +// Concrete observers +class ChartDisplay implements Observer { + update(price: number): void { + console.log(`Chart updated with price: $${price}`); + } +} + +class TableDisplay implements Observer { + update(price: number): void { + console.log(`Table updated with price: $${price}`); + } +} + +class AlertSystem implements Observer { + update(price: number): void { + if (price > 100) { + console.log(`ALERT: Price exceeded $100! Current: $${price}`); + } + } +} +``` + +**Benefits**: +- ✅ **Loose coupling**: Subject doesn't know concrete observer types +- ✅ **Open/Closed**: Add new observers without modifying subject +- ✅ **Dynamic relationships**: Attach/detach observers at runtime +- ✅ **Broadcast communication**: One change notifies multiple observers + +### Connection to SOLID Principles + +Observer Pattern demonstrates multiple SOLID principles: + +- **Dependency Inversion**: Subject depends on Observer interface, not concrete implementations +- **Interface Segregation**: Observer interface is small and focused (just `update()`) +- **Open/Closed**: Add new observers without modifying subject + +### When to Use Observer + +- ✅ Multiple objects need to react to state changes +- ✅ You want loose coupling between subject and observers +- ✅ Set of observing objects can change at runtime +- ✅ Broadcasting changes to unknown number of objects + +**Real-world examples**: Event systems, UI updates (React, Vue), pub/sub messaging, model-view updates (MVC), websocket notifications, monitoring systems + +### Implementation Example + +See the complete TypeScript implementation with stock price monitoring in: +`examples/ch11/classical-patterns/observer/` + +## Decorator Pattern + +> **Category**: Structural +> **SOLID Connection**: Open/Closed Principle, Single Responsibility +> **Use Case**: When you need to add responsibilities to objects dynamically + +### The Problem + +You need to add features or behaviors to objects, but inheritance creates rigid hierarchies and subclass explosion. + +```python +# BAD: Inheritance explosion +class Coffee: + def cost(self) -> float: + return 2.0 + +class CoffeeWithMilk(Coffee): + def cost(self) -> float: + return 2.5 + +class CoffeeWithSugar(Coffee): + def cost(self) -> float: + return 2.3 + +class CoffeeWithMilkAndSugar(Coffee): + def cost(self) -> float: + return 2.8 + +# Need CoffeeWithMilkAndSugarAndWhippedCream? +# Need CoffeeWithMilkAndWhippedCream? +# This explodes combinatorially! +``` + +**Problems**: +- Need a class for every combination of features +- Can't add features dynamically at runtime +- Fragile inheritance hierarchy +- Violates Open/Closed Principle (must modify hierarchy for new features) + +### The Solution: Decorator Pattern + +The Decorator Pattern attaches additional responsibilities to objects dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. + +```python +# GOOD: Decorator Pattern +from abc import ABC, abstractmethod + +class Beverage(ABC): + @abstractmethod + def cost(self) -> float: + pass + + @abstractmethod + def description(self) -> str: + pass + +class Coffee(Beverage): + def cost(self) -> float: + return 2.0 + + def description(self) -> str: + return "Coffee" + +# Decorator base class +class BeverageDecorator(Beverage): + def __init__(self, beverage: Beverage): + self._beverage = beverage + +# Concrete decorators +class MilkDecorator(BeverageDecorator): + def cost(self) -> float: + return self._beverage.cost() + 0.5 + + def description(self) -> str: + return self._beverage.description() + ", Milk" + +class SugarDecorator(BeverageDecorator): + def cost(self) -> float: + return self._beverage.cost() + 0.3 + + def description(self) -> str: + return self._beverage.description() + ", Sugar" + +class WhippedCreamDecorator(BeverageDecorator): + def cost(self) -> float: + return self._beverage.cost() + 0.7 + + def description(self) -> str: + return self._beverage.description() + ", Whipped Cream" + +# Usage: Build combinations dynamically +coffee = Coffee() +coffee_with_milk = MilkDecorator(coffee) +coffee_with_milk_and_sugar = SugarDecorator(coffee_with_milk) +fancy_coffee = WhippedCreamDecorator(coffee_with_milk_and_sugar) + +print(f"{fancy_coffee.description()}: ${fancy_coffee.cost()}") +# Output: Coffee, Milk, Sugar, Whipped Cream: $3.5 +``` + +**Benefits**: +- ✅ **Open/Closed**: Add new decorators without modifying existing code +- ✅ **Single Responsibility**: Each decorator adds one feature +- ✅ **Flexibility**: Combine decorators in any order at runtime +- ✅ **Transparency**: Decorated objects look like base objects + +### Connection to Open/Closed Principle + +The Decorator Pattern is a textbook implementation of OCP. The base Beverage class is **closed for modification** (you never change Coffee), but **open for extension** (you add features by wrapping with decorators). + +Unlike the Strategy Pattern which swaps entire algorithms, Decorator adds layers of functionality while maintaining the same interface. + +See [Open/Closed Principle](11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp) for detailed explanation of OCP. + +### When to Use Decorator + +- ✅ Add responsibilities to objects dynamically +- ✅ Avoid subclass explosion from feature combinations +- ✅ Need to add/remove responsibilities at runtime +- ✅ Extension by subclassing is impractical + +**Real-world examples**: I/O streams (BufferedReader wrapping FileReader), middleware chains (Express, Django), UI components (borders, scrollbars), logging wrappers, caching layers + +### Implementation Example + +See the complete Python implementation with coffee shop in: +`examples/ch11/classical-patterns/decorator/` + +## Pattern Recognition in Production Code + +Identifying design patterns in existing codebases is a crucial skill. Here's how to recognize the four patterns we've covered: + +### Strategy Pattern Recognition + +**Look for**: +- Interface or abstract class defining an algorithm +- Multiple concrete implementations of that interface +- Context class that uses the strategy via dependency injection +- No conditional logic (if/elif/switch) for algorithm selection + +**Code signatures**: +```python +class SomeContext: + def __init__(self, strategy: StrategyInterface): + self.strategy = strategy +``` + +**Common naming**: `*Strategy`, `*Algorithm`, `*Handler`, `*Policy` + +### Factory Pattern Recognition + +**Look for**: +- Methods or classes with "Factory" in the name +- Methods that return interface types, not concrete classes +- Static methods like `create()`, `build()`, `newInstance()` +- Configuration-based object creation + +**Code signatures**: +```go +func NewClient(config Config) ClientInterface { + // Returns interface, not concrete type +} +``` + +**Common naming**: `*Factory`, `Create*`, `New*`, `Build*`, `Make*` + +### Observer Pattern Recognition + +**Look for**: +- Methods like `attach()`, `detach()`, `subscribe()`, `unsubscribe()` +- Methods like `notify()`, `notifyAll()`, `publish()`, `emit()` +- Listener or Observer interfaces +- Collections of observers/listeners maintained by subject + +**Code signatures**: +```typescript +interface EventListener { + onEvent(event: Event): void; +} + +class EventEmitter { + private listeners: EventListener[] = []; + subscribe(listener: EventListener): void { /* ... */ } +} +``` + +**Common naming**: `*Observer`, `*Listener`, `*Subscriber`, `*Handler`, event emitters + +### Decorator Pattern Recognition + +**Look for**: +- Classes that wrap other classes of the same interface +- Constructor accepting an object of the interface being decorated +- Methods that call the wrapped object and add behavior +- Nested wrapping (decorator wrapping decorator) + +**Code signatures**: +```python +class Decorator(BaseInterface): + def __init__(self, wrapped: BaseInterface): + self._wrapped = wrapped + + def operation(self): + # Add behavior before/after + return self._wrapped.operation() +``` + +**Common naming**: `*Decorator`, `*Wrapper`, stream classes (BufferedReader), middleware + +## Pattern Recognition Exercise + +### Self-Directed Learning + +Choose a production codebase you have access to (could be open-source or from work/internship) and complete the following: + +1. **Search for Strategy Pattern**: + - Find at least one example where multiple algorithms are swapped + - Document: What interface defines the strategy? What are the concrete implementations? + - Analyze: Could this be improved? Is it following OCP? + +2. **Search for Factory Pattern**: + - Find at least one example of object creation abstraction + - Document: What objects are being created? How does it support DIP? + - Analyze: Is the factory tested independently? + +3. **Search for Observer Pattern**: + - Find at least one event system or notification mechanism + - Document: What is the subject? What are the observers? + - Analyze: Can observers be added/removed dynamically? + +4. **Search for Decorator Pattern**: + - Find at least one example of behavior wrapping + - Document: What is being decorated? What behaviors are added? + - Analyze: Could you add more decorators without modifying existing code? + +### Reflection Questions + +After your exploration, consider: + +- Which pattern was easiest to find? Why? +- Which pattern was hardest to recognize? Why? +- Did you find any anti-patterns (patterns used incorrectly)? +- How do these patterns contribute to the codebase's maintainability? +- Which SOLID principles are most evident in the patterns you found? + +### Recommended Codebases + +If you need suggestions for codebases to explore: + +- **Python**: Django (web framework), Requests (HTTP library), Flask (web framework) +- **Go**: Kubernetes (container orchestration), Docker (containerization), Terraform (IaC) +- **TypeScript**: VSCode (editor), React (UI library), NestJS (backend framework) +- **Java**: Spring Framework, JUnit (testing), Apache Commons + +Look for patterns in core libraries and frameworks—they tend to be excellent examples of pattern usage. + +## Quiz + +```quiz +quiz: /src/quizzes/chapter-11/11.2.4/classical-patterns-quiz.js +``` + +## Summary + +The four classical Gang of Four patterns we've covered represent essential tools for writing maintainable, extensible code: + +| Pattern | Category | SOLID Connection | Use Case | +|---------|----------|------------------|----------| +| **Strategy** | Behavioral | Open/Closed | Swappable algorithms | +| **Factory** | Creational | Dependency Inversion | Object creation abstraction | +| **Observer** | Behavioral | Dependency Inversion, Interface Segregation | Event-driven updates | +| **Decorator** | Structural | Open/Closed, Single Responsibility | Dynamic behavior extension | + +### Key Takeaways + +1. **Patterns embody SOLID**: Each pattern is a practical application of one or more SOLID principles +2. **Patterns solve specific problems**: Don't use patterns for the sake of using patterns—use them when they solve real problems +3. **Patterns are language-agnostic**: The same pattern works in Python, Go, TypeScript, or any OO language +4. **Patterns enable communication**: Saying "use the Strategy Pattern" conveys a design idea instantly + +### When NOT to Use Patterns + +Remember the principle of **YAGNI** (You Aren't Gonna Need It): + +- Don't use Strategy if you only have one algorithm and no plans for more +- Don't use Factory if you're only creating one type of object +- Don't use Observer if you have one subscriber that never changes +- Don't use Decorator if you're not adding multiple layers of behavior + +**Premature abstraction is as bad as premature optimization**. Use patterns when you see the need, not speculatively. + +### Next Steps + +- Review [SOLID Principles](11-application-development/11.2.1-solid-principles.md) to deepen your understanding of the foundations +- Explore the complete code examples in `examples/ch11/classical-patterns/` +- Complete the pattern recognition exercise with a real codebase +- Study the remaining 19 GoF patterns when you need them + +The Gang of Four patterns have stood the test of time because they solve real problems in maintainable ways. Understanding these four gives you a strong foundation for recognizing and applying patterns throughout your career. diff --git a/examples/ch11/classical-patterns/factory/.keep b/examples/ch11/classical-patterns/factory/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/ch11/classical-patterns/factory/factory.go b/examples/ch11/classical-patterns/factory/factory.go new file mode 100644 index 00000000..64b65dee --- /dev/null +++ b/examples/ch11/classical-patterns/factory/factory.go @@ -0,0 +1,261 @@ +// Package factory demonstrates the Factory Pattern for creating database connections. +// +// The Factory Pattern embodies the Dependency Inversion Principle by ensuring +// that high-level code (like UserService) depends on abstractions (Database interface) +// rather than concrete implementations (MySQL, PostgreSQL, etc.). +package main + +import ( + "fmt" + "strings" +) + +// Database defines the interface that all database implementations must satisfy. +// This abstraction allows different database types to be used interchangeably. +type Database interface { + Connect() error + Query(sql string) ([]map[string]interface{}, error) + Close() error + GetType() string +} + +// DatabaseConfig holds configuration for database connections +type DatabaseConfig struct { + Host string + Port int + Username string + Password string + Database string +} + +// DatabaseFactory defines the interface for creating databases. +// This allows different factory implementations for different database types. +type DatabaseFactory interface { + CreateDatabase(config DatabaseConfig) (Database, error) +} + +// ============================================================================= +// MySQL Implementation +// ============================================================================= + +// MySQLDatabase implements the Database interface for MySQL +type MySQLDatabase struct { + config DatabaseConfig + connected bool +} + +// Connect establishes a MySQL connection +func (db *MySQLDatabase) Connect() error { + fmt.Printf("Connecting to MySQL at %s:%d...\n", db.config.Host, db.config.Port) + // In real implementation: open actual MySQL connection + db.connected = true + fmt.Println("✓ MySQL connection established") + return nil +} + +// Query executes a SQL query on MySQL +func (db *MySQLDatabase) Query(sql string) ([]map[string]interface{}, error) { + if !db.connected { + return nil, fmt.Errorf("database not connected") + } + fmt.Printf("MySQL Query: %s\n", sql) + // In real implementation: execute actual query + return []map[string]interface{}{ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"}, + }, nil +} + +// Close closes the MySQL connection +func (db *MySQLDatabase) Close() error { + fmt.Println("Closing MySQL connection") + db.connected = false + return nil +} + +// GetType returns the database type +func (db *MySQLDatabase) GetType() string { + return "MySQL" +} + +// MySQLFactory creates MySQL database instances +type MySQLFactory struct{} + +// CreateDatabase creates a new MySQL database instance +func (f *MySQLFactory) CreateDatabase(config DatabaseConfig) (Database, error) { + fmt.Println("Factory: Creating MySQL database instance") + return &MySQLDatabase{ + config: config, + connected: false, + }, nil +} + +// ============================================================================= +// PostgreSQL Implementation +// ============================================================================= + +// PostgreSQLDatabase implements the Database interface for PostgreSQL +type PostgreSQLDatabase struct { + config DatabaseConfig + connected bool +} + +// Connect establishes a PostgreSQL connection +func (db *PostgreSQLDatabase) Connect() error { + fmt.Printf("Connecting to PostgreSQL at %s:%d...\n", db.config.Host, db.config.Port) + // In real implementation: open actual PostgreSQL connection + db.connected = true + fmt.Println("✓ PostgreSQL connection established") + return nil +} + +// Query executes a SQL query on PostgreSQL +func (db *PostgreSQLDatabase) Query(sql string) ([]map[string]interface{}, error) { + if !db.connected { + return nil, fmt.Errorf("database not connected") + } + fmt.Printf("PostgreSQL Query: %s\n", sql) + // In real implementation: execute actual query + return []map[string]interface{}{ + {"id": 1, "name": "Charlie"}, + {"id": 2, "name": "Diana"}, + }, nil +} + +// Close closes the PostgreSQL connection +func (db *PostgreSQLDatabase) Close() error { + fmt.Println("Closing PostgreSQL connection") + db.connected = false + return nil +} + +// GetType returns the database type +func (db *PostgreSQLDatabase) GetType() string { + return "PostgreSQL" +} + +// PostgreSQLFactory creates PostgreSQL database instances +type PostgreSQLFactory struct{} + +// CreateDatabase creates a new PostgreSQL database instance +func (f *PostgreSQLFactory) CreateDatabase(config DatabaseConfig) (Database, error) { + fmt.Println("Factory: Creating PostgreSQL database instance") + return &PostgreSQLDatabase{ + config: config, + connected: false, + }, nil +} + +// ============================================================================= +// SQLite Implementation +// ============================================================================= + +// SQLiteDatabase implements the Database interface for SQLite +type SQLiteDatabase struct { + config DatabaseConfig + connected bool +} + +// Connect establishes a SQLite connection +func (db *SQLiteDatabase) Connect() error { + fmt.Printf("Opening SQLite database: %s\n", db.config.Database) + // In real implementation: open actual SQLite file + db.connected = true + fmt.Println("✓ SQLite database opened") + return nil +} + +// Query executes a SQL query on SQLite +func (db *SQLiteDatabase) Query(sql string) ([]map[string]interface{}, error) { + if !db.connected { + return nil, fmt.Errorf("database not connected") + } + fmt.Printf("SQLite Query: %s\n", sql) + // In real implementation: execute actual query + return []map[string]interface{}{ + {"id": 1, "name": "Eve"}, + {"id": 2, "name": "Frank"}, + }, nil +} + +// Close closes the SQLite database +func (db *SQLiteDatabase) Close() error { + fmt.Println("Closing SQLite database") + db.connected = false + return nil +} + +// GetType returns the database type +func (db *SQLiteDatabase) GetType() string { + return "SQLite" +} + +// SQLiteFactory creates SQLite database instances +type SQLiteFactory struct{} + +// CreateDatabase creates a new SQLite database instance +func (f *SQLiteFactory) CreateDatabase(config DatabaseConfig) (Database, error) { + fmt.Println("Factory: Creating SQLite database instance") + return &SQLiteDatabase{ + config: config, + connected: false, + }, nil +} + +// ============================================================================= +// Factory Registry (Simple Factory Pattern) +// ============================================================================= + +// GetDatabaseFactory returns the appropriate factory for the given database type. +// This is a simple factory that creates factory instances. +func GetDatabaseFactory(dbType string) (DatabaseFactory, error) { + switch strings.ToLower(dbType) { + case "mysql": + return &MySQLFactory{}, nil + case "postgresql", "postgres": + return &PostgreSQLFactory{}, nil + case "sqlite": + return &SQLiteFactory{}, nil + default: + return nil, fmt.Errorf("unsupported database type: %s", dbType) + } +} + +// ============================================================================= +// UserService (Demonstrates Dependency Inversion Principle) +// ============================================================================= + +// UserService is a high-level service that depends on the Database abstraction. +// This demonstrates the Dependency Inversion Principle: +// - High-level module (UserService) does NOT depend on low-level modules (MySQL, PostgreSQL) +// - Both depend on abstraction (Database interface) +type UserService struct { + db Database +} + +// NewUserService creates a new UserService with dependency injection. +// The database is injected (provided from outside) rather than created internally. +// This is the "Hollywood Principle": "Don't call us, we'll call you" +func NewUserService(db Database) *UserService { + return &UserService{db: db} +} + +// Initialize connects to the database +func (s *UserService) Initialize() error { + return s.db.Connect() +} + +// GetAllUsers retrieves all users from the database +func (s *UserService) GetAllUsers() ([]map[string]interface{}, error) { + return s.db.Query("SELECT * FROM users") +} + +// Shutdown closes the database connection +func (s *UserService) Shutdown() error { + return s.db.Close() +} + +// GetDatabaseType returns the type of database being used +func (s *UserService) GetDatabaseType() string { + return s.db.GetType() +} diff --git a/examples/ch11/classical-patterns/factory/factory_test.go b/examples/ch11/classical-patterns/factory/factory_test.go new file mode 100644 index 00000000..c8970f68 --- /dev/null +++ b/examples/ch11/classical-patterns/factory/factory_test.go @@ -0,0 +1,207 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Test database factory creation +func TestGetDatabaseFactory(t *testing.T) { + tests := []struct { + name string + dbType string + wantErr bool + }{ + {"MySQL factory", "mysql", false}, + {"PostgreSQL factory", "postgresql", false}, + {"Postgres alias", "postgres", false}, + {"SQLite factory", "sqlite", false}, + {"Invalid type", "invalid", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory, err := GetDatabaseFactory(tt.dbType) + + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, factory) + } else { + assert.NoError(t, err) + assert.NotNil(t, factory) + } + }) + } +} + +// Test MySQL implementation +func TestMySQLDatabase(t *testing.T) { + config := DatabaseConfig{ + Host: "localhost", + Port: 3306, + Database: "testdb", + } + + factory := &MySQLFactory{} + db, err := factory.CreateDatabase(config) + require.NoError(t, err) + require.NotNil(t, db) + + assert.Equal(t, "MySQL", db.GetType()) + + // Test connection + err = db.Connect() + assert.NoError(t, err) + + // Test query + results, err := db.Query("SELECT * FROM users") + assert.NoError(t, err) + assert.NotEmpty(t, results) + + // Test close + err = db.Close() + assert.NoError(t, err) +} + +// Test PostgreSQL implementation +func TestPostgreSQLDatabase(t *testing.T) { + config := DatabaseConfig{ + Host: "localhost", + Port: 5432, + Database: "testdb", + } + + factory := &PostgreSQLFactory{} + db, err := factory.CreateDatabase(config) + require.NoError(t, err) + require.NotNil(t, db) + + assert.Equal(t, "PostgreSQL", db.GetType()) + + err = db.Connect() + assert.NoError(t, err) + + results, err := db.Query("SELECT * FROM users") + assert.NoError(t, err) + assert.NotEmpty(t, results) + + err = db.Close() + assert.NoError(t, err) +} + +// Test SQLite implementation +func TestSQLiteDatabase(t *testing.T) { + config := DatabaseConfig{ + Database: "./test.db", + } + + factory := &SQLiteFactory{} + db, err := factory.CreateDatabase(config) + require.NoError(t, err) + require.NotNil(t, db) + + assert.Equal(t, "SQLite", db.GetType()) + + err = db.Connect() + assert.NoError(t, err) + + results, err := db.Query("SELECT * FROM users") + assert.NoError(t, err) + assert.NotEmpty(t, results) + + err = db.Close() + assert.NoError(t, err) +} + +// Test UserService with dependency injection +func TestUserService(t *testing.T) { + config := DatabaseConfig{ + Host: "localhost", + Port: 3306, + Database: "testdb", + } + + factory := &MySQLFactory{} + db, err := factory.CreateDatabase(config) + require.NoError(t, err) + + // Create service with injected dependency + service := NewUserService(db) + assert.NotNil(t, service) + + // Test initialization + err = service.Initialize() + assert.NoError(t, err) + + // Test getting database type + assert.Equal(t, "MySQL", service.GetDatabaseType()) + + // Test retrieving users + users, err := service.GetAllUsers() + assert.NoError(t, err) + assert.NotEmpty(t, users) + + // Test shutdown + err = service.Shutdown() + assert.NoError(t, err) +} + +// Test Dependency Inversion Principle +// UserService should work with ANY database implementation +func TestDependencyInversionPrinciple(t *testing.T) { + dbTypes := []string{"mysql", "postgresql", "sqlite"} + + for _, dbType := range dbTypes { + t.Run(dbType, func(t *testing.T) { + factory, err := GetDatabaseFactory(dbType) + require.NoError(t, err) + + config := DatabaseConfig{ + Host: "localhost", + Port: 3306, + Database: "test.db", + } + + db, err := factory.CreateDatabase(config) + require.NoError(t, err) + + // UserService works with ANY database without modification + service := NewUserService(db) + err = service.Initialize() + assert.NoError(t, err) + + users, err := service.GetAllUsers() + assert.NoError(t, err) + assert.NotEmpty(t, users) + + err = service.Shutdown() + assert.NoError(t, err) + }) + } +} + +// Test that UserService doesn't depend on concrete implementations +func TestUserServiceDoesNotDependOnConcreteTypes(t *testing.T) { + // This test demonstrates that UserService only knows about the Database interface + // It has no knowledge of MySQL, PostgreSQL, or SQLite concrete types + + // Create different database instances + mysqlFactory, _ := GetDatabaseFactory("mysql") + mysqlDB, _ := mysqlFactory.CreateDatabase(DatabaseConfig{}) + + postgresFactory, _ := GetDatabaseFactory("postgresql") + postgresDB, _ := postgresFactory.CreateDatabase(DatabaseConfig{}) + + // UserService treats them identically through the interface + service1 := NewUserService(mysqlDB) + service2 := NewUserService(postgresDB) + + // Both services work the same way despite different underlying implementations + assert.NotNil(t, service1) + assert.NotNil(t, service2) + + // The services don't know or care about concrete types + // They only depend on the Database interface (DIP in action!) +} diff --git a/examples/ch11/classical-patterns/factory/go.mod b/examples/ch11/classical-patterns/factory/go.mod new file mode 100644 index 00000000..c752852b --- /dev/null +++ b/examples/ch11/classical-patterns/factory/go.mod @@ -0,0 +1,13 @@ +module github.com/liatrio/devops-bootcamp/examples/ch11/classical-patterns/factory + +go 1.21 + +require ( + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/ch11/classical-patterns/factory/main.go b/examples/ch11/classical-patterns/factory/main.go new file mode 100644 index 00000000..5bae8e9e --- /dev/null +++ b/examples/ch11/classical-patterns/factory/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + "log" + "strings" +) + +func main() { + fmt.Println("=== Factory Pattern Demo: Database Connections ===\n") + + // Configuration + config := DatabaseConfig{ + Host: "localhost", + Port: 3306, + Username: "admin", + Password: "secret", + Database: "myapp", + } + + // Example 1: MySQL using Factory + fmt.Println("--- Example 1: MySQL Database ---") + runExample("mysql", config) + + fmt.Println() + + // Example 2: PostgreSQL using Factory + fmt.Println("--- Example 2: PostgreSQL Database ---") + postgresConfig := config + postgresConfig.Port = 5432 + runExample("postgresql", postgresConfig) + + fmt.Println() + + // Example 3: SQLite using Factory + fmt.Println("--- Example 3: SQLite Database ---") + sqliteConfig := DatabaseConfig{ + Database: "./myapp.db", + } + runExample("sqlite", sqliteConfig) + + fmt.Println() + + // Example 4: Demonstrating Dependency Inversion Principle + fmt.Println("--- Example 4: Dependency Inversion Principle ---") + demonstrateDIP() + + fmt.Println("\n" + strings.Repeat("=", 60)) + fmt.Println("Demo completed!") + fmt.Println(strings.Repeat("=", 60)) + fmt.Println("\nKey Takeaway:") + fmt.Println("UserService depends on the Database INTERFACE, not concrete implementations.") + fmt.Println("This is the Dependency Inversion Principle in action!") + fmt.Println("The factory provides the concrete instance, but the client only knows the interface.") +} + +func runExample(dbType string, config DatabaseConfig) { + // Get the appropriate factory + factory, err := GetDatabaseFactory(dbType) + if err != nil { + log.Fatalf("Error getting factory: %v", err) + } + + // Create database instance using factory + db, err := factory.CreateDatabase(config) + if err != nil { + log.Fatalf("Error creating database: %v", err) + } + + // Create service with dependency injection + service := NewUserService(db) + + // Use the service + if err := service.Initialize(); err != nil { + log.Fatalf("Error initializing service: %v", err) + } + + fmt.Printf("Service is using: %s\n", service.GetDatabaseType()) + + users, err := service.GetAllUsers() + if err != nil { + log.Fatalf("Error getting users: %v", err) + } + + fmt.Printf("Retrieved %d users\n", len(users)) + + if err := service.Shutdown(); err != nil { + log.Fatalf("Error shutting down: %v", err) + } + + fmt.Println("✓ Example completed successfully") +} + +func demonstrateDIP() { + fmt.Println("\nWithout Factory Pattern (VIOLATES DIP):") + fmt.Println(" service := &UserService{") + fmt.Println(" db: &MySQLDatabase{...}, // ❌ Tight coupling to MySQL") + fmt.Println(" }") + fmt.Println(" Problem: UserService is tightly coupled to MySQL") + + fmt.Println("\nWith Factory Pattern (FOLLOWS DIP):") + fmt.Println(" factory := GetDatabaseFactory(\"mysql\")") + fmt.Println(" db := factory.CreateDatabase(config)") + fmt.Println(" service := NewUserService(db) // ✓ Depends on interface") + fmt.Println(" Solution: UserService depends on Database interface") + + fmt.Println("\nBenefits of DIP with Factory:") + fmt.Println(" 1. Easy to switch databases via configuration") + fmt.Println(" 2. Easy to test with mock databases") + fmt.Println(" 3. UserService never changes when adding new database types") + fmt.Println(" 4. Dependencies are injected, not created internally") + + // Demonstrate switching databases easily + fmt.Println("\nDemonstrating easy database switching:") + + databases := []string{"mysql", "postgresql", "sqlite"} + for _, dbType := range databases { + factory, _ := GetDatabaseFactory(dbType) + db, _ := factory.CreateDatabase(DatabaseConfig{ + Host: "localhost", + Port: 3306, + Database: "test.db", + }) + + // Same UserService code works with ANY database! + service := NewUserService(db) + fmt.Printf(" - Created service with %s (no code changes!)\n", service.GetDatabaseType()) + } +} diff --git a/examples/ch11/classical-patterns/strategy/README.md b/examples/ch11/classical-patterns/strategy/README.md new file mode 100644 index 00000000..510decde --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/README.md @@ -0,0 +1,216 @@ +# Strategy Pattern Example: Payment Processing + +> **Pattern Category**: Behavioral +> **SOLID Connection**: Open/Closed Principle +> **Language**: Python 3.11+ + +## Overview + +This example demonstrates the **Strategy Pattern** through a payment processing system. The pattern allows swapping payment methods (credit card, PayPal, Bitcoin, bank transfer) at runtime without modifying the core `OrderProcessor` class. + +## The Strategy Pattern + +The Strategy Pattern defines a family of algorithms (payment methods), encapsulates each one behind a common interface (`PaymentStrategy`), and makes them interchangeable. The algorithm varies independently from clients that use it. + +### Key Components + +1. **Strategy Interface** (`PaymentStrategy`): Defines the contract all payment strategies must implement +2. **Concrete Strategies** (`CreditCardPayment`, `PayPalPayment`, etc.): Implement specific payment methods +3. **Context** (`OrderProcessor`): Uses a strategy to process orders, can switch strategies at runtime + +## Connection to Open/Closed Principle + +This implementation perfectly demonstrates the **Open/Closed Principle** from [SOLID Principles](../../../../docs/11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp): + +### Closed for Modification + +The `OrderProcessor` class is **closed for modification** - you NEVER need to change it when adding new payment methods: + +```python +class OrderProcessor: + def __init__(self, payment_strategy: PaymentStrategy): + self._payment_strategy = payment_strategy + + def process_order(self, order: Order) -> bool: + # This method never changes, regardless of how many + # payment strategies we add + return self._payment_strategy.process(order) +``` + +### Open for Extension + +The system is **open for extension** - you can add unlimited new payment strategies without touching existing code: + +```python +# Adding a new payment method is as simple as creating a new class +class ApplePayPayment(PaymentStrategy): + def process(self, order: Order) -> bool: + # Apple Pay specific logic + return True + + def get_name(self) -> str: + return "Apple Pay" + +# Use it immediately without modifying OrderProcessor +processor = OrderProcessor(ApplePayPayment()) +processor.process_order(my_order) +``` + +### Before Strategy Pattern (Violates OCP) + +Without the Strategy Pattern, you'd have code like this: + +```python +class OrderProcessor: + def process_order(self, order, payment_type): + if payment_type == "credit_card": + # Process credit card + elif payment_type == "paypal": + # Process PayPal + elif payment_type == "bitcoin": + # Process Bitcoin + # Every new payment type requires modifying this method! +``` + +**Problem**: Adding a new payment method requires modifying the `OrderProcessor` class, violating OCP. + +### With Strategy Pattern (Follows OCP) + +```python +# OrderProcessor never changes +class OrderProcessor: + def __init__(self, payment_strategy: PaymentStrategy): + self._payment_strategy = payment_strategy + + def process_order(self, order: Order) -> bool: + return self._payment_strategy.process(order) + +# Add new payment methods by creating new classes +class NewPaymentMethod(PaymentStrategy): + def process(self, order: Order) -> bool: + # Implementation + return True +``` + +**Solution**: New payment methods are new classes implementing `PaymentStrategy`. Zero modifications to `OrderProcessor`. + +## Setup + +### Prerequisites + +- Python 3.11 or higher +- `uv` for dependency management (install with: `pip install uv`) + +### Installation + +```bash +# Navigate to this directory +cd examples/ch11/classical-patterns/strategy + +# Install dependencies (including dev dependencies for testing) +uv sync --extra dev +``` + +## Running the Demo + +```bash +# Run the demonstration +uv run src/main.py +``` + +Expected output shows: +- Processing orders with different payment methods +- Switching payment strategies at runtime +- How OrderProcessor remains unchanged when adding payment methods + +## Running Tests + +```bash +# Run all tests +uv run pytest + +# Run with verbose output +uv run pytest -v + +# Run specific test file +uv run pytest tests/test_strategy.py -v + +# Run with coverage +uv run pytest --cov=src +``` + +All tests should pass, demonstrating: +- Each payment strategy works correctly +- Strategies are interchangeable +- Runtime strategy switching works +- OrderProcessor is never modified (OCP) + +## Project Structure + +``` +strategy/ +├── src/ +│ ├── strategy.py # Strategy interface and concrete implementations +│ └── main.py # Executable demo +├── tests/ +│ └── test_strategy.py # Unit tests +├── pyproject.toml # Project configuration and dependencies +└── README.md # This file +``` + +## When to Use Strategy Pattern + +✅ **Use Strategy when**: +- You have multiple algorithms for a specific task +- Algorithms should be interchangeable at runtime +- You want to avoid conditional logic for algorithm selection +- Different variants of an algorithm need to coexist + +❌ **Don't use Strategy when**: +- You only have one algorithm with no plans for more (YAGNI) +- The algorithm never changes +- The overhead of the pattern outweighs the benefits + +## Real-World Applications + +The Strategy Pattern is widely used in production code: + +1. **Payment Processing**: As demonstrated (Stripe, PayPal, cryptocurrency) +2. **Sorting Algorithms**: QuickSort vs MergeSort vs HeapSort +3. **Compression**: ZIP vs GZIP vs BZIP2 +4. **Routing**: Different pathfinding algorithms (Dijkstra, A*, BFS) +5. **Authentication**: OAuth, SAML, Basic Auth, JWT +6. **File Export**: PDF, CSV, JSON, XML formatters +7. **Validation**: Different validation rules per context +8. **Pricing**: Regular, seasonal, member, bulk pricing strategies + +## Key Takeaways + +1. **OCP in Action**: Strategy Pattern perfectly demonstrates Open/Closed Principle +2. **Runtime Flexibility**: Change behavior without changing code +3. **Testability**: Each strategy can be tested in isolation +4. **Maintainability**: Adding features doesn't risk breaking existing code +5. **Scalability**: Unlimited strategies without code modification + +## Learning Exercise + +Try extending this example: + +1. Add a new payment strategy (e.g., Apple Pay, Google Pay, cryptocurrency wallet) +2. Add validation to payment strategies (e.g., check card expiration, wallet balance) +3. Add a logging decorator to track payment attempts +4. Implement a factory to select strategies based on configuration + +Notice how you can do all of this **without modifying the OrderProcessor class**. That's the power of the Open/Closed Principle! + +## Related Patterns + +- **Factory Pattern**: Often used to create the appropriate strategy +- **Decorator Pattern**: Can wrap strategies to add behavior (logging, validation) +- **State Pattern**: Similar structure but strategies represent states with transitions + +## Further Reading + +- [SOLID Principles - Open/Closed Principle](../../../../docs/11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp) +- [Gang of Four Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) +- [Refactoring Guru - Strategy Pattern](https://refactoring.guru/design-patterns/strategy) diff --git a/examples/ch11/classical-patterns/strategy/pyproject.toml b/examples/ch11/classical-patterns/strategy/pyproject.toml new file mode 100644 index 00000000..93481d55 --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "strategy-pattern" +version = "0.1.0" +description = "Strategy Pattern Example - Payment Processing System" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", +] diff --git a/examples/ch11/classical-patterns/strategy/src/main.py b/examples/ch11/classical-patterns/strategy/src/main.py new file mode 100644 index 00000000..701e6786 --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/src/main.py @@ -0,0 +1,77 @@ +""" +Main demo for Strategy Pattern. + +This demo shows how the Strategy Pattern allows swapping payment methods +at runtime without modifying the OrderProcessor class. +""" +from strategy import ( + Order, + OrderProcessor, + CreditCardPayment, + PayPalPayment, + BitcoinPayment, + BankTransferPayment, +) + + +def main(): + """Demonstrate the Strategy Pattern with different payment methods.""" + + # Create sample orders + order1 = Order(order_id="ORD-001", total=99.99, customer_email="alice@example.com") + order2 = Order(order_id="ORD-002", total=249.50, customer_email="bob@example.com") + order3 = Order(order_id="ORD-003", total=1499.99, customer_email="charlie@example.com") + order4 = Order(order_id="ORD-004", total=59.99, customer_email="diana@example.com") + + print("=== Strategy Pattern Demo: Payment Processing ===\n") + + # Example 1: Process order with Credit Card + print("\n--- Example 1: Credit Card Payment ---") + credit_card = CreditCardPayment(card_number="4532123456789012", cvv="123") + processor = OrderProcessor(credit_card) + processor.process_order(order1) + + # Example 2: Change strategy to PayPal at runtime + print("\n--- Example 2: Switching to PayPal ---") + paypal = PayPalPayment(email="bob@example.com") + processor.set_payment_strategy(paypal) + processor.process_order(order2) + + # Example 3: Create new processor with Bitcoin strategy + print("\n--- Example 3: Bitcoin Payment ---") + bitcoin = BitcoinPayment(wallet_address="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") + bitcoin_processor = OrderProcessor(bitcoin) + bitcoin_processor.process_order(order3) + + # Example 4: Bank Transfer payment + print("\n--- Example 4: Bank Transfer Payment ---") + bank_transfer = BankTransferPayment( + account_number="9876543210", + routing_number="123456789" + ) + processor.set_payment_strategy(bank_transfer) + processor.process_order(order4) + + # Example 5: Demonstrate runtime flexibility + print("\n--- Example 5: Multiple Orders with Different Strategies ---") + orders = [ + (order1, CreditCardPayment("4532111111111111", "456")), + (order2, PayPalPayment("customer@email.com")), + (order3, BitcoinPayment("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")), + ] + + for order, strategy in orders: + processor.set_payment_strategy(strategy) + processor.process_order(order) + + print("\n" + "="*60) + print("Demo completed!") + print("="*60) + print("\nKey Takeaway:") + print("The OrderProcessor class was NEVER modified to add new payment methods.") + print("We extended functionality by creating new strategy classes.") + print("This is the Open/Closed Principle in action!") + + +if __name__ == "__main__": + main() diff --git a/examples/ch11/classical-patterns/strategy/src/strategy.py b/examples/ch11/classical-patterns/strategy/src/strategy.py new file mode 100644 index 00000000..d0caa6c3 --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/src/strategy.py @@ -0,0 +1,168 @@ +""" +Strategy Pattern Example - Payment Processing + +Demonstrates the Strategy Pattern by implementing different payment methods +that can be swapped at runtime. This pattern embodies the Open/Closed Principle: +the OrderProcessor is closed for modification (we never change it) but open +for extension (we can add new payment strategies). +""" +from abc import ABC, abstractmethod +from dataclasses import dataclass + + +@dataclass +class Order: + """Represents a customer order.""" + order_id: str + total: float + customer_email: str + + +class PaymentStrategy(ABC): + """ + Abstract strategy interface defining the contract for payment processing. + + This interface allows different payment implementations to be used + interchangeably by the OrderProcessor. + """ + + @abstractmethod + def process(self, order: Order) -> bool: + """ + Process payment for the given order. + + Args: + order: The order to process payment for + + Returns: + True if payment successful, False otherwise + """ + pass + + @abstractmethod + def get_name(self) -> str: + """Return the name of this payment method.""" + pass + + +class CreditCardPayment(PaymentStrategy): + """Credit card payment strategy.""" + + def __init__(self, card_number: str, cvv: str): + self.card_number = card_number[-4:] # Store only last 4 digits + self.cvv = cvv + + def process(self, order: Order) -> bool: + """Process credit card payment.""" + print(f"Processing ${order.total:.2f} via Credit Card ending in {self.card_number}") + # In real implementation: call payment gateway API + return True + + def get_name(self) -> str: + return "Credit Card" + + +class PayPalPayment(PaymentStrategy): + """PayPal payment strategy.""" + + def __init__(self, email: str): + self.email = email + + def process(self, order: Order) -> bool: + """Process PayPal payment.""" + print(f"Processing ${order.total:.2f} via PayPal account {self.email}") + # In real implementation: redirect to PayPal OAuth flow + return True + + def get_name(self) -> str: + return "PayPal" + + +class BitcoinPayment(PaymentStrategy): + """Bitcoin payment strategy.""" + + def __init__(self, wallet_address: str): + self.wallet_address = wallet_address + + def process(self, order: Order) -> bool: + """Process Bitcoin payment.""" + print(f"Processing ${order.total:.2f} via Bitcoin to wallet {self.wallet_address[:10]}...") + # In real implementation: generate payment request on blockchain + return True + + def get_name(self) -> str: + return "Bitcoin" + + +class BankTransferPayment(PaymentStrategy): + """Bank transfer payment strategy.""" + + def __init__(self, account_number: str, routing_number: str): + self.account_number = account_number[-4:] # Store only last 4 digits + self.routing_number = routing_number + + def process(self, order: Order) -> bool: + """Process bank transfer payment.""" + print(f"Processing ${order.total:.2f} via Bank Transfer from account ending in {self.account_number}") + # In real implementation: initiate ACH transfer + return True + + def get_name(self) -> str: + return "Bank Transfer" + + +class OrderProcessor: + """ + Context class that uses a payment strategy. + + This class demonstrates the Open/Closed Principle: + - CLOSED for modification: We never modify this class when adding new payment methods + - OPEN for extension: We can add unlimited payment strategies without changing this code + + The strategy can be set at construction or changed at runtime, providing + flexibility without coupling to specific payment implementations. + """ + + def __init__(self, payment_strategy: PaymentStrategy): + """ + Initialize with a payment strategy. + + Args: + payment_strategy: The payment strategy to use + """ + self._payment_strategy = payment_strategy + + def set_payment_strategy(self, payment_strategy: PaymentStrategy) -> None: + """ + Change the payment strategy at runtime. + + Args: + payment_strategy: The new payment strategy to use + """ + self._payment_strategy = payment_strategy + + def process_order(self, order: Order) -> bool: + """ + Process an order using the current payment strategy. + + Args: + order: The order to process + + Returns: + True if order processed successfully, False otherwise + """ + print(f"\n{'='*60}") + print(f"Processing Order #{order.order_id}") + print(f"Total: ${order.total:.2f}") + print(f"Payment Method: {self._payment_strategy.get_name()}") + print(f"{'='*60}") + + success = self._payment_strategy.process(order) + + if success: + print(f"✓ Order #{order.order_id} processed successfully!") + # In real implementation: send confirmation email, update inventory, etc. + else: + print(f"✗ Order #{order.order_id} failed!") + + return success diff --git a/examples/ch11/classical-patterns/strategy/tests/test_strategy.py b/examples/ch11/classical-patterns/strategy/tests/test_strategy.py new file mode 100644 index 00000000..3518c141 --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/tests/test_strategy.py @@ -0,0 +1,130 @@ +""" +Unit tests for Strategy Pattern implementation. + +These tests verify that each payment strategy works correctly and that +the OrderProcessor can switch between strategies. +""" +import sys +from pathlib import Path + +# Add src directory to path +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +import pytest +from strategy import ( + Order, + OrderProcessor, + CreditCardPayment, + PayPalPayment, + BitcoinPayment, + BankTransferPayment, +) + + +@pytest.fixture +def sample_order(): + """Create a sample order for testing.""" + return Order( + order_id="TEST-001", + total=99.99, + customer_email="test@example.com" + ) + + +class TestPaymentStrategies: + """Test each payment strategy implementation.""" + + def test_credit_card_payment(self, sample_order): + """Test credit card payment strategy.""" + strategy = CreditCardPayment(card_number="4532123456789012", cvv="123") + assert strategy.get_name() == "Credit Card" + assert strategy.process(sample_order) is True + + def test_paypal_payment(self, sample_order): + """Test PayPal payment strategy.""" + strategy = PayPalPayment(email="test@example.com") + assert strategy.get_name() == "PayPal" + assert strategy.process(sample_order) is True + + def test_bitcoin_payment(self, sample_order): + """Test Bitcoin payment strategy.""" + strategy = BitcoinPayment(wallet_address="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") + assert strategy.get_name() == "Bitcoin" + assert strategy.process(sample_order) is True + + def test_bank_transfer_payment(self, sample_order): + """Test bank transfer payment strategy.""" + strategy = BankTransferPayment( + account_number="9876543210", + routing_number="123456789" + ) + assert strategy.get_name() == "Bank Transfer" + assert strategy.process(sample_order) is True + + +class TestOrderProcessor: + """Test the OrderProcessor context class.""" + + def test_process_order_with_credit_card(self, sample_order): + """Test processing an order with credit card.""" + strategy = CreditCardPayment(card_number="4532123456789012", cvv="123") + processor = OrderProcessor(strategy) + assert processor.process_order(sample_order) is True + + def test_process_order_with_paypal(self, sample_order): + """Test processing an order with PayPal.""" + strategy = PayPalPayment(email="test@example.com") + processor = OrderProcessor(strategy) + assert processor.process_order(sample_order) is True + + def test_change_strategy_at_runtime(self, sample_order): + """Test changing payment strategy at runtime.""" + # Start with credit card + credit_card = CreditCardPayment(card_number="4532123456789012", cvv="123") + processor = OrderProcessor(credit_card) + assert processor.process_order(sample_order) is True + + # Switch to PayPal + paypal = PayPalPayment(email="test@example.com") + processor.set_payment_strategy(paypal) + assert processor.process_order(sample_order) is True + + # Switch to Bitcoin + bitcoin = BitcoinPayment(wallet_address="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") + processor.set_payment_strategy(bitcoin) + assert processor.process_order(sample_order) is True + + def test_multiple_processors_different_strategies(self): + """Test multiple processors with different strategies work independently.""" + order1 = Order(order_id="ORD-001", total=50.00, customer_email="alice@example.com") + order2 = Order(order_id="ORD-002", total=100.00, customer_email="bob@example.com") + + processor1 = OrderProcessor(CreditCardPayment("4532123456789012", "123")) + processor2 = OrderProcessor(PayPalPayment("bob@example.com")) + + assert processor1.process_order(order1) is True + assert processor2.process_order(order2) is True + + +class TestOpenClosedPrinciple: + """Tests demonstrating the Open/Closed Principle.""" + + def test_adding_new_strategy_does_not_modify_processor(self, sample_order): + """ + Verify that adding a new payment strategy does not require + modifying the OrderProcessor class (Open/Closed Principle). + """ + # This test demonstrates OCP by showing that OrderProcessor + # works with any PaymentStrategy implementation without modification + + # Original strategies work + processor = OrderProcessor(CreditCardPayment("4532123456789012", "123")) + assert processor.process_order(sample_order) is True + + # New strategy (BankTransfer) works without modifying OrderProcessor + new_strategy = BankTransferPayment("9876543210", "123456789") + processor.set_payment_strategy(new_strategy) + assert processor.process_order(sample_order) is True + + # OrderProcessor code remains unchanged - it's closed for modification + # but open for extension through new PaymentStrategy implementations diff --git a/examples/ch11/classical-patterns/strategy/uv.lock b/examples/ch11/classical-patterns/strategy/uv.lock new file mode 100644 index 00000000..5b220d62 --- /dev/null +++ b/examples/ch11/classical-patterns/strategy/uv.lock @@ -0,0 +1,78 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "strategy-pattern" +version = "0.1.0" +source = { virtual = "." } + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }] +provides-extras = ["dev"] From 4935681c30ed9c990a8ae631db8455a9fb6bc47e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:57:13 +0000 Subject: [PATCH 3/5] Add Observer Pattern TypeScript example and Factory README - Implemented Observer Pattern in TypeScript with stock price monitoring - Created subject (StockPrice) and multiple observers (Chart, Table, Alert, Logger) - Demonstrates loose coupling and dynamic observer management - Added comprehensive tests for Observer pattern - Completed Factory Pattern README with DIP explanation - All Factory Pattern tests passing Co-authored-by: jburns24 <19497855+jburns24@users.noreply.github.com> --- .../ch11/classical-patterns/factory/README.md | 274 ++++++++++++++++++ .../ch11/classical-patterns/factory/go.mod | 4 +- .../ch11/classical-patterns/factory/go.sum | 10 + .../ch11/classical-patterns/factory/main.go | 3 +- .../ch11/classical-patterns/observer/.keep | 0 .../observer/jest.config.js | 10 + .../classical-patterns/observer/package.json | 27 ++ .../classical-patterns/observer/src/main.ts | 94 ++++++ .../observer/src/observer.ts | 155 ++++++++++ .../observer/src/subject.ts | 117 ++++++++ .../observer/tests/observer.test.ts | 215 ++++++++++++++ .../classical-patterns/observer/tsconfig.json | 20 ++ 12 files changed, 925 insertions(+), 4 deletions(-) create mode 100644 examples/ch11/classical-patterns/factory/README.md create mode 100644 examples/ch11/classical-patterns/factory/go.sum create mode 100644 examples/ch11/classical-patterns/observer/.keep create mode 100644 examples/ch11/classical-patterns/observer/jest.config.js create mode 100644 examples/ch11/classical-patterns/observer/package.json create mode 100644 examples/ch11/classical-patterns/observer/src/main.ts create mode 100644 examples/ch11/classical-patterns/observer/src/observer.ts create mode 100644 examples/ch11/classical-patterns/observer/src/subject.ts create mode 100644 examples/ch11/classical-patterns/observer/tests/observer.test.ts create mode 100644 examples/ch11/classical-patterns/observer/tsconfig.json diff --git a/examples/ch11/classical-patterns/factory/README.md b/examples/ch11/classical-patterns/factory/README.md new file mode 100644 index 00000000..68d166ae --- /dev/null +++ b/examples/ch11/classical-patterns/factory/README.md @@ -0,0 +1,274 @@ +# Factory Pattern Example: Database Connections + +> **Pattern Category**: Creational +> **SOLID Connection**: Dependency Inversion Principle +> **Language**: Go 1.21+ + +## Overview + +This example demonstrates the **Factory Pattern** through a database connection system. The pattern abstracts object creation, allowing clients to work with database interfaces rather than concrete implementations (MySQL, PostgreSQL, SQLite). + +## The Factory Pattern + +The Factory Pattern defines an interface for creating objects but lets implementations decide which concrete class to instantiate. This decouples object creation from object usage. + +### Key Components + +1. **Product Interface** (`Database`): Defines the contract all database implementations must satisfy +2. **Concrete Products** (`MySQLDatabase`, `PostgreSQLDatabase`, `SQLiteDatabase`): Specific database implementations +3. **Factory Interface** (`DatabaseFactory`): Defines the creation contract +4. **Concrete Factories** (`MySQLFactory`, `PostgreSQLFactory`, `SQLiteFactory`): Create specific database instances +5. **Client** (`UserService`): Uses the Database interface without knowing concrete types + +## Connection to Dependency Inversion Principle + +This implementation perfectly demonstrates the **Dependency Inversion Principle** from [SOLID Principles](../../../../docs/11-application-development/11.2.1-solid-principles.md#dependency-inversion-principle-dip): + +### High-Level Modules Depend on Abstractions + +The `UserService` (high-level module) depends on the `Database` interface (abstraction), not on concrete implementations (MySQL, PostgreSQL): + +```go +type UserService struct { + db Database // ✓ Depends on interface, not concrete type +} + +func NewUserService(db Database) *UserService { + return &UserService{db: db} +} +``` + +### Low-Level Modules Also Depend on Abstractions + +Concrete database implementations (`MySQLDatabase`, `PostgreSQLDatabase`) implement the `Database` interface: + +```go +type MySQLDatabase struct { /* ... */ } + +func (db *MySQLDatabase) Connect() error { /* ... */ } +func (db *MySQLDatabase) Query(sql string) ([]map[string]interface{}, error) { /* ... */ } +// Implements Database interface +``` + +### Before Factory Pattern (Violates DIP) + +Without the Factory Pattern, you'd have code like this: + +```go +type UserService struct { + db *MySQLDatabase // ❌ Tightly coupled to MySQL +} + +func NewUserService() *UserService { + return &UserService{ + db: &MySQLDatabase{ // ❌ Creates concrete type internally + host: "localhost", + port: 3306, + }, + } +} +``` + +**Problems**: +- UserService is tightly coupled to MySQL +- Can't test with mock database +- Can't switch databases without modifying UserService +- Violates DIP (high-level module depends on low-level module) + +### With Factory Pattern (Follows DIP) + +```go +// UserService depends on abstraction +type UserService struct { + db Database // ✓ Interface, not concrete type +} + +// Dependency is injected from outside +func NewUserService(db Database) *UserService { + return &UserService{db: db} +} + +// Factory creates the concrete type +factory := GetDatabaseFactory("mysql") +db := factory.CreateDatabase(config) + +// Client receives abstraction +service := NewUserService(db) // ✓ Depends on interface +``` + +**Benefits**: +- ✅ **DIP Compliance**: High-level module (UserService) depends on abstraction (Database) +- ✅ **Testability**: Inject mock database for testing +- ✅ **Flexibility**: Switch databases via configuration +- ✅ **Maintainability**: Add new database types without changing UserService + +## The Hollywood Principle + +The Factory Pattern embodies the "Hollywood Principle": **"Don't call us, we'll call you"** + +Instead of UserService creating its own dependencies (calling): +```go +// DON'T do this (calling) +service := &UserService{ + db: &MySQLDatabase{...}, +} +``` + +Dependencies are provided to UserService (we'll call you): +```go +// DO this (we'll call you) +db := factory.CreateDatabase(config) +service := NewUserService(db) // Dependency injected +``` + +## Setup + +### Prerequisites + +- Go 1.21 or higher +- Go modules enabled (default in modern Go) + +### Installation + +```bash +# Navigate to this directory +cd examples/ch11/classical-patterns/factory + +# Download dependencies +go mod download + +# Or use go mod tidy to clean up +go mod tidy +``` + +## Running the Demo + +```bash +# Run the demonstration +go run . + +# Or explicitly +go run main.go factory.go +``` + +Expected output shows: +- Creating different database instances using factories +- UserService working identically with different databases +- Demonstration of DIP and easy database switching + +## Running Tests + +```bash +# Run all tests +go test -v + +# Run specific test +go test -v -run TestDependencyInversionPrinciple + +# Run with coverage +go test -cover + +# Run with detailed coverage +go test -coverprofile=coverage.out +go tool cover -html=coverage.out +``` + +All tests should pass, demonstrating: +- Each database factory works correctly +- Each database implementation satisfies the interface +- UserService works with any database (DIP) +- No dependency on concrete types + +## Project Structure + +``` +factory/ +├── factory.go # Database interface, factories, and implementations +├── main.go # Executable demo +├── factory_test.go # Unit tests +├── go.mod # Go module definition +└── README.md # This file +``` + +## When to Use Factory Pattern + +✅ **Use Factory when**: +- Object creation is complex or requires configuration +- You want to decouple creation from usage +- You need to create different types based on conditions +- You want to centralize creation logic + +❌ **Don't use Factory when**: +- You're only creating one type of object +- Object creation is trivial (just `new()` or struct literal) +- The overhead outweighs the benefits (YAGNI) + +## Real-World Applications + +The Factory Pattern is ubiquitous in production code: + +1. **Database Connections**: As demonstrated (MySQL, PostgreSQL, SQLite, MongoDB) +2. **HTTP Clients**: Creating clients for different APIs or services +3. **Cloud Providers**: AWS, GCP, Azure resource creation +4. **Loggers**: Creating different logger implementations (file, console, remote) +5. **Document Parsers**: JSON, XML, YAML, TOML parsers +6. **Payment Gateways**: Stripe, PayPal, Square clients +7. **Message Queues**: RabbitMQ, Kafka, SQS producers/consumers +8. **Authentication Providers**: OAuth, SAML, LDAP authenticators + +## Key Takeaways + +1. **DIP in Action**: Factory Pattern enables Dependency Inversion Principle +2. **Loose Coupling**: Clients depend on interfaces, not implementations +3. **Testability**: Easy to inject mocks for testing +4. **Flexibility**: Switch implementations via configuration +5. **Hollywood Principle**: Dependencies are injected, not created internally + +## Learning Exercise + +Try extending this example: + +1. Add a new database type (e.g., MongoDB, Redis) +2. Add connection pooling to database implementations +3. Add error handling and retry logic +4. Implement a configuration file to select database type +5. Add metrics/logging to track database usage + +Notice how you can do all of this **without modifying the UserService**. The service only knows about the `Database` interface. That's the power of the Dependency Inversion Principle! + +## Testing with Mocks + +One of the biggest benefits of DIP with Factory is easy testing: + +```go +// Create a mock database for testing +type MockDatabase struct{} + +func (m *MockDatabase) Connect() error { return nil } +func (m *MockDatabase) Query(sql string) ([]map[string]interface{}, error) { + return []map[string]interface{}{{"id": 1, "name": "test"}}, nil +} +func (m *MockDatabase) Close() error { return nil } +func (m *MockDatabase) GetType() string { return "Mock" } + +// Use it in tests +func TestUserService(t *testing.T) { + mockDB := &MockDatabase{} + service := NewUserService(mockDB) // ✓ Works perfectly! + // Test without real database +} +``` + +## Related Patterns + +- **Abstract Factory**: Creates families of related objects +- **Builder**: Constructs complex objects step by step +- **Singleton**: Ensures only one instance exists (can combine with Factory) +- **Strategy**: Often created by factories (Factory + Strategy is powerful) + +## Further Reading + +- [SOLID Principles - Dependency Inversion Principle](../../../../docs/11-application-development/11.2.1-solid-principles.md#dependency-inversion-principle-dip) +- [Gang of Four Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) +- [Refactoring Guru - Factory Method](https://refactoring.guru/design-patterns/factory-method) +- [Go Interfaces and Dependency Injection](https://golang.org/doc/effective_go#interfaces) diff --git a/examples/ch11/classical-patterns/factory/go.mod b/examples/ch11/classical-patterns/factory/go.mod index c752852b..cb74af47 100644 --- a/examples/ch11/classical-patterns/factory/go.mod +++ b/examples/ch11/classical-patterns/factory/go.mod @@ -2,9 +2,7 @@ module github.com/liatrio/devops-bootcamp/examples/ch11/classical-patterns/facto go 1.21 -require ( - github.com/stretchr/testify v1.10.0 -) +require github.com/stretchr/testify v1.10.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/examples/ch11/classical-patterns/factory/go.sum b/examples/ch11/classical-patterns/factory/go.sum new file mode 100644 index 00000000..713a0b4f --- /dev/null +++ b/examples/ch11/classical-patterns/factory/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/ch11/classical-patterns/factory/main.go b/examples/ch11/classical-patterns/factory/main.go index 5bae8e9e..3c5cac25 100644 --- a/examples/ch11/classical-patterns/factory/main.go +++ b/examples/ch11/classical-patterns/factory/main.go @@ -7,7 +7,8 @@ import ( ) func main() { - fmt.Println("=== Factory Pattern Demo: Database Connections ===\n") + fmt.Println("=== Factory Pattern Demo: Database Connections ===") + fmt.Println() // Configuration config := DatabaseConfig{ diff --git a/examples/ch11/classical-patterns/observer/.keep b/examples/ch11/classical-patterns/observer/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/ch11/classical-patterns/observer/jest.config.js b/examples/ch11/classical-patterns/observer/jest.config.js new file mode 100644 index 00000000..3ba104c4 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/tests'], + testMatch: ['**/*.test.ts'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts' + ] +}; diff --git a/examples/ch11/classical-patterns/observer/package.json b/examples/ch11/classical-patterns/observer/package.json new file mode 100644 index 00000000..ec4f9723 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/package.json @@ -0,0 +1,27 @@ +{ + "name": "observer-pattern", + "version": "1.0.0", + "description": "Observer Pattern Example - Stock Price Monitoring", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "start": "tsc && node dist/main.js", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "keywords": [ + "design-patterns", + "observer", + "typescript" + ], + "author": "", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.5.0", + "@types/node": "^20.0.0", + "jest": "^29.5.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.0" + } +} diff --git a/examples/ch11/classical-patterns/observer/src/main.ts b/examples/ch11/classical-patterns/observer/src/main.ts new file mode 100644 index 00000000..1ecc6d1e --- /dev/null +++ b/examples/ch11/classical-patterns/observer/src/main.ts @@ -0,0 +1,94 @@ +/** + * Observer Pattern Demo - Stock Price Monitoring + * + * This demo shows how the Observer pattern enables loose coupling between + * a subject (stock price) and its observers (displays, alerts, loggers). + */ + +import { StockPrice } from './subject'; +import { ChartDisplay, TableDisplay, AlertSystem, Logger } from './observer'; + +function main() { + console.log('=== Observer Pattern Demo: Stock Price Monitoring ===\n'); + + // Create the subject (observable) + const appleStock = new StockPrice('AAPL', 150.00); + + // Create observers + const chart = new ChartDisplay('Apple Chart'); + const table = new TableDisplay('Apple Table'); + const alertHigh = new AlertSystem(180.00, 'High Price Alert'); + const logger = new Logger('Apple Price Logger'); + + console.log('--- Example 1: Attaching Observers ---'); + appleStock.attach(chart); + appleStock.attach(table); + appleStock.attach(alertHigh); + appleStock.attach(logger); + + console.log(`\nTotal observers: ${appleStock.getObserverCount()}`); + + // Example 2: Price changes notify all observers + console.log('\n--- Example 2: Price Changes Notify All Observers ---'); + appleStock.setPrice(155.50); + appleStock.setPrice(162.25); + appleStock.setPrice(158.75); + + // Example 3: Price crosses alert threshold + console.log('\n--- Example 3: Price Crosses Alert Threshold ---'); + appleStock.setPrice(185.00); // Should trigger alert + + // Example 4: Dynamic observer management + console.log('\n--- Example 4: Detaching an Observer ---'); + console.log('Detaching table display...'); + appleStock.detach(table); + + console.log('\nPrice change after detaching table:'); + appleStock.setPrice(190.50); // Table won't be notified + + // Example 5: Reattaching observer + console.log('\n--- Example 5: Reattaching Observer ---'); + console.log('Reattaching table display...'); + appleStock.attach(table); + + console.log('\nPrice change after reattaching table:'); + appleStock.setPrice(192.75); // Table will be notified again + + // Example 6: Adding new observer at runtime + console.log('\n--- Example 6: Adding New Observer at Runtime ---'); + const alertLow = new AlertSystem(160.00, 'Low Price Alert'); + appleStock.attach(alertLow); + + console.log('\nPrice drop below new threshold:'); + appleStock.setPrice(155.00); // Should trigger low price alert + + // Example 7: Multiple stocks, independent observers + console.log('\n--- Example 7: Multiple Stocks with Independent Observers ---'); + const googleStock = new StockPrice('GOOGL', 120.00); + const googleChart = new ChartDisplay('Google Chart'); + const googleLogger = new Logger('Google Price Logger'); + + googleStock.attach(googleChart); + googleStock.attach(googleLogger); + + console.log('\nUpdating Google stock (Apple observers not notified):'); + googleStock.setPrice(125.50); + + console.log('\nUpdating Apple stock (Google observers not notified):'); + appleStock.setPrice(157.25); + + // Summary + console.log('\n' + '='.repeat(60)); + console.log('Demo completed!'); + console.log('='.repeat(60)); + console.log('\nKey Takeaways:'); + console.log('1. Subject (StockPrice) is loosely coupled to observers'); + console.log('2. Observers can be attached/detached dynamically at runtime'); + console.log('3. One change in subject notifies ALL observers automatically'); + console.log('4. Subject depends on Observer interface, not concrete types (DIP)'); + console.log('5. Each observer can react differently to the same notification'); + console.log('6. Multiple subjects can have independent observer lists'); +} + +// Run the demo +main(); diff --git a/examples/ch11/classical-patterns/observer/src/observer.ts b/examples/ch11/classical-patterns/observer/src/observer.ts new file mode 100644 index 00000000..470c02d5 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/src/observer.ts @@ -0,0 +1,155 @@ +/** + * Observer Pattern - Observer Interface and Concrete Observers + * + * The Observer interface defines the contract that all observers must implement. + * Concrete observers implement this interface to receive updates from subjects. + */ + +/** + * Observer interface defines the contract for receiving updates. + * This is a small, focused interface (Interface Segregation Principle). + */ +export interface Observer { + /** + * Called when the subject's state changes. + * @param price - The new stock price + */ + update(price: number): void; + + /** + * Returns the name of this observer for identification. + */ + getName(): string; +} + +/** + * ChartDisplay shows stock price in a visual chart format. + * In a real application, this would render an actual chart. + */ +export class ChartDisplay implements Observer { + private name: string; + private history: number[] = []; + + constructor(name: string = "Chart Display") { + this.name = name; + } + + update(price: number): void { + this.history.push(price); + console.log(`📊 ${this.name}: Price updated to $${price.toFixed(2)}`); + + // Show mini chart with last 5 prices + const recent = this.history.slice(-5); + const chart = recent.map(p => `$${p.toFixed(2)}`).join(" → "); + console.log(` Trend: ${chart}`); + } + + getName(): string { + return this.name; + } + + getHistory(): number[] { + return [...this.history]; + } +} + +/** + * TableDisplay shows stock price in a tabular format. + * This is a different visualization of the same data. + */ +export class TableDisplay implements Observer { + private name: string; + private updateCount: number = 0; + private lastPrice: number = 0; + + constructor(name: string = "Table Display") { + this.name = name; + } + + update(price: number): void { + this.updateCount++; + const change = this.lastPrice > 0 ? price - this.lastPrice : 0; + const changePercent = this.lastPrice > 0 + ? ((change / this.lastPrice) * 100).toFixed(2) + : "0.00"; + + console.log(`📋 ${this.name}: Update #${this.updateCount}`); + console.log(` Price: $${price.toFixed(2)} | Change: $${change.toFixed(2)} (${changePercent}%)`); + + this.lastPrice = price; + } + + getName(): string { + return this.name; + } + + getUpdateCount(): number { + return this.updateCount; + } +} + +/** + * AlertSystem monitors price and sends alerts when thresholds are crossed. + * This demonstrates conditional observer logic. + */ +export class AlertSystem implements Observer { + private name: string; + private threshold: number; + private alerts: string[] = []; + + constructor(threshold: number, name: string = "Alert System") { + this.name = name; + this.threshold = threshold; + } + + update(price: number): void { + if (price > this.threshold) { + const alert = `⚠️ ${this.name}: ALERT! Price $${price.toFixed(2)} exceeded threshold $${this.threshold.toFixed(2)}`; + console.log(alert); + this.alerts.push(alert); + } else { + console.log(`✓ ${this.name}: Price $${price.toFixed(2)} within normal range (threshold: $${this.threshold.toFixed(2)})`); + } + } + + getName(): string { + return this.name; + } + + getAlerts(): string[] { + return [...this.alerts]; + } + + getThreshold(): number { + return this.threshold; + } +} + +/** + * Logger observer records all price changes to a log. + * This demonstrates the flexibility of the Observer pattern - + * we can add logging without modifying the subject. + */ +export class Logger implements Observer { + private name: string; + private logs: string[] = []; + + constructor(name: string = "Price Logger") { + this.name = name; + } + + update(price: number): void { + const timestamp = new Date().toISOString(); + const logEntry = `[${timestamp}] Price: $${price.toFixed(2)}`; + this.logs.push(logEntry); + console.log(`📝 ${this.name}: ${logEntry}`); + } + + getName(): string { + return this.name; + } + + getLogs(): string[] { + return [...this.logs]; + } +} diff --git a/examples/ch11/classical-patterns/observer/src/subject.ts b/examples/ch11/classical-patterns/observer/src/subject.ts new file mode 100644 index 00000000..5ca0177a --- /dev/null +++ b/examples/ch11/classical-patterns/observer/src/subject.ts @@ -0,0 +1,117 @@ +/** + * Observer Pattern - Subject (Observable) + * + * The Subject maintains a list of observers and notifies them of state changes. + * This demonstrates loose coupling: the subject only knows about the Observer + * interface, not concrete observer types. + */ + +import { Observer } from './observer'; + +/** + * StockPrice is the Subject in the Observer pattern. + * It maintains the current price and notifies all observers when it changes. + * + * Key characteristics: + * - Depends on Observer INTERFACE, not concrete types (Dependency Inversion) + * - Can have any number of observers attached/detached at runtime + * - Doesn't know what observers do with the information (loose coupling) + */ +export class StockPrice { + private price: number = 0; + private observers: Observer[] = []; + private symbol: string; + + constructor(symbol: string, initialPrice: number = 0) { + this.symbol = symbol; + this.price = initialPrice; + } + + /** + * Attach an observer to receive updates. + * This can be done at any time, even after the subject has been running. + */ + attach(observer: Observer): void { + if (this.observers.includes(observer)) { + console.log(`Observer ${observer.getName()} is already attached`); + return; + } + + this.observers.push(observer); + console.log(`✓ Attached observer: ${observer.getName()}`); + } + + /** + * Detach an observer to stop receiving updates. + * Demonstrates dynamic observer management. + */ + detach(observer: Observer): void { + const index = this.observers.indexOf(observer); + if (index === -1) { + console.log(`Observer ${observer.getName()} is not attached`); + return; + } + + this.observers.splice(index, 1); + console.log(`✓ Detached observer: ${observer.getName()}`); + } + + /** + * Notify all attached observers of a price change. + * This is the core of the Observer pattern - one change, many notifications. + */ + private notify(): void { + console.log(`\n📢 Notifying ${this.observers.length} observer(s) of price change...`); + for (const observer of this.observers) { + observer.update(this.price); + } + console.log(''); // Blank line for readability + } + + /** + * Set a new price and notify observers. + * This is the method that triggers the observer notifications. + */ + setPrice(newPrice: number): void { + if (newPrice < 0) { + throw new Error('Price cannot be negative'); + } + + const oldPrice = this.price; + this.price = newPrice; + + console.log(`\n${'='.repeat(60)}`); + console.log(`${this.symbol}: Price changed from $${oldPrice.toFixed(2)} to $${newPrice.toFixed(2)}`); + console.log('='.repeat(60)); + + this.notify(); + } + + /** + * Get the current price. + */ + getPrice(): number { + return this.price; + } + + /** + * Get the stock symbol. + */ + getSymbol(): string { + return this.symbol; + } + + /** + * Get the number of attached observers. + */ + getObserverCount(): number { + return this.observers.length; + } + + /** + * Get list of attached observer names (for debugging/testing). + */ + getObserverNames(): string[] { + return this.observers.map(o => o.getName()); + } +} diff --git a/examples/ch11/classical-patterns/observer/tests/observer.test.ts b/examples/ch11/classical-patterns/observer/tests/observer.test.ts new file mode 100644 index 00000000..0fa28686 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/tests/observer.test.ts @@ -0,0 +1,215 @@ +/** + * Unit tests for Observer Pattern implementation + */ + +import { StockPrice } from '../src/subject'; +import { ChartDisplay, TableDisplay, AlertSystem, Logger, Observer } from '../src/observer'; + +describe('Observer Pattern', () => { + describe('StockPrice (Subject)', () => { + it('should create with initial price', () => { + const stock = new StockPrice('AAPL', 150.00); + expect(stock.getSymbol()).toBe('AAPL'); + expect(stock.getPrice()).toBe(150.00); + }); + + it('should attach observers', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer = new ChartDisplay(); + + stock.attach(observer); + expect(stock.getObserverCount()).toBe(1); + }); + + it('should not attach same observer twice', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer = new ChartDisplay(); + + stock.attach(observer); + stock.attach(observer); + expect(stock.getObserverCount()).toBe(1); + }); + + it('should detach observers', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer = new ChartDisplay(); + + stock.attach(observer); + stock.detach(observer); + expect(stock.getObserverCount()).toBe(0); + }); + + it('should update price and notify observers', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer = new ChartDisplay(); + const updateSpy = jest.spyOn(observer, 'update'); + + stock.attach(observer); + stock.setPrice(155.00); + + expect(stock.getPrice()).toBe(155.00); + expect(updateSpy).toHaveBeenCalledWith(155.00); + }); + + it('should throw error for negative price', () => { + const stock = new StockPrice('AAPL', 150.00); + expect(() => stock.setPrice(-10)).toThrow('Price cannot be negative'); + }); + }); + + describe('ChartDisplay (Observer)', () => { + it('should update with new price', () => { + const chart = new ChartDisplay('Test Chart'); + chart.update(150.00); + + const history = chart.getHistory(); + expect(history).toEqual([150.00]); + }); + + it('should maintain price history', () => { + const chart = new ChartDisplay(); + chart.update(150.00); + chart.update(155.00); + chart.update(160.00); + + const history = chart.getHistory(); + expect(history).toEqual([150.00, 155.00, 160.00]); + }); + + it('should return observer name', () => { + const chart = new ChartDisplay('My Chart'); + expect(chart.getName()).toBe('My Chart'); + }); + }); + + describe('TableDisplay (Observer)', () => { + it('should track update count', () => { + const table = new TableDisplay(); + expect(table.getUpdateCount()).toBe(0); + + table.update(150.00); + expect(table.getUpdateCount()).toBe(1); + + table.update(155.00); + expect(table.getUpdateCount()).toBe(2); + }); + }); + + describe('AlertSystem (Observer)', () => { + it('should trigger alert when price exceeds threshold', () => { + const alert = new AlertSystem(180.00); + expect(alert.getAlerts()).toHaveLength(0); + + alert.update(185.00); // Above threshold + expect(alert.getAlerts()).toHaveLength(1); + }); + + it('should not trigger alert when price is below threshold', () => { + const alert = new AlertSystem(180.00); + alert.update(175.00); // Below threshold + + expect(alert.getAlerts()).toHaveLength(0); + }); + + it('should return threshold', () => { + const alert = new AlertSystem(180.00); + expect(alert.getThreshold()).toBe(180.00); + }); + }); + + describe('Logger (Observer)', () => { + it('should log price updates', () => { + const logger = new Logger(); + logger.update(150.00); + + const logs = logger.getLogs(); + expect(logs).toHaveLength(1); + expect(logs[0]).toContain('150.00'); + }); + + it('should maintain log history', () => { + const logger = new Logger(); + logger.update(150.00); + logger.update(155.00); + logger.update(160.00); + + expect(logger.getLogs()).toHaveLength(3); + }); + }); + + describe('Observer Pattern Integration', () => { + it('should notify all observers when price changes', () => { + const stock = new StockPrice('AAPL', 150.00); + const chart = new ChartDisplay(); + const table = new TableDisplay(); + const alert = new AlertSystem(180.00); + + stock.attach(chart); + stock.attach(table); + stock.attach(alert); + + stock.setPrice(155.00); + + expect(chart.getHistory()).toContain(155.00); + expect(table.getUpdateCount()).toBe(1); + }); + + it('should not notify detached observers', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer = new ChartDisplay(); + const updateSpy = jest.spyOn(observer, 'update'); + + stock.attach(observer); + stock.detach(observer); + stock.setPrice(155.00); + + expect(updateSpy).not.toHaveBeenCalled(); + }); + + it('should allow dynamic observer management', () => { + const stock = new StockPrice('AAPL', 150.00); + const observer1 = new ChartDisplay('Chart 1'); + const observer2 = new ChartDisplay('Chart 2'); + + stock.attach(observer1); + expect(stock.getObserverCount()).toBe(1); + + stock.attach(observer2); + expect(stock.getObserverCount()).toBe(2); + + stock.detach(observer1); + expect(stock.getObserverCount()).toBe(1); + expect(stock.getObserverNames()).toEqual(['Chart 2']); + }); + }); + + describe('Dependency Inversion Principle', () => { + it('should accept any observer implementing the interface', () => { + // This test demonstrates DIP: StockPrice depends on Observer interface + class CustomObserver implements Observer { + private updated = false; + + update(price: number): void { + this.updated = true; + } + + getName(): string { + return 'Custom Observer'; + } + + wasUpdated(): boolean { + return this.updated; + } + } + + const stock = new StockPrice('AAPL', 150.00); + const custom = new CustomObserver(); + + stock.attach(custom); + stock.setPrice(155.00); + + expect(custom.wasUpdated()).toBe(true); + // StockPrice works with ANY Observer implementation without modification + }); + }); +}); diff --git a/examples/ch11/classical-patterns/observer/tsconfig.json b/examples/ch11/classical-patterns/observer/tsconfig.json new file mode 100644 index 00000000..97c4e25d --- /dev/null +++ b/examples/ch11/classical-patterns/observer/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node" + }, + "include": ["src/**/*", "tests/**/*"], + "exclude": ["node_modules", "dist"] +} From f77141bf0bd7a59fcfd1ed16d66bbebe95dadb9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:03:02 +0000 Subject: [PATCH 4/5] Complete Classical GoF Patterns: Decorator, Observer READMEs, and Quiz - Implemented Decorator Pattern in Python with coffee shop example - All Decorator tests passing (22 tests) - Created comprehensive READMEs for Observer and Decorator patterns - Created interactive quiz with 11 multi-language questions - All four patterns complete with working examples and tests - Documentation complete with pattern recognition guidance Co-authored-by: jburns24 <19497855+jburns24@users.noreply.github.com> --- .../ch11/classical-patterns/decorator/.keep | 0 .../classical-patterns/decorator/README.md | 251 ++++++++++++++++++ .../decorator/pyproject.toml | 12 + .../decorator/src/decorator.py | 173 ++++++++++++ .../classical-patterns/decorator/src/main.py | 174 ++++++++++++ .../decorator/tests/test_decorator.py | 242 +++++++++++++++++ .../ch11/classical-patterns/decorator/uv.lock | 78 ++++++ .../classical-patterns/observer/README.md | 229 ++++++++++++++++ src/quizzes/chapter-11/11.2.4/.keep | 0 .../11.2.4/classical-patterns-quiz.js | 129 +++++++++ 10 files changed, 1288 insertions(+) create mode 100644 examples/ch11/classical-patterns/decorator/.keep create mode 100644 examples/ch11/classical-patterns/decorator/README.md create mode 100644 examples/ch11/classical-patterns/decorator/pyproject.toml create mode 100644 examples/ch11/classical-patterns/decorator/src/decorator.py create mode 100644 examples/ch11/classical-patterns/decorator/src/main.py create mode 100644 examples/ch11/classical-patterns/decorator/tests/test_decorator.py create mode 100644 examples/ch11/classical-patterns/decorator/uv.lock create mode 100644 examples/ch11/classical-patterns/observer/README.md create mode 100644 src/quizzes/chapter-11/11.2.4/.keep create mode 100644 src/quizzes/chapter-11/11.2.4/classical-patterns-quiz.js diff --git a/examples/ch11/classical-patterns/decorator/.keep b/examples/ch11/classical-patterns/decorator/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/ch11/classical-patterns/decorator/README.md b/examples/ch11/classical-patterns/decorator/README.md new file mode 100644 index 00000000..cff4ab59 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/README.md @@ -0,0 +1,251 @@ +# Decorator Pattern Example: Coffee Shop + +> **Pattern Category**: Structural +> **SOLID Connection**: Open/Closed Principle, Single Responsibility +> **Language**: Python 3.11+ + +## Overview + +This example demonstrates the **Decorator Pattern** through a coffee shop system where customers can customize beverages with various add-ons. The pattern attaches additional responsibilities to objects dynamically, providing a flexible alternative to subclassing for extending functionality. + +## The Decorator Pattern + +The Decorator Pattern allows you to add new behaviors to objects by wrapping them in decorator objects. Decorators provide a more flexible alternative to inheritance for extending functionality. + +### Key Components + +1. **Component Interface** (`Beverage`): Defines the interface for objects that can have responsibilities added +2. **Concrete Components** (`Coffee`, `Espresso`, `Tea`): Base objects to which decorators can be attached +3. **Decorator Base Class** (`BeverageDecorator`): Maintains reference to a component and conforms to component interface +4. **Concrete Decorators** (`MilkDecorator`, `SugarDecorator`, etc.): Add specific responsibilities + +## Connection to Open/Closed Principle + +This implementation perfectly demonstrates the **Open/Closed Principle** from [SOLID Principles](../../../../docs/11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp): + +### Closed for Modification + +The base `Coffee`, `Espresso`, and `Tea` classes are **closed for modification** - you NEVER need to change them when adding new features: + +```python +class Coffee(Beverage): + def cost(self) -> float: + return 2.00 # This never changes + + def description(self) -> str: + return "Coffee" # This never changes +``` + +### Open for Extension + +The system is **open for extension** - you can add unlimited new decorators without touching existing code: + +```python +# Adding a new feature is as simple as creating a new decorator +class HoneyDecorator(BeverageDecorator): + def cost(self) -> float: + return self._beverage.cost() + 0.40 + + def description(self) -> str: + return self._beverage.description() + ", Honey" + +# Use it immediately without modifying Coffee +beverage = HoneyDecorator(Coffee()) # ✓ Extended without modification +``` + +### Before Decorator Pattern (Violates OCP) + +Without the Decorator Pattern, you'd need a class for every combination: + +```python +class Coffee(Beverage): pass +class CoffeeWithMilk(Beverage): pass +class CoffeeWithSugar(Beverage): pass +class CoffeeWithMilkAndSugar(Beverage): pass +class CoffeeWithMilkAndSugarAndWhippedCream(Beverage): pass +# This explodes combinatorially! With 5 add-ons, you'd need 32 classes! +``` + +**Problem**: Adding a new add-on requires creating many new classes. This violates OCP. + +### With Decorator Pattern (Follows OCP) + +```python +# Base classes never change +class Coffee(Beverage): pass + +# Add features by wrapping +coffee = Coffee() +coffee = MilkDecorator(coffee) +coffee = SugarDecorator(coffee) +coffee = WhippedCreamDecorator(coffee) +# Unlimited combinations, zero class modifications! +``` + +**Solution**: Add features by creating decorators that wrap beverages. Zero modifications to base classes. + +## Avoiding Class Explosion + +One of the biggest benefits of Decorator Pattern is avoiding combinatorial explosion: + +- **Without Decorator**: 5 add-ons = 2^5 = 32 classes needed +- **With Decorator**: 5 add-ons = 5 decorator classes + 1 base = 6 classes total + +The Decorator Pattern scales linearly, not exponentially! + +## Setup + +### Prerequisites + +- Python 3.11 or higher +- `uv` for dependency management (install with: `pip install uv`) + +### Installation + +```bash +# Navigate to this directory +cd examples/ch11/classical-patterns/decorator + +# Install dependencies (including dev dependencies for testing) +uv sync --extra dev +``` + +## Running the Demo + +```bash +# Run the demonstration +uv run src/main.py +``` + +Expected output shows: +- Plain beverages +- Single decorators +- Multiple decorators chained together +- Step-by-step building of complex beverages +- How decorators avoid class explosion + +## Running Tests + +```bash +# Run all tests +uv run pytest + +# Run with verbose output +uv run pytest -v + +# Run specific test file +uv run pytest tests/test_decorator.py -v + +# Run with coverage +uv run pytest --cov=src +``` + +All tests should pass, demonstrating: +- Each decorator adds cost and description correctly +- Decorators can be chained in any order +- Same decorator can be applied multiple times +- Base classes are never modified (OCP) + +## Project Structure + +``` +decorator/ +├── src/ +│ ├── decorator.py # Component interface, decorators +│ └── main.py # Executable demo +├── tests/ +│ └── test_decorator.py # Unit tests +├── pyproject.toml # Project configuration and dependencies +└── README.md # This file +``` + +## When to Use Decorator Pattern + +✅ **Use Decorator when**: +- You need to add responsibilities to objects dynamically +- Extension by subclassing is impractical (combinatorial explosion) +- You need to add/remove responsibilities at runtime +- Responsibilities can be combined in various ways + +❌ **Don't use Decorator when**: +- You only have one or two add-ons (simple inheritance is fine) +- Add-ons are always used together (create a single composite class) +- The order of decorators matters semantically (use Chain of Responsibility) + +## Real-World Applications + +The Decorator Pattern is widely used in production code: + +1. **I/O Streams**: Java's `BufferedReader(FileReader)`, Python's file wrappers +2. **HTTP Middleware**: Express.js, Django middleware chains +3. **UI Components**: Adding borders, scrollbars, shadows to components +4. **Caching Layers**: Wrapping service calls with caching +5. **Logging/Monitoring**: Adding logging to existing functions +6. **Compression**: Wrapping streams with compression (GZip, Deflate) +7. **Encryption**: Adding encryption layer to data streams +8. **Authentication**: Wrapping endpoints with auth checks + +## Decorator vs Strategy + +Both patterns involve wrapping, but serve different purposes: + +| Decorator | Strategy | +|-----------|----------| +| Adds behavior/responsibilities | Swaps entire algorithm | +| Can be chained/stacked | Usually just one strategy | +| Changes what object does | Changes how object does it | +| Adds layers around object | Replaces object's core logic | + +Example: +- **Decorator**: Coffee → +Milk → +Sugar (adding layers) +- **Strategy**: Payment → CreditCard OR PayPal (choosing one) + +## Key Takeaways + +1. **OCP in Action**: Decorator Pattern perfectly demonstrates Open/Closed Principle +2. **Runtime Flexibility**: Add/remove features without changing code +3. **Avoids Class Explosion**: Linear growth instead of exponential +4. **Single Responsibility**: Each decorator does one thing +5. **Composition over Inheritance**: Flexible alternative to subclassing + +## Learning Exercise + +Try extending this example: + +1. Add new decorators (Honey, Cinnamon, Almond Milk, Oat Milk) +2. Add size variations (Small, Medium, Large) that multiply cost +3. Implement a "Remove Decorator" operation +4. Add a `getIngredients()` method that lists all ingredients +5. Implement decorator with state (e.g., temperature: hot vs iced) + +Notice how you can do all of this **without modifying the Coffee, Espresso, or Tea classes**. That's the power of the Open/Closed Principle! + +## Common Pitfalls + +1. **Too Many Decorators**: Don't over-abstract. Simple inheritance is fine for simple cases. +2. **Decorator Order**: Order matters for description but shouldn't matter for cost. Be careful with stateful decorators. +3. **Type Identity**: Decorated objects have different types than base objects (use interfaces). +4. **Complexity**: Too many decorators can make code hard to debug. Keep it simple. + +## Pattern Combination + +Decorators work well with other patterns: + +- **Factory**: Use factory to create pre-configured decorator chains +- **Strategy**: Decorators can wrap strategies to add logging/caching +- **Composite**: Decorators and composites both use recursive composition +- **Prototype**: Clone decorated objects to create similar instances + +## Related Patterns + +- **Proxy**: Similar structure but different intent (Proxy controls access, Decorator adds behavior) +- **Composite**: Both use recursive composition, but Composite represents part-whole hierarchies +- **Strategy**: Both involve wrapping, but Strategy swaps algorithms, Decorator adds layers +- **Chain of Responsibility**: Similar chaining, but CoR passes requests along a chain + +## Further Reading + +- [SOLID Principles - Open/Closed Principle](../../../../docs/11-application-development/11.2.1-solid-principles.md#openclosed-principle-ocp) +- [Gang of Four Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) +- [Refactoring Guru - Decorator Pattern](https://refactoring.guru/design-patterns/decorator) +- [Python Decorator Pattern](https://refactoring.guru/design-patterns/decorator/python/example) diff --git a/examples/ch11/classical-patterns/decorator/pyproject.toml b/examples/ch11/classical-patterns/decorator/pyproject.toml new file mode 100644 index 00000000..288f8893 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "decorator-pattern" +version = "0.1.0" +description = "Decorator Pattern Example - Coffee Shop with Dynamic Customizations" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", +] diff --git a/examples/ch11/classical-patterns/decorator/src/decorator.py b/examples/ch11/classical-patterns/decorator/src/decorator.py new file mode 100644 index 00000000..5483a5b8 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/src/decorator.py @@ -0,0 +1,173 @@ +""" +Decorator Pattern Example - Coffee Shop + +Demonstrates the Decorator Pattern by implementing a coffee shop system where +customers can customize their beverages with various add-ons. This pattern embodies +the Open/Closed Principle: the base Beverage is closed for modification (we never +change Coffee or Espresso), but open for extension (we can add unlimited decorators). +""" +from abc import ABC, abstractmethod + + +class Beverage(ABC): + """ + Abstract component defining the interface for beverages. + Both concrete beverages and decorators must implement this interface. + """ + + @abstractmethod + def cost(self) -> float: + """Return the cost of this beverage.""" + pass + + @abstractmethod + def description(self) -> str: + """Return a description of this beverage.""" + pass + + +# ============================================================================ +# Concrete Components (Base Beverages) +# ============================================================================ + +class Coffee(Beverage): + """Basic coffee beverage.""" + + def cost(self) -> float: + return 2.00 + + def description(self) -> str: + return "Coffee" + + +class Espresso(Beverage): + """Espresso beverage.""" + + def cost(self) -> float: + return 2.50 + + def description(self) -> str: + return "Espresso" + + +class Tea(Beverage): + """Tea beverage.""" + + def cost(self) -> float: + return 1.75 + + def description(self) -> str: + return "Tea" + + +# ============================================================================ +# Decorator Base Class +# ============================================================================ + +class BeverageDecorator(Beverage): + """ + Abstract decorator class that wraps a Beverage. + + This class maintains a reference to a Beverage object and delegates + to it. Concrete decorators extend this class to add behavior. + + Key characteristic: A decorator IS-A Beverage and HAS-A Beverage. + """ + + def __init__(self, beverage: Beverage): + self._beverage = beverage + + @abstractmethod + def cost(self) -> float: + """Subclasses must implement to add their cost.""" + pass + + @abstractmethod + def description(self) -> str: + """Subclasses must implement to add their description.""" + pass + + +# ============================================================================ +# Concrete Decorators (Add-ons) +# ============================================================================ + +class MilkDecorator(BeverageDecorator): + """Add milk to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.50 + + def description(self) -> str: + return self._beverage.description() + ", Milk" + + +class SugarDecorator(BeverageDecorator): + """Add sugar to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.30 + + def description(self) -> str: + return self._beverage.description() + ", Sugar" + + +class WhippedCreamDecorator(BeverageDecorator): + """Add whipped cream to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.70 + + def description(self) -> str: + return self._beverage.description() + ", Whipped Cream" + + +class VanillaDecorator(BeverageDecorator): + """Add vanilla syrup to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.60 + + def description(self) -> str: + return self._beverage.description() + ", Vanilla" + + +class CaramelDecorator(BeverageDecorator): + """Add caramel syrup to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.65 + + def description(self) -> str: + return self._beverage.description() + ", Caramel" + + +class ChocolateDecorator(BeverageDecorator): + """Add chocolate to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 0.75 + + def description(self) -> str: + return self._beverage.description() + ", Chocolate" + + +class ExtraShotDecorator(BeverageDecorator): + """Add an extra shot of espresso to a beverage.""" + + def cost(self) -> float: + return self._beverage.cost() + 1.00 + + def description(self) -> str: + return self._beverage.description() + ", Extra Shot" + + +# ============================================================================ +# Helper Functions +# ============================================================================ + +def print_beverage(beverage: Beverage) -> None: + """Print a nicely formatted beverage order.""" + print(f" {beverage.description()}") + print(f" Price: ${beverage.cost():.2f}") + print() diff --git a/examples/ch11/classical-patterns/decorator/src/main.py b/examples/ch11/classical-patterns/decorator/src/main.py new file mode 100644 index 00000000..17343258 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/src/main.py @@ -0,0 +1,174 @@ +""" +Main demo for Decorator Pattern. + +This demo shows how the Decorator Pattern allows adding features to objects +dynamically without modifying their classes (Open/Closed Principle). +""" +from decorator import ( + Coffee, + Espresso, + Tea, + MilkDecorator, + SugarDecorator, + WhippedCreamDecorator, + VanillaDecorator, + CaramelDecorator, + ChocolateDecorator, + ExtraShotDecorator, + print_beverage, +) + + +def main(): + """Demonstrate the Decorator Pattern with coffee shop orders.""" + + print("=== Decorator Pattern Demo: Coffee Shop ===") + print() + + # Example 1: Plain beverages + print("--- Example 1: Plain Beverages ---") + print("Simple orders without decorators:") + print() + + coffee = Coffee() + print_beverage(coffee) + + espresso = Espresso() + print_beverage(espresso) + + tea = Tea() + print_beverage(tea) + + # Example 2: Single decorator + print("--- Example 2: Single Decorator ---") + print("Adding one modification:") + print() + + coffee_with_milk = MilkDecorator(Coffee()) + print_beverage(coffee_with_milk) + + espresso_with_sugar = SugarDecorator(Espresso()) + print_beverage(espresso_with_sugar) + + # Example 3: Multiple decorators (the power of Decorator Pattern!) + print("--- Example 3: Multiple Decorators (Chaining) ---") + print("Building complex beverages by chaining decorators:") + print() + + # Vanilla Latte: Coffee + Milk + Vanilla + vanilla_latte = VanillaDecorator(MilkDecorator(Coffee())) + print_beverage(vanilla_latte) + + # Caramel Macchiato: Espresso + Milk + Caramel + Whipped Cream + caramel_macchiato = WhippedCreamDecorator( + CaramelDecorator( + MilkDecorator( + Espresso() + ) + ) + ) + print_beverage(caramel_macchiato) + + # Mocha: Coffee + Milk + Chocolate + Whipped Cream + mocha = WhippedCreamDecorator( + ChocolateDecorator( + MilkDecorator( + Coffee() + ) + ) + ) + print_beverage(mocha) + + # Example 4: Building beverages step by step + print("--- Example 4: Building Step by Step ---") + print("Demonstrating how decorators wrap each other:") + print() + + print("Step 1: Start with base") + beverage = Coffee() + print(f" {beverage.description()} - ${beverage.cost():.2f}") + + print("Step 2: Add milk") + beverage = MilkDecorator(beverage) + print(f" {beverage.description()} - ${beverage.cost():.2f}") + + print("Step 3: Add sugar") + beverage = SugarDecorator(beverage) + print(f" {beverage.description()} - ${beverage.cost():.2f}") + + print("Step 4: Add whipped cream") + beverage = WhippedCreamDecorator(beverage) + print(f" {beverage.description()} - ${beverage.cost():.2f}") + print() + + # Example 5: Same decorator multiple times + print("--- Example 5: Multiple Instances of Same Decorator ---") + print("You can even apply the same decorator multiple times:") + print() + + extra_strong = ExtraShotDecorator( + ExtraShotDecorator( + Espresso() + ) + ) + print_beverage(extra_strong) + + # Example 6: The Ultimate Coffee + print("--- Example 6: The Ultimate Coffee ---") + print("Creating a complex order with many customizations:") + print() + + ultimate_coffee = WhippedCreamDecorator( + CaramelDecorator( + ChocolateDecorator( + VanillaDecorator( + MilkDecorator( + ExtraShotDecorator( + Coffee() + ) + ) + ) + ) + ) + ) + print_beverage(ultimate_coffee) + + # Example 7: Different combinations, same base + print("--- Example 7: Variations from Same Base ---") + print("Different customers, different preferences:") + print() + + orders = [ + ("Alice's order", ChocolateDecorator(MilkDecorator(Coffee()))), + ("Bob's order", SugarDecorator(Coffee())), + ("Charlie's order", WhippedCreamDecorator(CaramelDecorator(Espresso()))), + ("Diana's order", VanillaDecorator(MilkDecorator(Tea()))), + ] + + for customer, order in orders: + print(f"{customer}:") + print(f" {order.description()}") + print(f" Price: ${order.cost():.2f}") + print() + + # Summary + print("=" * 60) + print("Demo completed!") + print("=" * 60) + print() + print("Key Takeaway:") + print("The base beverage classes (Coffee, Espresso, Tea) were NEVER modified.") + print("We added features by creating decorator classes that wrap beverages.") + print("This is the Open/Closed Principle in action:") + print(" - CLOSED for modification (never change Coffee class)") + print(" - OPEN for extension (add unlimited decorators)") + print() + print("Benefits:") + print(" 1. Add features without modifying existing classes") + print(" 2. Combine features in any order at runtime") + print(" 3. Each decorator has single responsibility") + print(" 4. Avoid combinatorial class explosion") + + +if __name__ == "__main__": + main() diff --git a/examples/ch11/classical-patterns/decorator/tests/test_decorator.py b/examples/ch11/classical-patterns/decorator/tests/test_decorator.py new file mode 100644 index 00000000..821bc210 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/tests/test_decorator.py @@ -0,0 +1,242 @@ +""" +Unit tests for Decorator Pattern implementation. + +These tests verify that decorators can be combined in any order and that +the base beverage classes remain unchanged (Open/Closed Principle). +""" +import sys +from pathlib import Path + +# Add src directory to path +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +import pytest +from decorator import ( + Coffee, + Espresso, + Tea, + MilkDecorator, + SugarDecorator, + WhippedCreamDecorator, + VanillaDecorator, + CaramelDecorator, + ChocolateDecorator, + ExtraShotDecorator, +) + + +class TestBaseBeverages: + """Test the base beverage classes.""" + + def test_coffee_cost(self): + """Test coffee base cost.""" + coffee = Coffee() + assert coffee.cost() == 2.00 + + def test_coffee_description(self): + """Test coffee description.""" + coffee = Coffee() + assert coffee.description() == "Coffee" + + def test_espresso_cost(self): + """Test espresso base cost.""" + espresso = Espresso() + assert espresso.cost() == 2.50 + + def test_espresso_description(self): + """Test espresso description.""" + espresso = Espresso() + assert espresso.description() == "Espresso" + + def test_tea_cost(self): + """Test tea base cost.""" + tea = Tea() + assert tea.cost() == 1.75 + + def test_tea_description(self): + """Test tea description.""" + tea = Tea() + assert tea.description() == "Tea" + + +class TestSingleDecorators: + """Test individual decorators.""" + + def test_milk_decorator(self): + """Test milk decorator adds cost and description.""" + coffee = Coffee() + with_milk = MilkDecorator(coffee) + + assert with_milk.cost() == 2.50 # 2.00 + 0.50 + assert with_milk.description() == "Coffee, Milk" + + def test_sugar_decorator(self): + """Test sugar decorator.""" + coffee = Coffee() + with_sugar = SugarDecorator(coffee) + + assert with_sugar.cost() == 2.30 # 2.00 + 0.30 + assert with_sugar.description() == "Coffee, Sugar" + + def test_whipped_cream_decorator(self): + """Test whipped cream decorator.""" + coffee = Coffee() + with_cream = WhippedCreamDecorator(coffee) + + assert with_cream.cost() == 2.70 # 2.00 + 0.70 + assert with_cream.description() == "Coffee, Whipped Cream" + + def test_vanilla_decorator(self): + """Test vanilla decorator.""" + coffee = Coffee() + with_vanilla = VanillaDecorator(coffee) + + assert with_vanilla.cost() == 2.60 # 2.00 + 0.60 + assert with_vanilla.description() == "Coffee, Vanilla" + + def test_caramel_decorator(self): + """Test caramel decorator.""" + espresso = Espresso() + with_caramel = CaramelDecorator(espresso) + + assert with_caramel.cost() == 3.15 # 2.50 + 0.65 + assert with_caramel.description() == "Espresso, Caramel" + + def test_chocolate_decorator(self): + """Test chocolate decorator.""" + coffee = Coffee() + with_chocolate = ChocolateDecorator(coffee) + + assert with_chocolate.cost() == 2.75 # 2.00 + 0.75 + assert with_chocolate.description() == "Coffee, Chocolate" + + def test_extra_shot_decorator(self): + """Test extra shot decorator.""" + espresso = Espresso() + with_shot = ExtraShotDecorator(espresso) + + assert with_shot.cost() == 3.50 # 2.50 + 1.00 + assert with_shot.description() == "Espresso, Extra Shot" + + +class TestMultipleDecorators: + """Test combining multiple decorators.""" + + def test_two_decorators(self): + """Test chaining two decorators.""" + coffee = Coffee() + with_milk_and_sugar = SugarDecorator(MilkDecorator(coffee)) + + assert with_milk_and_sugar.cost() == 2.80 # 2.00 + 0.50 + 0.30 + assert with_milk_and_sugar.description() == "Coffee, Milk, Sugar" + + def test_three_decorators(self): + """Test chaining three decorators.""" + coffee = Coffee() + latte = WhippedCreamDecorator( + VanillaDecorator( + MilkDecorator(coffee) + ) + ) + + assert latte.cost() == 3.80 # 2.00 + 0.50 + 0.60 + 0.70 + assert latte.description() == "Coffee, Milk, Vanilla, Whipped Cream" + + def test_order_matters_for_description(self): + """Test that decorator order affects description.""" + coffee1 = VanillaDecorator(MilkDecorator(Coffee())) + coffee2 = MilkDecorator(VanillaDecorator(Coffee())) + + # Costs are the same + assert coffee1.cost() == coffee2.cost() + + # But descriptions are different + assert coffee1.description() == "Coffee, Milk, Vanilla" + assert coffee2.description() == "Coffee, Vanilla, Milk" + + def test_same_decorator_multiple_times(self): + """Test applying the same decorator multiple times.""" + espresso = Espresso() + double_shot = ExtraShotDecorator(ExtraShotDecorator(espresso)) + + assert double_shot.cost() == 4.50 # 2.50 + 1.00 + 1.00 + assert double_shot.description() == "Espresso, Extra Shot, Extra Shot" + + +class TestComplexBeverages: + """Test complex beverage combinations.""" + + def test_vanilla_latte(self): + """Test vanilla latte recipe.""" + vanilla_latte = VanillaDecorator(MilkDecorator(Coffee())) + + assert vanilla_latte.cost() == 3.10 # 2.00 + 0.50 + 0.60 + assert vanilla_latte.description() == "Coffee, Milk, Vanilla" + + def test_mocha(self): + """Test mocha recipe.""" + mocha = WhippedCreamDecorator( + ChocolateDecorator( + MilkDecorator(Coffee()) + ) + ) + + assert mocha.cost() == 3.95 # 2.00 + 0.50 + 0.75 + 0.70 + assert mocha.description() == "Coffee, Milk, Chocolate, Whipped Cream" + + def test_caramel_macchiato(self): + """Test caramel macchiato recipe.""" + caramel_macchiato = WhippedCreamDecorator( + CaramelDecorator( + MilkDecorator(Espresso()) + ) + ) + + assert caramel_macchiato.cost() == 4.35 # 2.50 + 0.50 + 0.65 + 0.70 + assert caramel_macchiato.description() == "Espresso, Milk, Caramel, Whipped Cream" + + +class TestOpenClosedPrinciple: + """Tests demonstrating the Open/Closed Principle.""" + + def test_adding_new_decorator_does_not_modify_base(self): + """ + Verify that adding new decorators does not require modifying + base beverage classes (Open/Closed Principle). + """ + # Base coffee works + coffee = Coffee() + assert coffee.cost() == 2.00 + + # Adding decorators doesn't change the base class + decorated = MilkDecorator(coffee) + assert coffee.cost() == 2.00 # Base unchanged + assert decorated.cost() == 2.50 # Decorator adds cost + + # We can add more decorators without touching Coffee class + more_decorated = SugarDecorator(decorated) + assert coffee.cost() == 2.00 # Base still unchanged + assert more_decorated.cost() == 2.80 + + # Coffee class code remains unchanged - it's closed for modification + # but we extended functionality by adding decorators - open for extension + + def test_unlimited_combinations_without_modifying_classes(self): + """Test that we can create unlimited combinations without new classes.""" + # All these combinations work without creating new beverage classes + combinations = [ + MilkDecorator(Coffee()), + SugarDecorator(MilkDecorator(Coffee())), + WhippedCreamDecorator(SugarDecorator(MilkDecorator(Coffee()))), + VanillaDecorator(Coffee()), + ChocolateDecorator(MilkDecorator(Coffee())), + CaramelDecorator(WhippedCreamDecorator(Coffee())), + ] + + # All work without modifying base classes + for combo in combinations: + assert combo.cost() > 0 + assert len(combo.description()) > 0 + + # We achieved this without creating CoffeeWithMilk, CoffeeWithSugar, etc. + # This avoids the combinatorial explosion of subclasses! diff --git a/examples/ch11/classical-patterns/decorator/uv.lock b/examples/ch11/classical-patterns/decorator/uv.lock new file mode 100644 index 00000000..50b9f4a8 --- /dev/null +++ b/examples/ch11/classical-patterns/decorator/uv.lock @@ -0,0 +1,78 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "decorator-pattern" +version = "0.1.0" +source = { virtual = "." } + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }] +provides-extras = ["dev"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] diff --git a/examples/ch11/classical-patterns/observer/README.md b/examples/ch11/classical-patterns/observer/README.md new file mode 100644 index 00000000..82485e32 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/README.md @@ -0,0 +1,229 @@ +# Observer Pattern Example: Stock Price Monitoring + +> **Pattern Category**: Behavioral +> **SOLID Connection**: Dependency Inversion Principle, Interface Segregation +> **Language**: TypeScript / Node.js 20+ + +## Overview + +This example demonstrates the **Observer Pattern** through a stock price monitoring system. The pattern establishes a one-to-many dependency between objects so that when one object (subject) changes state, all its dependents (observers) are notified and updated automatically. + +## The Observer Pattern + +The Observer Pattern defines a subscription mechanism where multiple observers can listen to events from a subject. When the subject's state changes, it automatically notifies all registered observers. + +### Key Components + +1. **Observer Interface** (`Observer`): Defines the contract for receiving updates +2. **Concrete Observers** (`ChartDisplay`, `TableDisplay`, `AlertSystem`, `Logger`): Different visualizations/actions for the same data +3. **Subject** (`StockPrice`): Maintains state and notifies observers of changes + +## Connection to SOLID Principles + +This implementation demonstrates multiple SOLID principles: + +### Dependency Inversion Principle + +The `StockPrice` subject depends on the `Observer` interface (abstraction), not on concrete observer implementations: + +```typescript +class StockPrice { + private observers: Observer[] = []; // ✓ Depends on interface + + attach(observer: Observer): void { + this.observers.push(observer); // Accepts any Observer implementation + } +} +``` + +### Interface Segregation Principle + +The `Observer` interface is small and focused - just one method: + +```typescript +interface Observer { + update(price: number): void; // ✓ Small, focused interface + getName(): string; +} +``` + +Observers only need to implement what they use. There's no "fat interface" forcing unnecessary methods. + +### Open/Closed Principle + +You can add new observers without modifying the `StockPrice` class: + +```typescript +// Add a new observer type without touching StockPrice +class EmailNotifier implements Observer { + update(price: number): void { + // Send email notification + } + getName(): string { return "Email Notifier"; } +} + +// Use it immediately +stock.attach(new EmailNotifier()); // ✓ No modification to StockPrice +``` + +## Setup + +### Prerequisites + +- Node.js 20 or higher +- npm (comes with Node.js) + +### Installation + +```bash +# Navigate to this directory +cd examples/ch11/classical-patterns/observer + +# Install dependencies +npm install +``` + +## Running the Demo + +```bash +# Build and run +npm start + +# Or build and run separately +npm run build +node dist/main.js +``` + +Expected output shows: +- Attaching/detaching observers dynamically +- All observers being notified when price changes +- Different observers reacting differently to the same event +- Multiple independent subjects with separate observer lists + +## Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Run with coverage +npm run test:coverage +``` + +All tests should pass, demonstrating: +- Observers receive notifications +- Dynamic attach/detach works +- Subject doesn't depend on concrete observer types (DIP) +- Multiple observers react independently + +## Project Structure + +``` +observer/ +├── src/ +│ ├── observer.ts # Observer interface and concrete observers +│ ├── subject.ts # StockPrice subject implementation +│ └── main.ts # Executable demo +├── tests/ +│ └── observer.test.ts # Unit tests +├── package.json # Dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── jest.config.js # Jest testing configuration +└── README.md # This file +``` + +## When to Use Observer Pattern + +✅ **Use Observer when**: +- Multiple objects need to react to state changes +- You want loose coupling between subject and observers +- The set of observers can change at runtime +- You need to broadcast changes to unknown number of objects + +❌ **Don't use Observer when**: +- You have one observer that never changes (just call it directly) +- Observers need to know about each other (use Mediator instead) +- The notification order matters critically (Observer doesn't guarantee order) + +## Real-World Applications + +The Observer Pattern is everywhere in modern software: + +1. **UI Frameworks**: React, Vue, Angular (component updates when state changes) +2. **Event Systems**: DOM events, Node.js EventEmitter +3. **Pub/Sub Messaging**: RabbitMQ, Kafka, Redis pub/sub +4. **Model-View Updates**: MVC/MVVM architectures +5. **Stock Tickers**: As demonstrated (real-time price updates) +6. **Monitoring Systems**: Metrics publishing to multiple dashboards +7. **Chat Applications**: Message broadcasting to multiple clients +8. **WebSockets**: Server pushing updates to multiple connected clients + +## Key Takeaways + +1. **Loose Coupling**: Subject doesn't know concrete observer types (DIP) +2. **Dynamic Relationships**: Attach/detach observers at runtime +3. **Broadcast Communication**: One change notifies many observers +4. **Different Reactions**: Each observer can respond differently to the same event +5. **ISP Compliance**: Observer interface is small and focused + +## Push vs Pull + +This implementation uses **push** model - the subject pushes data to observers: + +```typescript +// Push model (our implementation) +observer.update(price); // Subject pushes price to observer +``` + +Alternative **pull** model where observers pull data from subject: + +```typescript +// Pull model (alternative) +interface Observer { + update(subject: StockPrice): void; // Observer pulls data from subject +} + +class ChartDisplay implements Observer { + update(subject: StockPrice): void { + const price = subject.getPrice(); // Observer pulls what it needs + } +} +``` + +Push is simpler but less flexible. Pull gives observers control over what data they retrieve. + +## Learning Exercise + +Try extending this example: + +1. Add a new observer type (e.g., `EmailNotifier`, `SMSNotifier`, `DatabaseLogger`) +2. Implement the pull model instead of push +3. Add priority levels to observers (high-priority observers notified first) +4. Implement unsubscribe-on-condition (observer auto-detaches after certain events) +5. Add error handling (what if an observer's update() throws an error?) + +Notice how you can do all of this **without modifying the core StockPrice class**. That's loose coupling in action! + +## Common Pitfalls + +1. **Memory Leaks**: Forgetting to detach observers can cause memory leaks. Always detach when done. +2. **Notification Storms**: If observers trigger subject changes, you can get infinite loops. Be careful! +3. **Update Order**: Don't rely on notification order - Observer Pattern doesn't guarantee it. +4. **Synchronous vs Asynchronous**: Our implementation is synchronous. For async, consider promises or event queues. + +## Related Patterns + +- **Mediator**: When observers need to communicate with each other +- **Pub/Sub**: More decoupled version where publishers and subscribers don't know each other +- **Chain of Responsibility**: When observers need to process events in sequence +- **Command**: Can encapsulate state change notifications as command objects + +## Further Reading + +- [SOLID Principles - Dependency Inversion Principle](../../../../docs/11-application-development/11.2.1-solid-principles.md#dependency-inversion-principle-dip) +- [Gang of Four Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) +- [Refactoring Guru - Observer Pattern](https://refactoring.guru/design-patterns/observer) +- [Observer Pattern in JavaScript/TypeScript](https://www.patterns.dev/posts/observer-pattern/) diff --git a/src/quizzes/chapter-11/11.2.4/.keep b/src/quizzes/chapter-11/11.2.4/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/quizzes/chapter-11/11.2.4/classical-patterns-quiz.js b/src/quizzes/chapter-11/11.2.4/classical-patterns-quiz.js new file mode 100644 index 00000000..0d077c52 --- /dev/null +++ b/src/quizzes/chapter-11/11.2.4/classical-patterns-quiz.js @@ -0,0 +1,129 @@ +const rawQuizdown = ` +--- +shuffleQuestions: true +shuffleAnswers: true +--- + +# Which design pattern is demonstrated by this Python code: \`class OrderProcessor:\n def __init__(self, payment_strategy: PaymentStrategy):\n self.strategy = payment_strategy\`? + +1. [x] Strategy Pattern + > Correct! The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Here, PaymentStrategy is swapped via dependency injection. +1. [ ] Factory Pattern + > Not quite. Factory Pattern deals with object creation, not swappable algorithms. +1. [ ] Observer Pattern + > Not quite. Observer Pattern deals with event notification, not algorithm selection. +1. [ ] Decorator Pattern + > Not quite. Decorator Pattern adds behavior by wrapping, not by swapping strategies. + +# This Go code demonstrates which SOLID principle? \`func NewUserService(db Database) *UserService {\n return &UserService{db: db}\n}\` + +1. [x] Dependency Inversion Principle + > Correct! UserService depends on the Database interface (abstraction), not a concrete implementation like MySQL. Dependencies are injected, following DIP. +1. [ ] Single Responsibility Principle + > Not quite. While good design, the key here is depending on abstractions (DIP), not single responsibility. +1. [ ] Open/Closed Principle + > Not quite. OCP deals with extension without modification. This code demonstrates DIP through dependency injection. +1. [ ] Interface Segregation Principle + > Not quite. ISP deals with interface design. This code demonstrates DIP by depending on abstractions. + +# In the Observer Pattern, when \`stock.setPrice(150)\` is called, what happens? + +1. [x] All attached observers are automatically notified via their update() method + > Correct! The Observer Pattern implements one-to-many dependencies. When the subject's state changes, all registered observers are notified automatically. +1. [ ] Only the most recently attached observer is notified + > Not quite. Observer Pattern notifies ALL attached observers, not just the most recent one. +1. [ ] Observers must manually check for price changes + > Not quite. Observer Pattern is push-based - the subject pushes notifications to observers. +1. [ ] The price is stored but no notifications occur + > Not quite. The core purpose of Observer Pattern is automatic notification of state changes. + +# This TypeScript code shows which pattern? \`class WhippedCreamDecorator extends BeverageDecorator {\n cost() { return this._beverage.cost() + 0.70; }\n}\` + +1. [x] Decorator Pattern + > Correct! The Decorator Pattern wraps an object to add behavior. WhippedCreamDecorator wraps a beverage and adds cost/functionality without modifying the base class. +1. [ ] Strategy Pattern + > Not quite. Strategy swaps entire algorithms. Decorator adds layers of behavior while maintaining the same interface. +1. [ ] Observer Pattern + > Not quite. Observer handles event notification. This code wraps an object to add behavior. +1. [ ] Factory Pattern + > Not quite. Factory handles object creation. This code wraps existing objects to extend behavior. + +# Identify the pattern: \`factory := GetDatabaseFactory("mysql")\ndb := factory.CreateDatabase(config)\` + +1. [x] Factory Pattern + > Correct! The Factory Pattern abstracts object creation. GetDatabaseFactory returns a factory that creates database instances, decoupling creation from usage. +1. [ ] Builder Pattern + > Not quite. While Builder also creates objects, Factory Pattern is specifically for creating objects through a common interface based on type. +1. [ ] Strategy Pattern + > Not quite. Strategy swaps algorithms. This code is about creating objects, not swapping behavior. +1. [ ] Singleton Pattern + > Not quite. Singleton ensures one instance. This code creates database instances through a factory. + +# Which statement about the Strategy Pattern and Open/Closed Principle is TRUE? + +1. [x] Adding a new payment strategy requires creating a new class, not modifying existing code + > Correct! Strategy Pattern embodies OCP: closed for modification (OrderProcessor never changes), open for extension (add new strategies by creating new classes). +1. [ ] Adding a new payment strategy requires modifying the OrderProcessor class + > Not quite. This would violate OCP. With Strategy Pattern, you create new strategy classes without modifying existing code. +1. [ ] Strategy Pattern violates Open/Closed Principle + > Not quite. Strategy Pattern is a textbook implementation of OCP - it allows extension without modification. +1. [ ] You must modify the PaymentStrategy interface to add new strategies + > Not quite. The interface remains unchanged. New strategies implement the existing interface. + +# In Python's Decorator Pattern, what does \`MilkDecorator(SugarDecorator(Coffee()))\` create? + +1. [x] Coffee with sugar, then milk added (decorators applied inside-out) + > Correct! Decorators wrap from inside out. Coffee is wrapped by SugarDecorator, then that result is wrapped by MilkDecorator. Description would be "Coffee, Sugar, Milk". +1. [ ] Coffee with milk, then sugar added + > Not quite. Decorators apply inside-out: Coffee → Sugar → Milk. The innermost is applied first. +1. [ ] A compile-time error because decorators must be applied in alphabetical order + > Not quite. Decorators can be combined in any order at runtime - that's their power! +1. [ ] Only the milk decorator because it's outermost + > Not quite. All decorators contribute. Each wraps the previous one, adding its own behavior. + +# Why is the Observer Pattern said to demonstrate loose coupling? + +1. [x] The subject only knows about the Observer interface, not concrete observer types + > Correct! The subject depends on the Observer interface (Dependency Inversion), not concrete implementations. It doesn't know or care what observers do with notifications. +1. [ ] Observers can only be attached once + > Not quite. Observers can be attached/detached dynamically. Loose coupling means minimal dependencies between subject and observers. +1. [ ] The subject creates the observer objects + > Not quite. Observers are typically created externally and attached. Loose coupling means the subject doesn't create or manage observer lifecycles. +1. [ ] Observers share a single update method + > Not quite. While they implement the same interface, loose coupling refers to the subject's independence from concrete observer types. + +# Which code smell suggests you should use the Factory Pattern? + +1. [x] Constructor calls like \`new MySQLDatabase()\` scattered throughout high-level code + > Correct! When high-level code creates concrete instances directly, it violates DIP. Factory Pattern centralizes creation and allows high-level code to depend on abstractions. +1. [ ] A class with more than 5 methods + > Not quite. This might suggest SRP violation, not a need for Factory Pattern. Factory addresses object creation, not class size. +1. [ ] Multiple if/else statements checking object types + > Not quite. This suggests Strategy Pattern (use polymorphism instead of type checks), not Factory Pattern. +1. [ ] Methods that return void + > Not quite. Return types don't indicate Factory Pattern. Look for tight coupling to concrete classes during object creation. + +# What makes Decorator Pattern different from simple inheritance? + +1. [x] Decorators allow adding behavior at runtime; inheritance is fixed at compile time + > Correct! Decorator Pattern provides runtime flexibility to compose behaviors. Inheritance creates a fixed hierarchy at compile time and can lead to class explosion. +1. [ ] Decorators can only be used in Python + > Not quite. Decorator Pattern is language-agnostic and works in any OO language (Python, Java, Go, TypeScript, etc.). +1. [ ] Inheritance is always better than decorators + > Not quite. Decorators solve the problem of combinatorial class explosion. With inheritance, you'd need a class for every combination of features. +1. [ ] Decorators modify the base class code + > Not quite. Decorators NEVER modify the base class - that's the point! They wrap objects to add behavior (OCP). + +# A system where \`ChartDisplay\`, \`TableDisplay\`, and \`AlertSystem\` all update when stock price changes uses which pattern? + +1. [x] Observer Pattern + > Correct! Observer Pattern implements one-to-many dependencies. Multiple observers (displays, alerts) react to changes in a subject (stock price) automatically. +1. [ ] Strategy Pattern + > Not quite. Strategy swaps algorithms. This scenario describes multiple objects reacting to state changes (Observer). +1. [ ] Factory Pattern + > Not quite. Factory handles creation. This scenario describes notification of state changes (Observer). +1. [ ] Decorator Pattern + > Not quite. Decorator wraps objects to add behavior. This scenario describes event notification (Observer). +`; + +export { rawQuizdown } From 3f1052e050f81dcc43bfa43cdee10ad43fc27bf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:04:57 +0000 Subject: [PATCH 5/5] Fix Observer TypeScript build configuration - Updated tsconfig.json to properly compile src directory - Observer demo now runs successfully - All Observer tests passing (19 tests) - All four patterns verified working with passing tests Co-authored-by: jburns24 <19497855+jburns24@users.noreply.github.com> --- .../observer/package-lock.json | 3844 +++++++++++++++++ .../classical-patterns/observer/tsconfig.json | 6 +- 2 files changed, 3847 insertions(+), 3 deletions(-) create mode 100644 examples/ch11/classical-patterns/observer/package-lock.json diff --git a/examples/ch11/classical-patterns/observer/package-lock.json b/examples/ch11/classical-patterns/observer/package-lock.json new file mode 100644 index 00000000..e12c0862 --- /dev/null +++ b/examples/ch11/classical-patterns/observer/package-lock.json @@ -0,0 +1,3844 @@ +{ + "name": "observer-pattern", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "observer-pattern", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.5.0", + "@types/node": "^20.0.0", + "jest": "^29.5.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/ch11/classical-patterns/observer/tsconfig.json b/examples/ch11/classical-patterns/observer/tsconfig.json index 97c4e25d..f4b80bd5 100644 --- a/examples/ch11/classical-patterns/observer/tsconfig.json +++ b/examples/ch11/classical-patterns/observer/tsconfig.json @@ -4,7 +4,7 @@ "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", - "rootDir": "./", + "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -15,6 +15,6 @@ "sourceMap": true, "moduleResolution": "node" }, - "include": ["src/**/*", "tests/**/*"], - "exclude": ["node_modules", "dist"] + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] }