@famgia/omnify-laravel 0.0.88 → 0.0.90

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.
Files changed (49) hide show
  1. package/dist/{chunk-YVVAJA3T.js → chunk-2QSKZS63.js} +188 -12
  2. package/dist/chunk-2QSKZS63.js.map +1 -0
  3. package/dist/index.cjs +190 -11
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +48 -1
  6. package/dist/index.d.ts +48 -1
  7. package/dist/index.js +5 -1
  8. package/dist/plugin.cjs +186 -11
  9. package/dist/plugin.cjs.map +1 -1
  10. package/dist/plugin.js +1 -1
  11. package/package.json +5 -5
  12. package/scripts/postinstall.js +29 -36
  13. package/stubs/ai-guides/README.md.stub +95 -0
  14. package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
  15. package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
  16. package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
  17. package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
  18. package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
  19. package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
  20. package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
  21. package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
  22. package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
  23. package/stubs/ai-guides/claude-omnify/react-form-guide.md.stub +259 -0
  24. package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
  25. package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
  26. package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
  27. package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
  28. package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
  29. package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
  30. package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
  31. package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
  32. package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
  33. package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
  34. package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
  35. package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
  36. package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
  37. package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
  38. package/stubs/ai-guides/cursor/omnify.mdc.stub +58 -0
  39. package/stubs/ai-guides/laravel/README.md.stub +59 -0
  40. package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
  41. package/stubs/ai-guides/laravel/controller.md.stub +484 -0
  42. package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
  43. package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
  44. package/stubs/ai-guides/laravel/request.md.stub +450 -0
  45. package/stubs/ai-guides/laravel/resource.md.stub +516 -0
  46. package/stubs/ai-guides/laravel/service.md.stub +503 -0
  47. package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
  48. package/ai-guides/laravel-guide.md +0 -461
  49. package/dist/chunk-YVVAJA3T.js.map +0 -1
@@ -0,0 +1,503 @@
1
+ # Service Guide
2
+
3
+ > **Related:** [README](./README.md) | [Controller Guide](./controller-guide.md) | [Design Philosophy](./design-philosophy.md)
4
+
5
+ ## When to Use Service
6
+
7
+ | ✅ Use Service | ❌ Don't Use Service |
8
+ | ------------------------------------ | -------------------------- |
9
+ | Multi-step business logic | Simple CRUD |
10
+ | External API calls | Single model operation |
11
+ | Complex calculations | Basic queries |
12
+ | Logic reused across controllers | Controller-specific logic |
13
+ | Transaction spanning multiple models | Single model create/update |
14
+
15
+ ---
16
+
17
+ ## Service vs Action
18
+
19
+ | Service | Action |
20
+ | --------------------------------------------- | -------------------------------- |
21
+ | Multiple related operations | Single purpose |
22
+ | Stateful (can have dependencies) | Usually stateless |
23
+ | `OrderService::checkout()`, `processRefund()` | `CreateInvoiceAction::execute()` |
24
+ | Groups related methods | One class = one action |
25
+
26
+ ---
27
+
28
+ ## Service Template
29
+
30
+ ```php
31
+ <?php
32
+
33
+ namespace App\Services;
34
+
35
+ use App\Models\Cart;
36
+ use App\Models\Order;
37
+ use App\Models\User;
38
+ use App\Events\OrderPlaced;
39
+ use App\Exceptions\InsufficientInventoryException;
40
+ use App\Exceptions\PaymentFailedException;
41
+ use Illuminate\Support\Facades\DB;
42
+
43
+ class OrderService
44
+ {
45
+ public function __construct(
46
+ private PaymentService $paymentService,
47
+ private InventoryService $inventoryService
48
+ ) {}
49
+
50
+ /**
51
+ * Process checkout: validate → pay → create order → update inventory
52
+ *
53
+ * @throws InsufficientInventoryException
54
+ * @throws PaymentFailedException
55
+ */
56
+ public function checkout(Cart $cart, User $user, string $paymentMethod): Order
57
+ {
58
+ // 1. Validate business rules
59
+ $this->validateCart($cart);
60
+ $this->inventoryService->validateAvailability($cart->items);
61
+
62
+ // 2. Calculate totals
63
+ $totals = $this->calculateTotals($cart);
64
+
65
+ // 3. Process payment
66
+ $payment = $this->paymentService->charge(
67
+ user: $user,
68
+ amount: $totals['total'],
69
+ method: $paymentMethod
70
+ );
71
+
72
+ // 4. Create order (in transaction)
73
+ $order = DB::transaction(function () use ($cart, $user, $totals, $payment) {
74
+ $order = Order::create([
75
+ 'user_id' => $user->id,
76
+ 'subtotal' => $totals['subtotal'],
77
+ 'tax' => $totals['tax'],
78
+ 'total' => $totals['total'],
79
+ 'payment_id' => $payment->id,
80
+ 'status' => 'paid',
81
+ ]);
82
+
83
+ // Create order items
84
+ foreach ($cart->items as $item) {
85
+ $order->items()->create([
86
+ 'product_id' => $item->product_id,
87
+ 'quantity' => $item->quantity,
88
+ 'price' => $item->price,
89
+ ]);
90
+ }
91
+
92
+ // Update inventory
93
+ $this->inventoryService->decrementStock($cart->items);
94
+
95
+ // Clear cart
96
+ $cart->items()->delete();
97
+ $cart->delete();
98
+
99
+ return $order;
100
+ });
101
+
102
+ // 5. Dispatch events (outside transaction)
103
+ event(new OrderPlaced($order));
104
+
105
+ return $order;
106
+ }
107
+
108
+ /**
109
+ * Process refund for an order
110
+ */
111
+ public function refund(Order $order, ?string $reason = null): Order
112
+ {
113
+ if (!$order->canBeRefunded()) {
114
+ throw new \DomainException('Order cannot be refunded');
115
+ }
116
+
117
+ return DB::transaction(function () use ($order, $reason) {
118
+ // Refund payment
119
+ $this->paymentService->refund($order->payment_id);
120
+
121
+ // Restore inventory
122
+ $this->inventoryService->restoreStock($order->items);
123
+
124
+ // Update order
125
+ $order->update([
126
+ 'status' => 'refunded',
127
+ 'refund_reason' => $reason,
128
+ 'refunded_at' => now(),
129
+ ]);
130
+
131
+ return $order->fresh();
132
+ });
133
+ }
134
+
135
+ private function validateCart(Cart $cart): void
136
+ {
137
+ if ($cart->items->isEmpty()) {
138
+ throw new \DomainException('Cart is empty');
139
+ }
140
+ }
141
+
142
+ private function calculateTotals(Cart $cart): array
143
+ {
144
+ $subtotal = $cart->items->sum(fn ($item) => $item->price * $item->quantity);
145
+ $tax = $subtotal * 0.1; // 10% tax
146
+
147
+ return [
148
+ 'subtotal' => $subtotal,
149
+ 'tax' => $tax,
150
+ 'total' => $subtotal + $tax,
151
+ ];
152
+ }
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Using Service in Controller
159
+
160
+ ```php
161
+ <?php
162
+
163
+ namespace App\Http\Controllers;
164
+
165
+ use App\Http\Requests\OrderStoreRequest;
166
+ use App\Http\Resources\OrderResource;
167
+ use App\Models\Cart;
168
+ use App\Services\OrderService;
169
+
170
+ class OrderController extends Controller
171
+ {
172
+ public function __construct(
173
+ private OrderService $orderService
174
+ ) {}
175
+
176
+ public function store(OrderStoreRequest $request): OrderResource
177
+ {
178
+ $order = $this->orderService->checkout(
179
+ cart: Cart::findOrFail($request->cart_id),
180
+ user: $request->user(),
181
+ paymentMethod: $request->payment_method
182
+ );
183
+
184
+ return new OrderResource($order);
185
+ }
186
+
187
+ public function refund(Order $order, RefundRequest $request): OrderResource
188
+ {
189
+ $order = $this->orderService->refund(
190
+ order: $order,
191
+ reason: $request->reason
192
+ );
193
+
194
+ return new OrderResource($order);
195
+ }
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Service Rules
202
+
203
+ ### 1. Constructor Injection for Dependencies
204
+
205
+ ```php
206
+ // ✅ DO: Inject dependencies
207
+ class OrderService
208
+ {
209
+ public function __construct(
210
+ private PaymentService $paymentService,
211
+ private InventoryService $inventoryService
212
+ ) {}
213
+ }
214
+
215
+ // ❌ DON'T: Create dependencies inside
216
+ class OrderService
217
+ {
218
+ public function checkout()
219
+ {
220
+ $paymentService = new PaymentService(); // Hard to test
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### 2. Use Transactions for Multi-Model Operations
226
+
227
+ ```php
228
+ // ✅ DO: Wrap in transaction
229
+ return DB::transaction(function () {
230
+ $order = Order::create([...]);
231
+ $order->items()->createMany([...]);
232
+ $this->updateInventory();
233
+ return $order;
234
+ });
235
+
236
+ // ❌ DON'T: Multiple saves without transaction
237
+ $order = Order::create([...]);
238
+ $order->items()->createMany([...]); // If this fails, order exists without items
239
+ ```
240
+
241
+ ### 3. Dispatch Events Outside Transaction
242
+
243
+ ```php
244
+ // ✅ DO: Events after transaction commits
245
+ $order = DB::transaction(function () {
246
+ return Order::create([...]);
247
+ });
248
+ event(new OrderPlaced($order)); // After transaction
249
+
250
+ // ❌ DON'T: Events inside transaction (might fire before commit)
251
+ DB::transaction(function () {
252
+ $order = Order::create([...]);
253
+ event(new OrderPlaced($order)); // Dangerous!
254
+ });
255
+ ```
256
+
257
+ ### 4. Throw Domain Exceptions
258
+
259
+ ```php
260
+ // ✅ DO: Custom exceptions for business rules
261
+ if (!$order->canBeRefunded()) {
262
+ throw new OrderCannotBeRefundedException($order);
263
+ }
264
+
265
+ if ($cart->items->isEmpty()) {
266
+ throw new EmptyCartException();
267
+ }
268
+
269
+ // ❌ DON'T: Generic exceptions
270
+ throw new \Exception('Cart is empty'); // Not specific
271
+ ```
272
+
273
+ ### 5. Return Models, Not Arrays
274
+
275
+ ```php
276
+ // ✅ DO: Return Eloquent models
277
+ public function checkout(...): Order
278
+ {
279
+ // ...
280
+ return $order;
281
+ }
282
+
283
+ // ❌ DON'T: Return arrays (loses type safety)
284
+ public function checkout(...): array
285
+ {
286
+ return ['order' => $order, 'invoice' => $invoice];
287
+ }
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Action Pattern (Alternative)
293
+
294
+ For single-purpose operations that need to be reused:
295
+
296
+ ```php
297
+ <?php
298
+
299
+ namespace App\Actions;
300
+
301
+ use App\Models\Order;
302
+ use App\Models\Invoice;
303
+ use App\Jobs\GenerateInvoicePdf;
304
+
305
+ class CreateInvoiceAction
306
+ {
307
+ public function execute(Order $order): Invoice
308
+ {
309
+ $invoice = Invoice::create([
310
+ 'order_id' => $order->id,
311
+ 'number' => $this->generateNumber(),
312
+ 'subtotal' => $order->subtotal,
313
+ 'tax' => $order->tax,
314
+ 'total' => $order->total,
315
+ 'issued_at' => now(),
316
+ ]);
317
+
318
+ GenerateInvoicePdf::dispatch($invoice);
319
+
320
+ return $invoice;
321
+ }
322
+
323
+ private function generateNumber(): string
324
+ {
325
+ $year = now()->format('Y');
326
+ $sequence = Invoice::whereYear('created_at', $year)->count() + 1;
327
+
328
+ return sprintf('INV-%s-%05d', $year, $sequence);
329
+ }
330
+ }
331
+ ```
332
+
333
+ ### Using Action
334
+
335
+ ```php
336
+ // In Controller
337
+ public function store(OrderRequest $request, CreateInvoiceAction $createInvoice)
338
+ {
339
+ $order = Order::create($request->validated());
340
+ $createInvoice->execute($order);
341
+
342
+ return new OrderResource($order);
343
+ }
344
+
345
+ // In Job
346
+ class RecurringBillingJob
347
+ {
348
+ public function handle(CreateInvoiceAction $createInvoice)
349
+ {
350
+ $subscriptions = Subscription::due()->get();
351
+
352
+ foreach ($subscriptions as $subscription) {
353
+ $order = $subscription->createRenewalOrder();
354
+ $createInvoice->execute($order);
355
+ }
356
+ }
357
+ }
358
+
359
+ // In Console Command
360
+ class CreateMonthlyInvoicesCommand extends Command
361
+ {
362
+ public function handle(CreateInvoiceAction $createInvoice)
363
+ {
364
+ Order::unpaid()->each(fn ($order) => $createInvoice->execute($order));
365
+ }
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Service vs Controller Decision
372
+
373
+ ```
374
+ ┌─────────────────────────────────────────────┐
375
+ │ NEW FEATURE │
376
+ └─────────────────┬───────────────────────────┘
377
+
378
+ Is it simple CRUD?
379
+
380
+ ┌──────────┴──────────┐
381
+ │ │
382
+ YES NO
383
+ │ │
384
+ ▼ ▼
385
+ Controller How many steps?
386
+ handles it │
387
+ ┌──────────┴──────────┐
388
+ │ │
389
+ 1-2 steps 3+ steps
390
+ │ │
391
+ ▼ ▼
392
+ Controller Service
393
+ + Job/Event
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Anti-Patterns
399
+
400
+ ```php
401
+ // ❌ DON'T: Service for simple CRUD
402
+ class UserService
403
+ {
404
+ public function create(array $data): User
405
+ {
406
+ return User::create($data); // Pointless wrapper
407
+ }
408
+ }
409
+
410
+ // ❌ DON'T: Service calling controller methods
411
+ class UserService
412
+ {
413
+ public function __construct(private UserController $controller) {}
414
+ }
415
+
416
+ // ❌ DON'T: HTTP-specific logic in service
417
+ class UserService
418
+ {
419
+ public function create(Request $request) // Don't pass Request
420
+ {
421
+ return User::create($request->validated());
422
+ }
423
+ }
424
+
425
+ // ✅ DO: Pass validated data
426
+ class UserService
427
+ {
428
+ public function createWithTeam(array $userData, array $teamData): User
429
+ {
430
+ // Complex operation that involves multiple models
431
+ }
432
+ }
433
+
434
+ // ❌ DON'T: Return response in service
435
+ class OrderService
436
+ {
437
+ public function checkout(): JsonResponse // Don't return HTTP response
438
+ {
439
+ return response()->json($order);
440
+ }
441
+ }
442
+
443
+ // ✅ DO: Return model, let controller format response
444
+ class OrderService
445
+ {
446
+ public function checkout(): Order
447
+ {
448
+ return $order;
449
+ }
450
+ }
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Testing Services
456
+
457
+ ```php
458
+ <?php
459
+
460
+ namespace Tests\Unit\Services;
461
+
462
+ use App\Services\OrderService;
463
+ use App\Models\Cart;
464
+ use App\Models\User;
465
+ use Tests\TestCase;
466
+
467
+ class OrderServiceTest extends TestCase
468
+ {
469
+ private OrderService $service;
470
+
471
+ protected function setUp(): void
472
+ {
473
+ parent::setUp();
474
+ $this->service = app(OrderService::class);
475
+ }
476
+
477
+ public function test_checkout_creates_order(): void
478
+ {
479
+ $user = User::factory()->create();
480
+ $cart = Cart::factory()->hasItems(3)->create(['user_id' => $user->id]);
481
+
482
+ $order = $this->service->checkout(
483
+ cart: $cart,
484
+ user: $user,
485
+ paymentMethod: 'card'
486
+ );
487
+
488
+ $this->assertNotNull($order->id);
489
+ $this->assertEquals('paid', $order->status);
490
+ $this->assertCount(3, $order->items);
491
+ }
492
+
493
+ public function test_checkout_fails_with_empty_cart(): void
494
+ {
495
+ $this->expectException(\DomainException::class);
496
+
497
+ $user = User::factory()->create();
498
+ $cart = Cart::factory()->create(['user_id' => $user->id]);
499
+
500
+ $this->service->checkout($cart, $user, 'card');
501
+ }
502
+ }
503
+ ```