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.
- package/README.md +206 -28
- package/dist/cli.js +869 -564
- 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.
|
|
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
|
-
###
|
|
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
|
|
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
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|