routesync 1.0.35 → 1.0.39

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 (3) hide show
  1. package/README.md +206 -28
  2. package/dist/cli.js +869 -564
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,6 +14,7 @@ RouteSync does all of that. You point it at `routes/api.php` and it generates th
14
14
 
15
15
  ```bash
16
16
  # Step 1 — in your Laravel folder
17
+ npx routesync annotate --input routes/api.php # auto-inject #[Response] to controllers
17
18
  npx routesync scan --input routes/api.php --models
18
19
 
19
20
  # Step 2 — in your frontend folder
@@ -21,6 +22,8 @@ npx routesync generate --manifest routesync.manifest.json --output src/api --nex
21
22
  ```
22
23
 
23
24
  ```
25
+ ✔ Annotated 12 method(s) across 6 controller file(s)
26
+
24
27
  ✔ Found 35 routes, 19 models → routesync.manifest.json
25
28
 
26
29
  ✔ SDK generated → src/api
@@ -65,7 +68,35 @@ npm install routesync zod
65
68
 
66
69
  ## Full Workflow (Laravel + Next.js)
67
70
 
68
- ### 1. Scan routes & models
71
+ ### 1. Auto-annotate controllers (one-time setup)
72
+
73
+ Run this from your **Laravel project root** to auto-inject `#[Response]` attributes into every controller method:
74
+
75
+ ```bash
76
+ npx routesync annotate --input routes/api.php
77
+ ```
78
+
79
+ | Option | Description |
80
+ |---|---|
81
+ | `--input <file>` | Path to routes file (default: `routes/api.php`) |
82
+ | `--dry-run` | Preview what would be injected without writing files |
83
+ | `--force` | Re-annotate methods that already have `#[Response]` |
84
+
85
+ This command:
86
+ - Detects `return new XxxResource(...)` / `XxxResource::collection(...)` / `response()->json(new XxxResource(...))` in each controller method
87
+ - Resolves the model from the Resource's `@mixin` docblock (or strips the `Resource` suffix as fallback)
88
+ - Injects `#[Response(Model::class)]` or `#[Response(Model::class, collection: true)]` above the method
89
+ - Adds `use App\Attributes\Response;` to controller imports automatically
90
+ - Creates `app/Attributes/Response.php` if it doesn't exist yet
91
+
92
+ > **Tip:** Preview first with `--dry-run`:
93
+ > ```bash
94
+ > npx routesync annotate --input routes/api.php --dry-run
95
+ > ```
96
+
97
+ You only need to run this once, or again when you add new endpoints.
98
+
99
+ ### 2. Scan routes & models
69
100
 
70
101
  Run this from your **Laravel project root**:
71
102
 
@@ -92,28 +123,24 @@ npx routesync scan --input routes/api.php --models
92
123
  > cp ../backend/routesync.manifest.json .
93
124
  > ```
94
125
 
95
- ### 2. Generate the SDK
126
+ ### 3. Generate the SDK
96
127
 
97
128
  Run this from your **frontend project root**:
98
129
 
99
130
  ```bash
100
- npx routesync generate \
101
- --manifest routesync.manifest.json \
102
- --output src/api \
103
- --next-actions \
104
- --zod
131
+ npx routesync generate --manifest routesync.manifest.json --output src/api --next-actions --zod
105
132
  ```
106
133
 
107
134
  | Option | Default | Description |
108
135
  |---|---|---|
109
- | `--manifest` | `routesync.manifest.json` | Path to manifest from step 1 |
136
+ | `--manifest` | `routesync.manifest.json` | Path to manifest from step 2 |
110
137
  | `--output` | `src/api` | Output folder |
111
138
  | `--next-actions` | off | Generate `actions.ts` (Next.js Server Actions) |
112
139
  | `--zod` | off | Generate `schemas.ts` (Zod validation) |
113
140
  | `--no-hooks` | off | Skip generating `hooks.ts` |
114
141
  | `--msw` | off | Generate MSW mock handlers |
115
142
 
116
- > **Windows PowerShell note:** Do not use backslash `\` for line continuation — PowerShell treats it differently. Run the command on a single line:
143
+ > **Windows PowerShell note:** Do not use backslash `\` for line continuation. Run the command on a single line:
117
144
  >
118
145
  > ```powershell
119
146
  > npx routesync generate --manifest routesync.manifest.json --output src/api --next-actions --zod
@@ -133,7 +160,7 @@ src/api/
133
160
  └── models.ts ← Raw Eloquent model interfaces (when --models used)
134
161
  ```
135
162
 
136
- ### 3. Initialize the client
163
+ ### 4. Initialize the client
137
164
 
138
165
  Call `createClient` once at app startup (e.g. in your layout or provider):
139
166
 
@@ -147,7 +174,7 @@ createClient({
147
174
  })
148
175
  ```
149
176
 
150
- ### 4. Use in components
177
+ ### 5. Use in components
151
178
 
152
179
  ```tsx
153
180
  import { useApiQuery, useApiMutation } from 'routesync/react'
@@ -181,10 +208,9 @@ function AddToCart({ produkItemId }: { produkItemId: string }) {
181
208
  }
182
209
  ```
183
210
 
184
- ### 5. Use Server Actions (Next.js)
211
+ ### 6. Use Server Actions (Next.js)
185
212
 
186
213
  ```ts
187
- // In a Server Component or form action
188
214
  import { produkGetAction, cartPostItemsAction } from '@/api/actions'
189
215
 
190
216
  // GET — no params needed
@@ -200,6 +226,144 @@ const result = await produkGetIdAction({ params: { id: '42' } })
200
226
 
201
227
  ---
202
228
 
229
+ ## Response Type Inference
230
+
231
+ RouteSync automatically infers the TypeScript response type for each endpoint. The scanner works through **7 stages in order**, stopping at the first successful match.
232
+
233
+ ### How inference works
234
+
235
+ ```
236
+ Controller method
237
+
238
+
239
+ Stage 1: PHP 8 #[RouteSyncResponse] attribute on method ← most explicit
240
+
241
+
242
+ Stage 2: return new UserResource($user) in method body
243
+ │ ├─ Stage 2a: #[RouteSyncResponse] on Resource class
244
+ │ ├─ Stage 2b: @mixin \App\Models\User docblock
245
+ │ ├─ Stage 2c: __construct(User $user) type hint
246
+ │ ├─ Stage 2d: @var User $resource docblock
247
+ │ ├─ Stage 2e: Strip "Resource" suffix → App\Models\*
248
+ │ └─ Stage 2f: toArray() keys vs DB column matching
249
+
250
+
251
+ Stage 3: response()->json([...]) inline array
252
+ keys matched against DB columns (min score 2)
253
+
254
+
255
+ response: unknown ← annotate manually if all stages fail
256
+ ```
257
+
258
+ ### Zero-config inference (no annotation needed)
259
+
260
+ For the common convention `UserResource` → `User`, RouteSync infers automatically:
261
+
262
+ ```php
263
+ // ✅ Auto-detected — no annotation needed
264
+ public function show(User $user): JsonResponse
265
+ {
266
+ return new UserResource($user);
267
+ }
268
+
269
+ // ✅ Auto-detected — UserResource::collection → User[]
270
+ public function index(): JsonResponse
271
+ {
272
+ return UserResource::collection(User::all());
273
+ }
274
+ ```
275
+
276
+ ### Auto-annotate with CLI (recommended)
277
+
278
+ Instead of adding `#[Response]` by hand, let the CLI do it:
279
+
280
+ ```bash
281
+ # Preview first
282
+ npx routesync annotate --input routes/api.php --dry-run
283
+
284
+ # Apply
285
+ npx routesync annotate --input routes/api.php
286
+ ```
287
+
288
+ This scans every controller method, detects which Resource it returns, resolves the model, and injects `#[Response(Model::class)]` automatically. See [Auto-annotate controllers](#1-auto-annotate-controllers-one-time-setup) for details.
289
+
290
+ ### Manual annotation with PHP 8 Attribute
291
+
292
+ Use `#[RouteSyncResponse]` when auto-inference fails — for example when the Resource name doesn't match the model, or the response is a DTO/custom shape.
293
+
294
+ **Step 1 — Create the attribute class** (`app/Attributes/RouteSyncResponse.php`):
295
+
296
+ ```php
297
+ <?php
298
+
299
+ namespace App\Attributes;
300
+
301
+ use Attribute;
302
+
303
+ #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
304
+ class RouteSyncResponse
305
+ {
306
+ public function __construct(
307
+ public readonly string $model,
308
+ public readonly bool $collection = false,
309
+ ) {}
310
+ }
311
+ ```
312
+
313
+ **Step 2 — Annotate your controller method:**
314
+
315
+ ```php
316
+ use App\Attributes\RouteSyncResponse;
317
+ use App\Models\User;
318
+
319
+ class AuthController extends Controller
320
+ {
321
+ #[RouteSyncResponse(model: User::class)]
322
+ public function register(RegisterRequest $request): JsonResponse
323
+ {
324
+ $user = User::create($request->validated());
325
+ $token = $user->createToken('auth')->plainTextToken;
326
+ return response()->json(['token' => $token, 'user' => new UserResource($user)]);
327
+ }
328
+
329
+ #[RouteSyncResponse(model: User::class, collection: true)]
330
+ public function index(): JsonResponse
331
+ {
332
+ return response()->json(User::all());
333
+ }
334
+ }
335
+ ```
336
+
337
+ **Or annotate the Resource class directly** (applies to all endpoints that return this Resource):
338
+
339
+ ```php
340
+ use App\Attributes\RouteSyncResponse;
341
+ use App\Models\User;
342
+
343
+ #[RouteSyncResponse(model: User::class)]
344
+ class UserResource extends JsonResource
345
+ {
346
+ public function toArray($request): array
347
+ {
348
+ return ['id' => $this->id, 'name' => $this->name, 'email' => $this->email];
349
+ }
350
+ }
351
+ ```
352
+
353
+ > **Priority:** annotation on controller method > annotation on Resource class > auto-inference.
354
+
355
+ ### When to annotate manually
356
+
357
+ | Situation | Solution |
358
+ |---|---|
359
+ | Resource name doesn't match model (`PublicProfileResource` → `User`) | `#[RouteSyncResponse(model: User::class)]` |
360
+ | Response is a DTO, not an Eloquent model | Add model manually after generate |
361
+ | Controller returns `response()->json([...])` without a Resource | `#[RouteSyncResponse(model: User::class)]` |
362
+ | Multiple models in one response | Add model manually after generate |
363
+ | Response is still `unknown` after scan | Add `#[RouteSyncResponse]` attribute |
364
+
365
+ ---
366
+
203
367
  ## Data Transformation
204
368
 
205
369
  RouteSync handles all data mapping automatically:
@@ -225,13 +389,10 @@ import { defineApi, endpoint, resource } from 'routesync'
225
389
  createClient({ baseURL: 'https://api.myapp.com/api' })
226
390
 
227
391
  export const api = defineApi({
228
- // Basic endpoint
229
392
  auth: {
230
393
  login: endpoint<{ token: string }>({ method: 'POST', path: '/login' }),
231
394
  logout: endpoint({ method: 'POST', path: '/logout', auth: true }),
232
395
  },
233
-
234
- // With path params
235
396
  produk: {
236
397
  list: endpoint<ProdukItem[]>({ method: 'GET', path: '/produk' }),
237
398
  detail: endpoint<ProdukItem, { id: string }>({ method: 'GET', path: '/produk/:id' }),
@@ -239,8 +400,6 @@ export const api = defineApi({
239
400
  method: 'POST', path: '/produk', auth: true
240
401
  }),
241
402
  },
242
-
243
- // resource() — shared auth/headers for a group
244
403
  cart: resource({
245
404
  auth: true,
246
405
  endpoints: {
@@ -289,7 +448,7 @@ import { api } from '@/api/api'
289
448
  // GET
290
449
  const { data, isLoading } = useApiQuery(api.orders.get)
291
450
 
292
- // GET with params + query
451
+ // GET with params
293
452
  const { data } = useApiQuery(api.orders.getId, { params: { id: '1' } })
294
453
 
295
454
  // Mutation
@@ -302,8 +461,7 @@ mutation.mutate({ body: { produk_item_id: '5', qty: 1 } })
302
461
  ```ts
303
462
  import { generateHooks } from 'routesync'
304
463
 
305
- const hooks = generateHooks(api)
306
- const { useOrdersGet, useCartPostItems } = hooks
464
+ const { useOrdersGet, useCartPostItems } = generateHooks(api)
307
465
  // GET/DELETE → useQuery, everything else → useMutation
308
466
  ```
309
467
 
@@ -312,8 +470,7 @@ const { useOrdersGet, useCartPostItems } = hooks
312
470
  ```ts
313
471
  import { createHooks } from 'routesync/react'
314
472
 
315
- const cartHooks = createHooks(api.cart)
316
- const { usePostItems, usePatchItemsProdukItemId } = cartHooks
473
+ const { usePostItems, usePatchItemsProdukItemId } = createHooks(api.cart)
317
474
  ```
318
475
 
319
476
  ---
@@ -339,6 +496,15 @@ When using `--zod` with `routesync generate`, `schemas.ts` is generated from you
339
496
  ## CLI Reference
340
497
 
341
498
  ```bash
499
+ # Auto-inject #[Response] attributes into controller methods
500
+ npx routesync annotate --input routes/api.php
501
+
502
+ # Preview without writing files
503
+ npx routesync annotate --input routes/api.php --dry-run
504
+
505
+ # Re-annotate already-annotated methods
506
+ npx routesync annotate --input routes/api.php --force
507
+
342
508
  # Scan Laravel routes only
343
509
  npx routesync scan --input routes/api.php
344
510
 
@@ -361,16 +527,28 @@ npx routesync watch --input routes/api.php --output src/api
361
527
 
362
528
  ```
363
529
  routes/api.php + app/Models/
364
- ↓ routesync scan --models
530
+
531
+
532
+ npx routesync annotate ← inject #[Response] into controllers
533
+
534
+
535
+ npx routesync scan --models ← read routes + DB columns → manifest
536
+
537
+
365
538
  routesync.manifest.json
366
- ↓ routesync generate
539
+
540
+
541
+ npx routesync generate ← generate SDK from manifest
542
+
543
+
367
544
  src/api/
368
- ├── api.ts ← defineApi + endpoints
545
+ ├── api.ts ← defineApi + endpoints + Contract types
369
546
  ├── types.ts ← interfaces from DB columns
370
547
  ├── hooks.ts ← TanStack Query hooks
371
548
  ├── actions.ts ← Next.js Server Actions
372
549
  └── schemas.ts ← Zod schemas
373
-
550
+
551
+
374
552
  React / Vue / Next.js
375
553
  ```
376
554
 
@@ -381,7 +559,7 @@ The CLI parses your route file via PHP reflection (using Laravel's own bootstrap
381
559
  ## Requirements
382
560
 
383
561
  - Node.js >= 20
384
- - PHP available in PATH (for `scan --models`)
562
+ - PHP available in PATH (for `scan --models` and `annotate`)
385
563
  - Laravel project with database accessible (for `scan --models`)
386
564
 
387
565
  ## License