@famgia/omnify-laravel 0.0.88 → 0.0.89

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 (46) hide show
  1. package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
  2. package/dist/chunk-V7LWJ6OM.js.map +1 -0
  3. package/dist/index.cjs +180 -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 +176 -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/claude-agents/architect.md.stub +150 -0
  14. package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
  15. package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
  16. package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
  17. package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
  18. package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
  19. package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
  20. package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
  21. package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
  22. package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
  23. package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
  24. package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
  25. package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
  26. package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
  27. package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
  28. package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
  29. package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
  30. package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
  31. package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
  32. package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
  33. package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
  34. package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
  35. package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
  36. package/stubs/ai-guides/laravel/README.md.stub +59 -0
  37. package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
  38. package/stubs/ai-guides/laravel/controller.md.stub +484 -0
  39. package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
  40. package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
  41. package/stubs/ai-guides/laravel/request.md.stub +450 -0
  42. package/stubs/ai-guides/laravel/resource.md.stub +516 -0
  43. package/stubs/ai-guides/laravel/service.md.stub +503 -0
  44. package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
  45. package/ai-guides/laravel-guide.md +0 -461
  46. package/dist/chunk-YVVAJA3T.js.map +0 -1
@@ -0,0 +1,516 @@
1
+ # API Resource Guide
2
+
3
+ > **Related:** [README](./README.md) | [Controller Guide](./controller-guide.md) | [DateTime Guide](../laravel/datetime-guide.md)
4
+
5
+ ## Why Resources?
6
+
7
+ | Without Resources | With Resources |
8
+ | ----------------------- | ------------------------ |
9
+ | Expose all model fields | Control exposed fields |
10
+ | Inconsistent formats | Consistent API format |
11
+ | Manual JSON building | Automatic transformation |
12
+ | Date format varies | ISO 8601 everywhere |
13
+
14
+ ---
15
+
16
+ ## Resource Template
17
+
18
+ ```php
19
+ <?php
20
+
21
+ namespace App\Http\Resources;
22
+
23
+ use Illuminate\Http\Request;
24
+ use Illuminate\Http\Resources\Json\JsonResource;
25
+
26
+ class UserResource extends JsonResource
27
+ {
28
+ /**
29
+ * Transform the resource into an array.
30
+ *
31
+ * @return array<string, mixed>
32
+ */
33
+ public function toArray(Request $request): array
34
+ {
35
+ return [
36
+ 'id' => $this->id,
37
+ 'name' => $this->name,
38
+ 'email' => $this->email,
39
+ 'email_verified_at' => $this->email_verified_at?->toISOString(),
40
+ 'created_at' => $this->created_at?->toISOString(),
41
+ 'updated_at' => $this->updated_at?->toISOString(),
42
+ ];
43
+ }
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Response Formats
50
+
51
+ ### Single Resource
52
+
53
+ ```php
54
+ // Controller
55
+ return new UserResource($user);
56
+
57
+ // Response
58
+ {
59
+ "data": {
60
+ "id": 1,
61
+ "name": "John Doe",
62
+ "email": "john@example.com",
63
+ "created_at": "2024-01-15T10:30:00.000000Z"
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Collection (Paginated)
69
+
70
+ ```php
71
+ // Controller
72
+ return UserResource::collection($users->paginate(15));
73
+
74
+ // Response
75
+ {
76
+ "data": [
77
+ { "id": 1, "name": "John", ... },
78
+ { "id": 2, "name": "Jane", ... }
79
+ ],
80
+ "links": {
81
+ "first": "http://api.example.com/users?page=1",
82
+ "last": "http://api.example.com/users?page=10",
83
+ "prev": null,
84
+ "next": "http://api.example.com/users?page=2"
85
+ },
86
+ "meta": {
87
+ "current_page": 1,
88
+ "from": 1,
89
+ "last_page": 10,
90
+ "per_page": 15,
91
+ "to": 15,
92
+ "total": 150
93
+ }
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Date Formatting Rules
100
+
101
+ ### Always Use ISO 8601 UTC
102
+
103
+ ```php
104
+ // ✅ CORRECT: toISOString() returns UTC with Z suffix
105
+ 'created_at' => $this->created_at?->toISOString(),
106
+ // Output: "2024-01-15T10:30:00.000000Z"
107
+
108
+ // ❌ WRONG: Local format
109
+ 'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
110
+ // Output: "2024-01-15 10:30:00" (ambiguous timezone)
111
+
112
+ // ❌ WRONG: Local timezone
113
+ 'created_at' => $this->created_at?->setTimezone('Asia/Tokyo')->format('Y-m-d H:i:s'),
114
+ // Output: "2024-01-15 19:30:00" (local time, confusing)
115
+ ```
116
+
117
+ ### Nullable Dates
118
+
119
+ ```php
120
+ // ✅ Use null-safe operator
121
+ 'email_verified_at' => $this->email_verified_at?->toISOString(),
122
+ // Returns null if not set
123
+
124
+ // ❌ Don't return empty string
125
+ 'email_verified_at' => $this->email_verified_at ? $this->email_verified_at->toISOString() : '',
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Relationships
131
+
132
+ ### Eager Loading Required
133
+
134
+ ```php
135
+ // Controller - always eager load
136
+ public function show(User $user): UserResource
137
+ {
138
+ $user->load('posts', 'profile');
139
+ return new UserResource($user);
140
+ }
141
+
142
+ // Resource
143
+ public function toArray(Request $request): array
144
+ {
145
+ return [
146
+ 'id' => $this->id,
147
+ 'name' => $this->name,
148
+ 'profile' => new ProfileResource($this->whenLoaded('profile')),
149
+ 'posts' => PostResource::collection($this->whenLoaded('posts')),
150
+ ];
151
+ }
152
+ ```
153
+
154
+ ### Conditional Relationships
155
+
156
+ ```php
157
+ public function toArray(Request $request): array
158
+ {
159
+ return [
160
+ 'id' => $this->id,
161
+ 'name' => $this->name,
162
+
163
+ // Only include if loaded (prevents N+1)
164
+ 'profile' => new ProfileResource($this->whenLoaded('profile')),
165
+
166
+ // Only include if loaded
167
+ 'posts' => PostResource::collection($this->whenLoaded('posts')),
168
+
169
+ // Only include count if loaded
170
+ 'posts_count' => $this->whenCounted('posts'),
171
+ ];
172
+ }
173
+ ```
174
+
175
+ ### Nested Resources
176
+
177
+ ```php
178
+ // PostResource
179
+ public function toArray(Request $request): array
180
+ {
181
+ return [
182
+ 'id' => $this->id,
183
+ 'title' => $this->title,
184
+ 'author' => new UserResource($this->whenLoaded('author')),
185
+ 'comments' => CommentResource::collection($this->whenLoaded('comments')),
186
+ ];
187
+ }
188
+
189
+ // Controller with nested eager loading
190
+ public function show(Post $post): PostResource
191
+ {
192
+ $post->load(['author', 'comments.user']);
193
+ return new PostResource($post);
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Conditional Fields
200
+
201
+ ### Based on Auth
202
+
203
+ ```php
204
+ public function toArray(Request $request): array
205
+ {
206
+ return [
207
+ 'id' => $this->id,
208
+ 'name' => $this->name,
209
+ 'email' => $this->email,
210
+
211
+ // Only for admin
212
+ 'admin_notes' => $this->when(
213
+ $request->user()?->isAdmin(),
214
+ $this->admin_notes
215
+ ),
216
+
217
+ // Only for owner
218
+ 'api_token' => $this->when(
219
+ $request->user()?->id === $this->id,
220
+ $this->api_token
221
+ ),
222
+ ];
223
+ }
224
+ ```
225
+
226
+ ### Based on Model State
227
+
228
+ ```php
229
+ public function toArray(Request $request): array
230
+ {
231
+ return [
232
+ 'id' => $this->id,
233
+ 'title' => $this->title,
234
+ 'status' => $this->status,
235
+
236
+ // Only if published
237
+ 'published_at' => $this->when(
238
+ $this->status === 'published',
239
+ $this->published_at?->toISOString()
240
+ ),
241
+
242
+ // Only if has discount
243
+ 'discount' => $this->when($this->discount > 0, [
244
+ 'amount' => $this->discount,
245
+ 'percentage' => $this->discount_percentage,
246
+ ]),
247
+ ];
248
+ }
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Computed Fields
254
+
255
+ ```php
256
+ public function toArray(Request $request): array
257
+ {
258
+ return [
259
+ 'id' => $this->id,
260
+ 'first_name' => $this->first_name,
261
+ 'last_name' => $this->last_name,
262
+
263
+ // Computed
264
+ 'full_name' => $this->first_name . ' ' . $this->last_name,
265
+
266
+ // From accessor
267
+ 'avatar_url' => $this->avatar_url, // Model accessor
268
+
269
+ // Boolean flags
270
+ 'is_verified' => $this->email_verified_at !== null,
271
+ 'is_active' => $this->status === 'active',
272
+
273
+ // Formatted values
274
+ 'price_formatted' => '$' . number_format($this->price, 2),
275
+ ];
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Hiding Fields
282
+
283
+ ### Using Resource
284
+
285
+ ```php
286
+ public function toArray(Request $request): array
287
+ {
288
+ return [
289
+ 'id' => $this->id,
290
+ 'name' => $this->name,
291
+ 'email' => $this->email,
292
+ // Don't include: password, remember_token
293
+ ];
294
+ }
295
+ ```
296
+
297
+ ### Using Model $hidden
298
+
299
+ ```php
300
+ // Model
301
+ class User extends Model
302
+ {
303
+ protected $hidden = [
304
+ 'password',
305
+ 'remember_token',
306
+ ];
307
+ }
308
+ ```
309
+
310
+ ---
311
+
312
+ ## Additional Meta
313
+
314
+ ### For Single Resource
315
+
316
+ ```php
317
+ class OrderResource extends JsonResource
318
+ {
319
+ public function toArray(Request $request): array
320
+ {
321
+ return [
322
+ 'id' => $this->id,
323
+ 'total' => $this->total,
324
+ ];
325
+ }
326
+
327
+ public function with(Request $request): array
328
+ {
329
+ return [
330
+ 'meta' => [
331
+ 'currency' => 'USD',
332
+ 'tax_rate' => 0.1,
333
+ ],
334
+ ];
335
+ }
336
+ }
337
+
338
+ // Response
339
+ {
340
+ "data": { "id": 1, "total": 100 },
341
+ "meta": { "currency": "USD", "tax_rate": 0.1 }
342
+ }
343
+ ```
344
+
345
+ ### Custom Wrapper
346
+
347
+ ```php
348
+ // Disable "data" wrapper
349
+ class PlainResource extends JsonResource
350
+ {
351
+ public static $wrap = null;
352
+ }
353
+
354
+ // Response (no "data" key)
355
+ { "id": 1, "name": "John" }
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Resource Collection
361
+
362
+ ### Custom Collection Class
363
+
364
+ ```php
365
+ <?php
366
+
367
+ namespace App\Http\Resources;
368
+
369
+ use Illuminate\Http\Resources\Json\ResourceCollection;
370
+
371
+ class UserCollection extends ResourceCollection
372
+ {
373
+ public $collects = UserResource::class;
374
+
375
+ public function toArray(Request $request): array
376
+ {
377
+ return [
378
+ 'data' => $this->collection,
379
+ 'meta' => [
380
+ 'total_active' => $this->collection->where('status', 'active')->count(),
381
+ ],
382
+ ];
383
+ }
384
+ }
385
+
386
+ // Usage
387
+ return new UserCollection($users->paginate(15));
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Common Patterns
393
+
394
+ ### User Resource
395
+
396
+ ```php
397
+ class UserResource extends JsonResource
398
+ {
399
+ public function toArray(Request $request): array
400
+ {
401
+ return [
402
+ 'id' => $this->id,
403
+ 'name' => $this->name,
404
+ 'email' => $this->email,
405
+ 'avatar_url' => $this->avatar_url,
406
+ 'email_verified_at' => $this->email_verified_at?->toISOString(),
407
+ 'created_at' => $this->created_at?->toISOString(),
408
+ 'updated_at' => $this->updated_at?->toISOString(),
409
+
410
+ // Relations
411
+ 'profile' => new ProfileResource($this->whenLoaded('profile')),
412
+ 'roles' => RoleResource::collection($this->whenLoaded('roles')),
413
+ ];
414
+ }
415
+ }
416
+ ```
417
+
418
+ ### Post Resource (with Author)
419
+
420
+ ```php
421
+ class PostResource extends JsonResource
422
+ {
423
+ public function toArray(Request $request): array
424
+ {
425
+ return [
426
+ 'id' => $this->id,
427
+ 'title' => $this->title,
428
+ 'slug' => $this->slug,
429
+ 'content' => $this->content,
430
+ 'excerpt' => Str::limit($this->content, 150),
431
+ 'status' => $this->status,
432
+ 'published_at' => $this->published_at?->toISOString(),
433
+ 'created_at' => $this->created_at?->toISOString(),
434
+ 'updated_at' => $this->updated_at?->toISOString(),
435
+
436
+ // Author (always loaded)
437
+ 'author' => new UserResource($this->whenLoaded('author')),
438
+
439
+ // Comments (conditional)
440
+ 'comments' => CommentResource::collection($this->whenLoaded('comments')),
441
+ 'comments_count' => $this->whenCounted('comments'),
442
+
443
+ // Computed
444
+ 'is_published' => $this->status === 'published',
445
+ 'reading_time' => ceil(str_word_count($this->content) / 200) . ' min',
446
+ ];
447
+ }
448
+ }
449
+ ```
450
+
451
+ ### Order Resource (with Items)
452
+
453
+ ```php
454
+ class OrderResource extends JsonResource
455
+ {
456
+ public function toArray(Request $request): array
457
+ {
458
+ return [
459
+ 'id' => $this->id,
460
+ 'order_number' => $this->order_number,
461
+ 'status' => $this->status,
462
+ 'subtotal' => $this->subtotal,
463
+ 'tax' => $this->tax,
464
+ 'total' => $this->total,
465
+ 'currency' => 'JPY',
466
+ 'paid_at' => $this->paid_at?->toISOString(),
467
+ 'shipped_at' => $this->shipped_at?->toISOString(),
468
+ 'created_at' => $this->created_at?->toISOString(),
469
+
470
+ // Relations
471
+ 'customer' => new UserResource($this->whenLoaded('customer')),
472
+ 'items' => OrderItemResource::collection($this->whenLoaded('items')),
473
+
474
+ // Computed
475
+ 'items_count' => $this->whenCounted('items'),
476
+ 'can_cancel' => $this->status === 'pending',
477
+ 'can_refund' => in_array($this->status, ['paid', 'shipped']),
478
+ ];
479
+ }
480
+ }
481
+ ```
482
+
483
+ ---
484
+
485
+ ## Best Practices
486
+
487
+ ### ✅ DO
488
+
489
+ ```php
490
+ // Always use toISOString() for dates
491
+ 'created_at' => $this->created_at?->toISOString(),
492
+
493
+ // Use whenLoaded() for relationships
494
+ 'posts' => PostResource::collection($this->whenLoaded('posts')),
495
+
496
+ // Use when() for conditional fields
497
+ 'admin_notes' => $this->when($request->user()?->isAdmin(), $this->admin_notes),
498
+
499
+ // Use null-safe operator
500
+ 'profile' => new ProfileResource($this->whenLoaded('profile')),
501
+ ```
502
+
503
+ ### ❌ DON'T
504
+
505
+ ```php
506
+ // Don't expose sensitive fields
507
+ 'password' => $this->password,
508
+ 'remember_token' => $this->remember_token,
509
+
510
+ // Don't use model directly (causes N+1)
511
+ 'posts' => PostResource::collection($this->posts), // Load in controller!
512
+
513
+ // Don't format dates inconsistently
514
+ 'created_at' => $this->created_at->format('Y-m-d'),
515
+ 'updated_at' => $this->updated_at->toDateTimeString(),
516
+ ```