@miller-tech/uap 2.36.1 → 2.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/model-profiles/qwen38.json +3 -3
- package/config/model-registry.json +96 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +46 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +155 -36
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/models.d.ts +11 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +164 -3
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/workflow.d.ts +36 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +246 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/delivery/run-state.d.ts +1 -1
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +1 -1
- package/dist/delivery/run-state.js.map +1 -1
- package/dist/delivery/team-solve-deliver.d.ts +83 -0
- package/dist/delivery/team-solve-deliver.d.ts.map +1 -0
- package/dist/delivery/team-solve-deliver.js +191 -0
- package/dist/delivery/team-solve-deliver.js.map +1 -0
- package/dist/inference/model-files.d.ts +111 -0
- package/dist/inference/model-files.d.ts.map +1 -0
- package/dist/inference/model-files.js +172 -0
- package/dist/inference/model-files.js.map +1 -0
- package/dist/placement/registry.d.ts +650 -0
- package/dist/placement/registry.d.ts.map +1 -1
- package/dist/placement/registry.js +21 -0
- package/dist/placement/registry.js.map +1 -1
- package/dist/workflow/loader.d.ts +34 -0
- package/dist/workflow/loader.d.ts.map +1 -0
- package/dist/workflow/loader.js +232 -0
- package/dist/workflow/loader.js.map +1 -0
- package/dist/workflow/runner.d.ts +90 -0
- package/dist/workflow/runner.d.ts.map +1 -0
- package/dist/workflow/runner.js +416 -0
- package/dist/workflow/runner.js.map +1 -0
- package/dist/workflow/types.d.ts +400 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/dist/workflow/types.js +147 -0
- package/dist/workflow/types.js.map +1 -0
- package/docs/INDEX.md +1 -0
- package/docs/architecture/adr/0013-declarative-workflow-layer.md +99 -0
- package/docs/architecture/adr/0014-loop-steps-and-team-solve-deliver.md +142 -0
- package/docs/benchmarks/LCB_DROID_SMOKE.md +111 -0
- package/docs/guides/DELIVER.md +3 -1
- package/docs/guides/WORKFLOWS.md +179 -0
- package/docs/plans/model-performance-proof-options.md +39 -0
- package/docs/reference/CLI.md +33 -0
- package/docs/reference/CONFIGURATION.md +1 -0
- package/docs/reference/FEATURES.md +18 -0
- package/examples/auto-coordinator-example.js +127 -0
- package/examples/demo-order-solid.ts +19 -0
- package/examples/order-processing-strategy-pattern.ts +186 -0
- package/examples/order-processor-refactored.ts +473 -0
- package/examples/order-solid-refactor.ts +830 -0
- package/examples/refactored-order-processing.ts +106 -0
- package/examples/run-order-processing.ts +48 -0
- package/examples/solid-order-processing.ts +324 -0
- package/examples/strategy-pattern-example.ts +100 -0
- package/examples/workflows/team-solve/README.md +85 -0
- package/examples/workflows/team-solve/check-samples.sh +50 -0
- package/examples/workflows/team-solve/sample/task.md +22 -0
- package/examples/workflows/team-solve/sample/tests/1.in +2 -0
- package/examples/workflows/team-solve/sample/tests/1.out +1 -0
- package/examples/workflows/team-solve/sample/tests/2.in +2 -0
- package/examples/workflows/team-solve/sample/tests/2.out +1 -0
- package/examples/workflows/team-solve/sample/tests/3.in +2 -0
- package/examples/workflows/team-solve/sample/tests/3.out +1 -0
- package/examples/workflows/team-solve/sample/tests/4.in +2 -0
- package/examples/workflows/team-solve/sample/tests/4.out +1 -0
- package/examples/workflows/team-solve/sample/tests/5.in +1 -0
- package/examples/workflows/team-solve/sample/tests/5.out +1 -0
- package/examples/workflows/team-solve/steps/ideate-a.md +26 -0
- package/examples/workflows/team-solve/steps/ideate-b.md +28 -0
- package/examples/workflows/team-solve/steps/plan.md +20 -0
- package/examples/workflows/team-solve/steps/repair.md +25 -0
- package/examples/workflows/team-solve/steps/review.md +29 -0
- package/examples/workflows/team-solve/steps/solve.md +26 -0
- package/examples/workflows/team-solve/workflow.yaml +78 -0
- package/examples/workflows/team-solve-deliver/README.md +84 -0
- package/examples/workflows/team-solve-deliver/steps/ideate-a.md +25 -0
- package/examples/workflows/team-solve-deliver/steps/ideate-b.md +28 -0
- package/examples/workflows/team-solve-deliver/steps/plan.md +25 -0
- package/examples/workflows/team-solve-deliver/steps/repair.md +20 -0
- package/examples/workflows/team-solve-deliver/steps/review.md +31 -0
- package/examples/workflows/team-solve-deliver/steps/solve.md +28 -0
- package/examples/workflows/team-solve-deliver/steps/verify.md +9 -0
- package/examples/workflows/team-solve-deliver/workflow.yaml +74 -0
- package/package.json +2 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +181 -15
- package/tools/agents/tests/test_signature_quality.py +51 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOLID Principles-Compliant Order Processing System
|
|
3
|
+
*
|
|
4
|
+
* This module demonstrates a refactored order processing system that strictly
|
|
5
|
+
* follows SOLID principles and the project's coding standards.
|
|
6
|
+
*
|
|
7
|
+
* ASSUMPTIONS:
|
|
8
|
+
* - Orders contain a valid 'type' property (string)
|
|
9
|
+
* - Order types are known at runtime or have fallback behavior
|
|
10
|
+
* - Processing is synchronous (no async operations required)
|
|
11
|
+
*
|
|
12
|
+
* EDGE CASES HANDLED:
|
|
13
|
+
* - Invalid/missing order data (throws AppError)
|
|
14
|
+
* - Unknown order types (uses DefaultOrderStrategy)
|
|
15
|
+
* - Null/undefined input (throws AppError)
|
|
16
|
+
* - Invalid type values (falls back to default strategy)
|
|
17
|
+
*
|
|
18
|
+
* EDGE CASES NOT HANDLED:
|
|
19
|
+
* - Concurrent order processing (not thread-safe by default)
|
|
20
|
+
* - Distributed processing across multiple services
|
|
21
|
+
* - Complex validation beyond type checking
|
|
22
|
+
* - Order persistence or database operations
|
|
23
|
+
*
|
|
24
|
+
* @module order-solid-refactor
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Error Types
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Custom error class for order processing failures.
|
|
35
|
+
* Extends the built-in Error class with additional context.
|
|
36
|
+
*/
|
|
37
|
+
export class OrderProcessingError extends Error {
|
|
38
|
+
/** Error code for programmatic handling */
|
|
39
|
+
readonly code: string;
|
|
40
|
+
/** Additional context about the error */
|
|
41
|
+
readonly context?: Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new OrderProcessingError instance.
|
|
45
|
+
*
|
|
46
|
+
* @param message - Human-readable error message
|
|
47
|
+
* @param code - Error code for programmatic handling
|
|
48
|
+
* @param context - Optional additional context
|
|
49
|
+
*/
|
|
50
|
+
constructor(
|
|
51
|
+
message: string,
|
|
52
|
+
code: string,
|
|
53
|
+
context?: Record<string, unknown>
|
|
54
|
+
) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = 'OrderProcessingError';
|
|
57
|
+
this.code = code;
|
|
58
|
+
this.context = context;
|
|
59
|
+
|
|
60
|
+
// Maintains proper stack trace (V8 engines)
|
|
61
|
+
if (Error.captureStackTrace) {
|
|
62
|
+
Error.captureStackTrace(this, OrderProcessingError);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================
|
|
68
|
+
// Domain Models & Validation
|
|
69
|
+
// ============================================
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Order status enumeration for type safety.
|
|
73
|
+
*/
|
|
74
|
+
export enum OrderStatus {
|
|
75
|
+
DELIVERED = 'delivered',
|
|
76
|
+
SHIPPED = 'shipped',
|
|
77
|
+
ACTIVE = 'active',
|
|
78
|
+
SCHEDULED = 'scheduled',
|
|
79
|
+
PENDING = 'pending',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Minimum valid order interface.
|
|
84
|
+
* All order types must have at least these properties.
|
|
85
|
+
*/
|
|
86
|
+
export interface BaseOrder {
|
|
87
|
+
type: string;
|
|
88
|
+
id: string;
|
|
89
|
+
[key: string]: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Processed order with computed status.
|
|
94
|
+
* @template T - Additional order properties
|
|
95
|
+
*/
|
|
96
|
+
export interface ProcessedOrder extends BaseOrder {
|
|
97
|
+
status: OrderStatus;
|
|
98
|
+
[key: string]: unknown;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Zod schema for validating order objects.
|
|
103
|
+
* Ensures order has required 'type' and 'id' properties.
|
|
104
|
+
*/
|
|
105
|
+
const orderSchema = z.object({
|
|
106
|
+
type: z.string().min(1, 'Order type must be a non-empty string'),
|
|
107
|
+
id: z.string().min(1, 'Order ID must be a non-empty string'),
|
|
108
|
+
}).passthrough(); // Allow additional properties
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Validates and normalizes an order object.
|
|
112
|
+
*
|
|
113
|
+
* @param order - The order to validate
|
|
114
|
+
* @returns The validated order object
|
|
115
|
+
* @throws OrderProcessingError if validation fails
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* validateOrder({ type: 'digital', id: '123' }); // Success
|
|
120
|
+
* validateOrder({ type: '', id: '123' }); // Throws OrderProcessingError
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function validateOrder(order: unknown): BaseOrder {
|
|
124
|
+
const result = orderSchema.safeParse(order);
|
|
125
|
+
|
|
126
|
+
if (!result.success) {
|
|
127
|
+
throw new OrderProcessingError(
|
|
128
|
+
'Invalid order data',
|
|
129
|
+
'INVALID_ORDER',
|
|
130
|
+
{ errors: result.error.errors, input: order }
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result.data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================
|
|
138
|
+
// Strategy Pattern (Single Responsibility, Open/Closed)
|
|
139
|
+
// ============================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Strategy interface for processing different order types.
|
|
143
|
+
* Each strategy handles one specific order type (Single Responsibility Principle).
|
|
144
|
+
*
|
|
145
|
+
* Implements Strategy Pattern to satisfy:
|
|
146
|
+
* - Open/Closed Principle: New strategies can be added without modifying existing code
|
|
147
|
+
* - Single Responsibility Principle: Each class has one reason to change
|
|
148
|
+
*/
|
|
149
|
+
export interface OrderProcessingStrategy {
|
|
150
|
+
/**
|
|
151
|
+
* Processes an order and returns the result with updated status.
|
|
152
|
+
*
|
|
153
|
+
* @param order - The order to process (not mutated)
|
|
154
|
+
* @returns Processed order with status
|
|
155
|
+
* @throws OrderProcessingError if processing fails
|
|
156
|
+
*/
|
|
157
|
+
process(order: BaseOrder): ProcessedOrder;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Processes digital orders (e.g., software licenses, downloads).
|
|
162
|
+
*
|
|
163
|
+
* ASSUMPTIONS:
|
|
164
|
+
* - Order type is 'digital'
|
|
165
|
+
* - Order has valid delivery email or download method
|
|
166
|
+
*/
|
|
167
|
+
export class DigitalOrderStrategy implements OrderProcessingStrategy {
|
|
168
|
+
/**
|
|
169
|
+
* Processes a digital order by setting status to delivered.
|
|
170
|
+
*
|
|
171
|
+
* @param order - The digital order to process
|
|
172
|
+
* @returns Order with status set to 'delivered'
|
|
173
|
+
*/
|
|
174
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
175
|
+
// Assume: order.type === 'digital' (enforced by factory)
|
|
176
|
+
// Condition: Digital orders are immediately delivered
|
|
177
|
+
// Edge case: Invalid order data already validated before reaching here
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
...order,
|
|
181
|
+
status: OrderStatus.DELIVERED,
|
|
182
|
+
processedAt: new Date().toISOString(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Processes physical orders (e.g., products requiring shipping).
|
|
189
|
+
*
|
|
190
|
+
* ASSUMPTIONS:
|
|
191
|
+
* - Order type is 'physical'
|
|
192
|
+
* - Order contains shipping information
|
|
193
|
+
*/
|
|
194
|
+
export class PhysicalOrderStrategy implements OrderProcessingStrategy {
|
|
195
|
+
/**
|
|
196
|
+
* Processes a physical order by setting status to shipped.
|
|
197
|
+
*
|
|
198
|
+
* @param order - The physical order to process
|
|
199
|
+
* @returns Order with status set to 'shipped'
|
|
200
|
+
*/
|
|
201
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
202
|
+
// Assume: order.type === 'physical' (enforced by factory)
|
|
203
|
+
// Condition: Physical orders require shipping label generation
|
|
204
|
+
// Edge case: Missing shipping address would need additional validation
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
...order,
|
|
208
|
+
status: OrderStatus.SHIPPED,
|
|
209
|
+
processedAt: new Date().toISOString(),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Processes subscription orders (e.g., recurring services).
|
|
216
|
+
*
|
|
217
|
+
* ASSUMPTIONS:
|
|
218
|
+
* - Order type is 'subscription'
|
|
219
|
+
* - Order contains billing information
|
|
220
|
+
*/
|
|
221
|
+
export class SubscriptionOrderStrategy implements OrderProcessingStrategy {
|
|
222
|
+
/**
|
|
223
|
+
* Processes a subscription order by setting status to active.
|
|
224
|
+
*
|
|
225
|
+
* @param order - The subscription order to process
|
|
226
|
+
* @returns Order with status set to 'active'
|
|
227
|
+
*/
|
|
228
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
229
|
+
// Assume: order.type === 'subscription' (enforced by factory)
|
|
230
|
+
// Condition: Subscriptions activate immediately
|
|
231
|
+
// Edge case: Payment processing would occur before activation
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
...order,
|
|
235
|
+
status: OrderStatus.ACTIVE,
|
|
236
|
+
processedAt: new Date().toISOString(),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Processes gift card orders.
|
|
243
|
+
*
|
|
244
|
+
* ASSUMPTIONS:
|
|
245
|
+
* - Order type is 'gift_card'
|
|
246
|
+
* - Order contains recipient information
|
|
247
|
+
*/
|
|
248
|
+
export class GiftCardOrderStrategy implements OrderProcessingStrategy {
|
|
249
|
+
/**
|
|
250
|
+
* Processes a gift card order by setting status to delivered.
|
|
251
|
+
*
|
|
252
|
+
* @param order - The gift card order to process
|
|
253
|
+
* @returns Order with status set to 'delivered' (card generated)
|
|
254
|
+
*/
|
|
255
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
256
|
+
// Assume: order.type === 'gift_card' (enforced by factory)
|
|
257
|
+
// Condition: Gift cards are immediately available
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
...order,
|
|
261
|
+
status: OrderStatus.DELIVERED,
|
|
262
|
+
processedAt: new Date().toISOString(),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Default strategy for unknown order types.
|
|
269
|
+
* Provides graceful fallback behavior instead of failing.
|
|
270
|
+
*
|
|
271
|
+
* ASSUMPTIONS:
|
|
272
|
+
* - Order type is unknown or unregistered
|
|
273
|
+
* - System should still process the order with a safe default
|
|
274
|
+
*/
|
|
275
|
+
export class DefaultOrderStrategy implements OrderProcessingStrategy {
|
|
276
|
+
/**
|
|
277
|
+
* Processes an unknown order type by setting status to pending.
|
|
278
|
+
* This allows the order to be reviewed manually later.
|
|
279
|
+
*
|
|
280
|
+
* @param order - The unknown order type to process
|
|
281
|
+
* @returns Order with status set to 'pending'
|
|
282
|
+
*/
|
|
283
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
284
|
+
// Assume: order.type is not recognized
|
|
285
|
+
// Condition: Unknown types default to pending for manual review
|
|
286
|
+
// Edge case: Order might be completely invalid (already validated)
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
...order,
|
|
290
|
+
status: OrderStatus.PENDING,
|
|
291
|
+
processedAt: new Date().toISOString(),
|
|
292
|
+
requiresManualReview: true,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================
|
|
298
|
+
// Factory Pattern (Dependency Inversion)
|
|
299
|
+
// ============================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Factory for creating and managing order processing strategies.
|
|
303
|
+
* Implements Dependency Inversion Principle by depending on abstractions (interface).
|
|
304
|
+
*
|
|
305
|
+
* BENEFITS:
|
|
306
|
+
* - Central strategy registration
|
|
307
|
+
* - Easy to extend with new strategies
|
|
308
|
+
* - Testable via dependency injection
|
|
309
|
+
* - Type-safe strategy lookup
|
|
310
|
+
*/
|
|
311
|
+
export class OrderProcessingStrategyFactory {
|
|
312
|
+
private strategies: Map<string, OrderProcessingStrategy> = new Map();
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Creates a new factory with default strategies registered.
|
|
316
|
+
*/
|
|
317
|
+
constructor() {
|
|
318
|
+
this.registerDefaultStrategies();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Registers all default order processing strategies.
|
|
323
|
+
* This is a template method that can be overridden or extended.
|
|
324
|
+
*/
|
|
325
|
+
private registerDefaultStrategies(): void {
|
|
326
|
+
this.registerStrategy('digital', new DigitalOrderStrategy());
|
|
327
|
+
this.registerStrategy('physical', new PhysicalOrderStrategy());
|
|
328
|
+
this.registerStrategy('subscription', new SubscriptionOrderStrategy());
|
|
329
|
+
this.registerStrategy('gift_card', new GiftCardOrderStrategy());
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Registers a new strategy for a specific order type.
|
|
334
|
+
*
|
|
335
|
+
* @param type - The order type this strategy handles
|
|
336
|
+
* @param strategy - The strategy instance to register
|
|
337
|
+
* @throws TypeError if type is empty or strategy is invalid
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```typescript
|
|
341
|
+
* factory.registerStrategy('service', new ServiceOrderStrategy());
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
registerStrategy(type: string, strategy: OrderProcessingStrategy): void {
|
|
345
|
+
if (!type || typeof type !== 'string') {
|
|
346
|
+
throw new TypeError('Order type must be a non-empty string');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!this.isValidStrategy(strategy)) {
|
|
350
|
+
throw new TypeError('Strategy must implement OrderProcessingStrategy interface');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.strategies.set(type, strategy);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Retrieves a strategy for the given order type.
|
|
358
|
+
* Returns DefaultOrderStrategy if type is not registered.
|
|
359
|
+
*
|
|
360
|
+
* @param type - The order type to get a strategy for
|
|
361
|
+
* @returns The appropriate strategy instance
|
|
362
|
+
*/
|
|
363
|
+
getStrategy(type: string): OrderProcessingStrategy {
|
|
364
|
+
// Assume: type is a non-empty string
|
|
365
|
+
// Condition: Returns strategy if found, else returns default
|
|
366
|
+
// Edge case: type might be undefined - handled gracefully
|
|
367
|
+
|
|
368
|
+
if (!type || typeof type !== 'string') {
|
|
369
|
+
return new DefaultOrderStrategy();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return this.strategies.get(type) || new DefaultOrderStrategy();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Checks if a strategy is registered for the given order type.
|
|
377
|
+
*
|
|
378
|
+
* @param type - The order type to check
|
|
379
|
+
* @returns true if a strategy is registered, false otherwise
|
|
380
|
+
*/
|
|
381
|
+
hasStrategy(type: string): boolean {
|
|
382
|
+
return this.strategies.has(type);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Returns all registered order type keys.
|
|
387
|
+
*
|
|
388
|
+
* @returns Array of registered order types
|
|
389
|
+
*/
|
|
390
|
+
getRegisteredTypes(): string[] {
|
|
391
|
+
return Array.from(this.strategies.keys());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Validates that a strategy implements the required interface.
|
|
396
|
+
* Uses duck typing to check for the 'process' method.
|
|
397
|
+
*
|
|
398
|
+
* @param strategy - The strategy to validate
|
|
399
|
+
* @returns true if valid, false otherwise
|
|
400
|
+
*/
|
|
401
|
+
private isValidStrategy(strategy: unknown): boolean {
|
|
402
|
+
return (
|
|
403
|
+
strategy !== null &&
|
|
404
|
+
typeof strategy === 'object' &&
|
|
405
|
+
'process' in strategy &&
|
|
406
|
+
typeof (strategy as OrderProcessingStrategy).process === 'function'
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ============================================
|
|
412
|
+
// Context / Processor (Liskov Substitution, Interface Segregation)
|
|
413
|
+
// ============================================
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Primary context class for processing orders.
|
|
417
|
+
* Implements Liskov Substitution Principle by working with
|
|
418
|
+
* OrderProcessingStrategy interface abstractions.
|
|
419
|
+
*
|
|
420
|
+
* Implements Interface Segregation Principle by having focused, cohesive methods.
|
|
421
|
+
*/
|
|
422
|
+
export class OrderProcessor {
|
|
423
|
+
private factory: OrderProcessingStrategyFactory;
|
|
424
|
+
private processingLog: Array<{ orderId: string; type: string; status: OrderStatus; timestamp: string }> = [];
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Creates a new OrderProcessor instance.
|
|
428
|
+
*
|
|
429
|
+
* @param factory - Optional custom strategy factory (dependency injection)
|
|
430
|
+
* @throws TypeError if factory is invalid
|
|
431
|
+
*/
|
|
432
|
+
constructor(factory?: OrderProcessingStrategyFactory) {
|
|
433
|
+
// Assume: factory is a valid OrderProcessingStrategyFactory instance
|
|
434
|
+
// Condition: Uses provided factory or creates default
|
|
435
|
+
// Edge case: Invalid factory - handled by type checking
|
|
436
|
+
|
|
437
|
+
if (factory && !(factory instanceof OrderProcessingStrategyFactory)) {
|
|
438
|
+
throw new TypeError('Factory must be an instance of OrderProcessingStrategyFactory');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.factory = factory || new OrderProcessingStrategyFactory();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Processes an order using the appropriate strategy.
|
|
446
|
+
*
|
|
447
|
+
* This method follows the Template Method pattern:
|
|
448
|
+
* 1. Validates the order
|
|
449
|
+
* 2. Selects the appropriate strategy
|
|
450
|
+
* 3. Processes the order
|
|
451
|
+
* 4. Logs the processing result
|
|
452
|
+
* 5. Returns the processed order
|
|
453
|
+
*
|
|
454
|
+
* @param order - The order to process
|
|
455
|
+
* @returns The processed order with updated status
|
|
456
|
+
* @throws OrderProcessingError if order is invalid
|
|
457
|
+
* @throws TypeError if order is null/undefined
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* const processor = new OrderProcessor();
|
|
462
|
+
* const result = processor.processOrder({ type: 'digital', id: '123' });
|
|
463
|
+
* console.log(result.status); // 'delivered'
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
processOrder(order: unknown): ProcessedOrder {
|
|
467
|
+
// Assume: order is a valid object with 'type' and 'id'
|
|
468
|
+
// Condition: Processes order using appropriate strategy
|
|
469
|
+
// Edge case: order is undefined/null - handled defensively
|
|
470
|
+
|
|
471
|
+
// Step 1: Input validation
|
|
472
|
+
if (order === null || order === undefined) {
|
|
473
|
+
throw new TypeError('Order cannot be null or undefined');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const validatedOrder = validateOrder(order);
|
|
477
|
+
|
|
478
|
+
// Step 2: Select strategy
|
|
479
|
+
const strategy = this.factory.getStrategy(validatedOrder.type);
|
|
480
|
+
|
|
481
|
+
// Step 3: Process order
|
|
482
|
+
const processedOrder = strategy.process(validatedOrder);
|
|
483
|
+
|
|
484
|
+
// Step 4: Log processing (optional, can be removed for pure functional approach)
|
|
485
|
+
this.logProcessing(validatedOrder.id, validatedOrder.type, processedOrder.status);
|
|
486
|
+
|
|
487
|
+
// Step 5: Return result
|
|
488
|
+
return processedOrder;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Processes multiple orders in batch.
|
|
493
|
+
* Returns results for all orders, even if some fail.
|
|
494
|
+
*
|
|
495
|
+
* @param orders - Array of orders to process
|
|
496
|
+
* @returns Array of processing results with success/failure status
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const results = processor.processBatch([
|
|
501
|
+
* { type: 'digital', id: '1' },
|
|
502
|
+
* { type: 'physical', id: '2' },
|
|
503
|
+
* ]);
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
processBatch(orders: unknown[]): Array<{ success: boolean; order: ProcessedOrder | null; error?: OrderProcessingError }> {
|
|
507
|
+
// Assume: orders is an array of objects
|
|
508
|
+
// Condition: Processes all orders, collecting results
|
|
509
|
+
// Edge case: orders array is empty - returns empty array
|
|
510
|
+
|
|
511
|
+
const results: Array<{ success: boolean; order: ProcessedOrder | null; error?: OrderProcessingError }> = [];
|
|
512
|
+
|
|
513
|
+
for (const order of orders) {
|
|
514
|
+
try {
|
|
515
|
+
const processedOrder = this.processOrder(order);
|
|
516
|
+
results.push({ success: true, order: processedOrder });
|
|
517
|
+
} catch (error) {
|
|
518
|
+
results.push({
|
|
519
|
+
success: false,
|
|
520
|
+
order: null,
|
|
521
|
+
error: error instanceof OrderProcessingError ? error : new OrderProcessingError(
|
|
522
|
+
'Unknown error during order processing',
|
|
523
|
+
'UNKNOWN_ERROR',
|
|
524
|
+
{ originalError: error }
|
|
525
|
+
),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return results;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Returns the internal strategy factory.
|
|
535
|
+
* Useful for testing or advanced configuration.
|
|
536
|
+
*
|
|
537
|
+
* @returns The strategy factory instance
|
|
538
|
+
*/
|
|
539
|
+
getFactory(): OrderProcessingStrategyFactory {
|
|
540
|
+
return this.factory;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Returns the processing log.
|
|
545
|
+
*
|
|
546
|
+
* @returns Array of processing log entries
|
|
547
|
+
*/
|
|
548
|
+
getProcessingLog(): Array<{ orderId: string; type: string; status: OrderStatus; timestamp: string }> {
|
|
549
|
+
return this.processingLog;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Clears the processing log.
|
|
554
|
+
* Useful for testing or resetting state.
|
|
555
|
+
*/
|
|
556
|
+
clearProcessingLog(): void {
|
|
557
|
+
this.processingLog = [];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Logs order processing internally.
|
|
562
|
+
* This can be replaced with a proper logging system.
|
|
563
|
+
*
|
|
564
|
+
* @param orderId - The order ID
|
|
565
|
+
* @param type - The order type
|
|
566
|
+
* @param status - The resulting status
|
|
567
|
+
*/
|
|
568
|
+
private logProcessing(orderId: string, type: string, status: OrderStatus): void {
|
|
569
|
+
this.processingLog.push({
|
|
570
|
+
orderId,
|
|
571
|
+
type,
|
|
572
|
+
status,
|
|
573
|
+
timestamp: new Date().toISOString(),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ============================================
|
|
579
|
+
// Usage Examples
|
|
580
|
+
// ============================================
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Example 1: Basic usage with default strategies.
|
|
584
|
+
*/
|
|
585
|
+
function example1_BasicUsage(): void {
|
|
586
|
+
console.log('=== Example 1: Basic Usage ===');
|
|
587
|
+
|
|
588
|
+
const processor = new OrderProcessor();
|
|
589
|
+
|
|
590
|
+
// Process different order types
|
|
591
|
+
const digitalOrder = processor.processOrder({
|
|
592
|
+
type: 'digital',
|
|
593
|
+
id: 'DIG-001',
|
|
594
|
+
items: ['software license'],
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const physicalOrder = processor.processOrder({
|
|
598
|
+
type: 'physical',
|
|
599
|
+
id: 'PHY-001',
|
|
600
|
+
items: ['laptop'],
|
|
601
|
+
shippingAddress: '123 Main St',
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
const subscriptionOrder = processor.processOrder({
|
|
605
|
+
type: 'subscription',
|
|
606
|
+
id: 'SUB-001',
|
|
607
|
+
items: ['monthly service'],
|
|
608
|
+
billingCycle: 'monthly',
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
console.log('Digital Order:', digitalOrder); // { type: 'digital', status: 'delivered', ... }
|
|
612
|
+
console.log('Physical Order:', physicalOrder); // { type: 'physical', status: 'shipped', ... }
|
|
613
|
+
console.log('Subscription Order:', subscriptionOrder); // { type: 'subscription', status: 'active', ... }
|
|
614
|
+
console.log('');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Example 2: Handling unknown order types.
|
|
619
|
+
*/
|
|
620
|
+
function example2_UnknownTypes(): void {
|
|
621
|
+
console.log('=== Example 2: Unknown Order Types ===');
|
|
622
|
+
|
|
623
|
+
const processor = new OrderProcessor();
|
|
624
|
+
|
|
625
|
+
// Unknown order type (falls back to DefaultOrderStrategy)
|
|
626
|
+
const unknownOrder = processor.processOrder({
|
|
627
|
+
type: 'unknown',
|
|
628
|
+
id: 'UNK-001',
|
|
629
|
+
items: ['something'],
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
console.log('Unknown Order:', unknownOrder); // { type: 'unknown', status: 'pending', requiresManualReview: true, ... }
|
|
633
|
+
console.log('');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Example 3: Extending with custom strategies (Open/Closed Principle).
|
|
638
|
+
*/
|
|
639
|
+
function example3_CustomStrategies(): void {
|
|
640
|
+
console.log('=== Example 3: Custom Strategies (OCP) ===');
|
|
641
|
+
|
|
642
|
+
// Define a new strategy for service orders
|
|
643
|
+
class ServiceOrderStrategy implements OrderProcessingStrategy {
|
|
644
|
+
process(order: BaseOrder): ProcessedOrder {
|
|
645
|
+
return { ...order, status: OrderStatus.SCHEDULED, processedAt: new Date().toISOString() };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Create custom factory with additional strategy
|
|
650
|
+
const customFactory = new OrderProcessingStrategyFactory();
|
|
651
|
+
customFactory.registerStrategy('service', new ServiceOrderStrategy());
|
|
652
|
+
|
|
653
|
+
// Create processor with custom factory
|
|
654
|
+
const processor = new OrderProcessor(customFactory);
|
|
655
|
+
|
|
656
|
+
// Now we can process service orders
|
|
657
|
+
const serviceOrder = processor.processOrder({
|
|
658
|
+
type: 'service',
|
|
659
|
+
id: 'SRV-001',
|
|
660
|
+
items: ['consultation'],
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
console.log('Service Order:', serviceOrder); // { type: 'service', status: 'scheduled', ... }
|
|
664
|
+
console.log('');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Example 4: Batch processing with error handling.
|
|
669
|
+
*/
|
|
670
|
+
function example4_BatchProcessing(): void {
|
|
671
|
+
console.log('=== Example 4: Batch Processing ===');
|
|
672
|
+
|
|
673
|
+
const processor = new OrderProcessor();
|
|
674
|
+
|
|
675
|
+
const results = processor.processBatch([
|
|
676
|
+
{ type: 'digital', id: '1' },
|
|
677
|
+
{ type: 'physical', id: '2' },
|
|
678
|
+
{ type: 'invalid', id: '' }, // Will fail validation
|
|
679
|
+
null, // Will fail type check
|
|
680
|
+
]);
|
|
681
|
+
|
|
682
|
+
console.log('Batch Results:', results);
|
|
683
|
+
console.log('Successful:', results.filter(r => r.success).length);
|
|
684
|
+
console.log('Failed:', results.filter(r => !r.success).length);
|
|
685
|
+
console.log('');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Example 5: Error handling edge cases.
|
|
690
|
+
*/
|
|
691
|
+
function example5_ErrorHandling(): void {
|
|
692
|
+
console.log('=== Example 5: Error Handling ===');
|
|
693
|
+
|
|
694
|
+
const processor = new OrderProcessor();
|
|
695
|
+
|
|
696
|
+
// Try invalid order (missing 'type')
|
|
697
|
+
try {
|
|
698
|
+
processor.processOrder({ id: '123' });
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.log('Caught error for missing type:', error instanceof OrderProcessingError ? error.message : error);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Try null order
|
|
704
|
+
try {
|
|
705
|
+
processor.processOrder(null);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.log('Caught error for null order:', error instanceof TypeError ? error.message : error);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
console.log('');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Example 6: Processing log inspection.
|
|
715
|
+
*/
|
|
716
|
+
function example6_ProcessingLog(): void {
|
|
717
|
+
console.log('=== Example 6: Processing Log ===');
|
|
718
|
+
|
|
719
|
+
const processor = new OrderProcessor();
|
|
720
|
+
|
|
721
|
+
// Process some orders
|
|
722
|
+
processor.processOrder({ type: 'digital', id: '1' });
|
|
723
|
+
processor.processOrder({ type: 'physical', id: '2' });
|
|
724
|
+
processor.processOrder({ type: 'subscription', id: '3' });
|
|
725
|
+
|
|
726
|
+
// Inspect the log
|
|
727
|
+
const log = processor.getProcessingLog();
|
|
728
|
+
console.log('Processing Log:', log);
|
|
729
|
+
console.log('');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Run all examples.
|
|
734
|
+
*/
|
|
735
|
+
export function runExamples(): void {
|
|
736
|
+
example1_BasicUsage();
|
|
737
|
+
example2_UnknownTypes();
|
|
738
|
+
example3_CustomStrategies();
|
|
739
|
+
example4_BatchProcessing();
|
|
740
|
+
example5_ErrorHandling();
|
|
741
|
+
example6_ProcessingLog();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ============================================
|
|
745
|
+
// Comparison with Original Code
|
|
746
|
+
// ============================================
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* ORIGINAL CODE (SOLID Violations):
|
|
750
|
+
*
|
|
751
|
+
* function processOrder(order: any) {
|
|
752
|
+
* if (order.type === 'digital') {
|
|
753
|
+
* console.log('Sending email with download link');
|
|
754
|
+
* order.status = 'delivered';
|
|
755
|
+
* } else if (order.type === 'physical') {
|
|
756
|
+
* console.log('Creating shipping label');
|
|
757
|
+
* order.status = 'shipped';
|
|
758
|
+
* } else if (order.type === 'subscription') {
|
|
759
|
+
* console.log('Activating subscription');
|
|
760
|
+
* order.status = 'active';
|
|
761
|
+
* }
|
|
762
|
+
* console.log('Order processed: ' + order.id);
|
|
763
|
+
* return order;
|
|
764
|
+
* }
|
|
765
|
+
*
|
|
766
|
+
* SOLID VIOLATIONS:
|
|
767
|
+
* - Single Responsibility: Handles multiple order types in one function
|
|
768
|
+
* - Open/Closed: Must modify function to add new order types
|
|
769
|
+
* - Liskov Substitution: No abstraction, relies on concrete conditionals
|
|
770
|
+
* - Interface Segregation: Monolithic function with mixed concerns
|
|
771
|
+
* - Dependency Inversion: Directly depends on concrete order structure
|
|
772
|
+
*
|
|
773
|
+
* OTHER ISSUES:
|
|
774
|
+
* - Uses 'any' type (no type safety)
|
|
775
|
+
* - Mutates input object (not idempotent)
|
|
776
|
+
* - No validation of input
|
|
777
|
+
* - No error handling
|
|
778
|
+
* - Console.log for side effects (not testable)
|
|
779
|
+
* - Cannot handle unknown order types
|
|
780
|
+
* - Hard to test individual behaviors
|
|
781
|
+
*/
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* REFACTORED CODE (SOLID Compliant):
|
|
785
|
+
*
|
|
786
|
+
* SOLID PRINCIPLES SATISFIED:
|
|
787
|
+
*
|
|
788
|
+
* Single Responsibility Principle (SRP):
|
|
789
|
+
* - Each strategy handles one type of order
|
|
790
|
+
* - OrderProcessor coordinates but doesn't implement logic
|
|
791
|
+
* - OrderProcessingStrategyFactory manages registration
|
|
792
|
+
*
|
|
793
|
+
* Open/Closed Principle (OCP):
|
|
794
|
+
* - New order types added via new strategies, no code modification
|
|
795
|
+
* - Factory allows runtime registration of strategies
|
|
796
|
+
* - Interface-based design allows easy extension
|
|
797
|
+
*
|
|
798
|
+
* Liskov Substitution Principle (LSP):
|
|
799
|
+
* - All strategies implement the same interface
|
|
800
|
+
* - Strategies are interchangeable without breaking code
|
|
801
|
+
* - DefaultOrderStrategy provides safe fallback behavior
|
|
802
|
+
*
|
|
803
|
+
* Interface Segregation Principle (ISP):
|
|
804
|
+
* - Each interface has focused methods
|
|
805
|
+
* - OrderProcessingStrategy has single method
|
|
806
|
+
* - OrderProcessor has cohesive, related methods
|
|
807
|
+
*
|
|
808
|
+
* Dependency Inversion Principle (DIP):
|
|
809
|
+
* - OrderProcessor depends on OrderProcessingStrategy interface
|
|
810
|
+
* - Concrete strategies injected via factory
|
|
811
|
+
* - Dependencies can be swapped at runtime
|
|
812
|
+
*
|
|
813
|
+
* ADDITIONAL IMPROVEMENTS:
|
|
814
|
+
* - Runtime validation with Zod schemas
|
|
815
|
+
* - Custom error types with context
|
|
816
|
+
* - Immutable operations (returns new objects)
|
|
817
|
+
* - Comprehensive JSDoc documentation
|
|
818
|
+
* - Type safety throughout
|
|
819
|
+
* - Testable design (dependency injection)
|
|
820
|
+
* - Handles unknown order types gracefully
|
|
821
|
+
* - Processing log for debugging
|
|
822
|
+
* - Batch processing support
|
|
823
|
+
* - Clear assumptions and edge case documentation
|
|
824
|
+
*/
|
|
825
|
+
|
|
826
|
+
// Export types for external use
|
|
827
|
+
export type {
|
|
828
|
+
BaseOrder,
|
|
829
|
+
ProcessedOrder,
|
|
830
|
+
};
|